lib/kaiser_ruby/transformer.rb in kaiser-ruby-0.7.1 vs lib/kaiser_ruby/transformer.rb in kaiser-ruby-0.8

- old
+ new

@@ -1,41 +1,56 @@ +# frozen_string_literal: true + module KaiserRuby + # taking the intermediate tree output of parsing, output Ruby code class Transformer attr_reader :parsed_tree, :output def initialize(tree) @parsed_tree = tree @output = [] @method_names = [] + @global_variables = [] + @nested_functions = {} @nesting = 0 @indentation = '' @lnum = 0 + @current_scope = [nil] end def transform @last_variable = nil # name of last used variable for pronouns @else_already = nil # line number of a current if block, so we can avoid double else - @local_variables = [] # locally defined variable names in current function block + # first pass over the tree to find out which variables are global and which are not + # in case some are declared *after* the function definition that uses them + @parsed_tree.each do |line_object| + next unless line_object[:current_scope].nil? + + line_object.extend(Hashie::Extensions::DeepLocate) + + line_object.deep_locate(:variable_name).each do |vname_obj| + @global_variables.push vname_obj.dig(:variable_name) + end + end + @global_variables = @global_variables.compact.uniq + @parsed_tree.each_with_index do |line_object, lnum| + @current_scope.push(line_object[:current_scope]) unless @current_scope.last == line_object[:current_scope] @lnum = lnum transformed_line = select_transformer(line_object) - if line_object[:nesting] - @nesting = line_object[:nesting] - else - @nesting = 0 - end - - @indentation = ' ' * @nesting + @nesting = line_object[:nesting] || 0 + actual_nesting = line_object.key?(:else) ? @nesting - 1 : @nesting + @indentation = ' ' * actual_nesting @output << @indentation + transformed_line end # at end of file, close all the blocks that are still started - while @nesting > 0 + while @nesting.positive? @nesting -= 1 @indentation = ' ' * @nesting - @output << @indentation + "end" + @output << @indentation + 'end' end @output << '' if @output.size > 1 @output.join("\n") end @@ -43,19 +58,18 @@ def select_transformer(object) key = object.keys.first send("transform_#{key}", object) end - def method_missing(m, *args, &block) - raise ArgumentError, "missing Transform rule: #{m}, #{args}" + def method_missing(rule, *args, &_block) + raise ArgumentError, "missing Transform rule: #{rule}, #{args}" end - # transform language tree into Ruby - def transform_print(object) var = select_transformer(object[:print]) - "puts #{var}" + + "puts (#{var}).to_s" end def transform_listen_to(object) var = select_transformer(object[:listen_to]) "print '> '\n__input = $stdin.gets.chomp\n#{var} = Float(__input) rescue __input" @@ -64,40 +78,41 @@ def transform_listen(_object) "print '> '\n$stdin.gets.chomp" end def transform_return(object) - raise KaiserRuby::RockstarSyntaxError, "Return used outside of a function" if object[:nesting].to_i.zero? + raise KaiserRuby::RockstarSyntaxError, 'Return used outside of a function' if object[:nesting].to_i.zero? + var = select_transformer(object[:return]) "return #{var}" end def transform_continue(object) - raise KaiserRuby::RockstarSyntaxError, "Continue used outside of a loop" if object[:nesting].to_i.zero? - "next" + raise KaiserRuby::RockstarSyntaxError, 'Continue used outside of a loop' if object[:nesting].to_i.zero? + + 'next' end def transform_break(object) - raise KaiserRuby::RockstarSyntaxError, "Break used outside of a loop" if object[:nesting].to_i.zero? - "break" + raise KaiserRuby::RockstarSyntaxError, 'Break used outside of a loop' if object[:nesting].to_i.zero? + + 'break' end def transform_variable_name(object) varname = object[:variable_name] + if object[:type] == :assignment - if @local_variables.empty? - varname = "@#{varname}" - else - @local_variables << varname - end - else - unless @local_variables.include?(varname) - varname = @method_names.include?(varname) ? varname : "@#{varname}" - end + varname = "@#{varname}" if @global_variables&.include?(varname) + elsif @global_variables.include?(varname) + varname = @method_names.include?(varname) ? varname : "@#{varname}" end - @last_variable = varname + # have to break this to make 99 beers example work as it only updates the pronoun + # on assignment, which is technically a bug but seems like a good feature though + # so will most likely make it way to spec as is + # @last_variable = varname varname end def transform_local_variable_name(object) object[:local_variable_name] @@ -114,11 +129,11 @@ def transform_string(object) object[:string] end def transform_number(object) - object[:number] + normalize_num(object[:number]) end def transform_argument_list(object) list = [] object[:argument_list].each do |arg| @@ -129,196 +144,228 @@ end def transform_addition(object) left = select_transformer(object[:addition][:left]) right = select_transformer(object[:addition][:right]) + "#{left} + #{right}" end def transform_multiplication(object) left = select_transformer(object[:multiplication][:left]) right = select_transformer(object[:multiplication][:right]) + "#{left} * #{right}" end def transform_subtraction(object) left = select_transformer(object[:subtraction][:left]) right = select_transformer(object[:subtraction][:right]) + "#{left} - #{right}" end def transform_division(object) left = select_transformer(object[:division][:left]) right = select_transformer(object[:division][:right]) + "#{left} / #{right}" end def transform_assignment(object) left = select_transformer(object[:assignment][:left]) right = select_transformer(object[:assignment][:right]) + + @last_variable = left "#{left} = #{right}" end def transform_decrement(object) argument = select_transformer(object[:decrement]) amount = object.dig(:decrement, :amount) + "#{argument} -= #{amount}" end def transform_increment(object) argument = select_transformer(object[:increment]) amount = object.dig(:increment, :amount) + "#{argument} += #{amount}" end def transform_function_call(object) func_name = select_transformer(object[:function_call][:left]) argument = select_transformer(object[:function_call][:right]) - "#{func_name}(#{argument})" + if @nested_functions[@current_scope.last]&.include?(func_name) + "#{func_name}.call(#{argument})" + else + "#{func_name}(#{argument})" + end end def transform_passed_function_call(object) - return transform_function_call(object[:passed_function_call]) + transform_function_call(object[:passed_function_call]) end def transform_poetic_string(object) var = select_transformer(object[:poetic_string][:left]) value = select_transformer(object[:poetic_string][:right]) + @last_variable = var "#{var} = #{value}" end def transform_poetic_type(object) var = select_transformer(object[:poetic_type][:left]) value = select_transformer(object[:poetic_type][:right]) + + @last_variable = var "#{var} = #{value}" end def transform_poetic_number(object) var = select_transformer(object[:poetic_number][:left]) value = select_transformer(object[:poetic_number][:right]) + + @last_variable = var "#{var} = #{value}" end def transform_number_literal(object) string = object[:number_literal] - if string.include?('.') - string.split('.', 2).map do |sub| - str_to_num(sub.strip) - end.join('.').to_f - else - str_to_num(string).to_f - end + out = if string.include?('.') + string.split('.', 2).map do |sub| + str_to_num(sub.strip) + end.join('.').to_f + else + str_to_num(string).to_f + end + + normalize_num(out) end def transform_type(object) case object[:type] - when "nil" + when 'mysterious' + 'KaiserRuby::Mysterious.new' + when 'null' 'nil' - when "null" - '0.0' - when "true" + when 'true' 'true' - when "false" + when 'false' 'false' end end def transform_empty_line(_object) - if @nesting == 0 - return "" + if @nesting.zero? + '' elsif @nesting == 1 - @local_variables = [] - return "end\n" + "end\n" else @else_already = nil - return "end\n" + "end\n" end end def additional_argument_transformation(argument) # testing function existence arg = @method_names.include?(argument) ? "defined?(#{argument})" : argument # single variable without any operator needs to return a refined boolean arg = "#{arg}.to_bool" if arg !~ /==|>|>=|<|<=|!=/ - return arg + arg end def transform_if(object) argument = select_transformer(object[:if][:argument]) argument = additional_argument_transformation(argument) + "if #{argument}" end def transform_else(object) - raise KaiserRuby::RockstarSyntaxError, "Else outside an if block" if object[:nesting].to_i.zero? - raise KaiserRuby::RockstarSyntaxError, "Double else inside if block" if @else_already != nil && object[:nesting_start_line] == @else_already + raise KaiserRuby::RockstarSyntaxError, 'Else outside an if block' if object[:nesting].to_i.zero? + raise KaiserRuby::RockstarSyntaxError, 'Double else inside if block' if !@else_already.nil? && object[:nesting_start_line] == @else_already @else_already = object[:nesting_start_line] - "else" + + 'else' end def transform_while(object) argument = select_transformer(object[:while][:argument]) argument = additional_argument_transformation(argument) + "while #{argument}" end def transform_until(object) argument = select_transformer(object[:until][:argument]) argument = additional_argument_transformation(argument) + "until #{argument}" end def transform_equality(object) left = select_transformer(object[:equality][:left]) right = select_transformer(object[:equality][:right]) + "#{left} == #{right}" end def transform_inequality(object) left = select_transformer(object[:inequality][:left]) right = select_transformer(object[:inequality][:right]) + "#{left} != #{right}" end def transform_gt(object) left = select_transformer(object[:gt][:left]) right = select_transformer(object[:gt][:right]) + "#{left} > #{right}" end def transform_gte(object) left = select_transformer(object[:gte][:left]) right = select_transformer(object[:gte][:right]) + "#{left} >= #{right}" end def transform_lt(object) left = select_transformer(object[:lt][:left]) right = select_transformer(object[:lt][:right]) + "#{left} < #{right}" end def transform_lte(object) left = select_transformer(object[:lte][:left]) right = select_transformer(object[:lte][:right]) + "#{left} <= #{right}" end def transform_function(object) funcname = transform_function_name(object[:function][:name]) argument = select_transformer(object[:function][:argument]) - # save method name and make local variables out of the function arguments - @method_names << funcname - @local_variables = argument.split(', ') + if @current_scope.last.nil? + @method_names << funcname + "def #{funcname}(#{argument})" + else + @nested_functions[@current_scope.last] ||= [] + @nested_functions[@current_scope.last].push funcname - "def #{funcname}(#{argument})" + "#{funcname} = ->(#{argument}) do" + end end def transform_and(object) left = select_transformer(object[:and][:left]) right = select_transformer(object[:and][:right]) @@ -326,10 +373,11 @@ "#{left} && #{right}" end def transform_not(object) arg = select_transformer(object[:not]) + "!#{arg}" end def transform_or(object) left = select_transformer(object[:or][:left]) @@ -350,9 +398,13 @@ def str_to_num(string) filter_string(string).map { |e| e.length % 10 }.join end def filter_string(string, rxp: /[[:alpha:]]/) - string.to_s.split(/\s+/).map { |e| e.chars.select { |c| c =~ rxp }.join }.reject { |a| a.empty? } + string.to_s.split(/\s+/).map { |e| e.chars.select { |c| c =~ rxp }.join }.reject(&:empty?) end + + def normalize_num(num) + num.modulo(1).zero? ? num.to_i : num + end end -end \ No newline at end of file +end