class KaiserRuby::Transformer

taking the intermediate tree output of parsing, output Ruby code

Attributes

output[R]
parsed_tree[R]

Public Class Methods

new(tree) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 8
def initialize(tree)
  @parsed_tree = tree
  @output = []
  @method_names = []
  @global_variables = []
  @nested_functions = {}
  @nesting = 0
  @indentation = ''
  @lnum = 0
  @current_scope = [nil]
end

Public Instance Methods

additional_argument_transformation(argument) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 272
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 !~ /==|>|>=|<|<=|!=/

  arg
end
filter_string(string, rxp: /[[:alpha:]]/) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 402
def filter_string(string, rxp: /[[:alpha:]]/)
  string.to_s.split(/\s+/).map { |e| e.chars.select { |c| c =~ rxp }.join }.reject(&:empty?)
end
method_missing(rule, *args, &_block) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 63
def method_missing(rule, *args, &_block)
  raise ArgumentError, "missing Transform rule: #{rule}, #{args}"
end
normalize_num(num) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 406
def normalize_num(num)
  num.modulo(1).zero? ? num.to_i : num
end
select_transformer(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 58
def select_transformer(object)
  key = object.keys.first
  send("transform_#{key}", object)
end
str_to_num(string) click to toggle source

private

# File lib/kaiser_ruby/transformer.rb, line 398
def str_to_num(string)
  filter_string(string).map { |e| e.length % 10 }.join
end
transform() click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 20
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

  # 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)
    @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.positive?
    @nesting -= 1
    @indentation = '  ' * @nesting
    @output << @indentation + 'end'
  end

  @output << '' if @output.size > 1
  @output.join("\n")
end
transform_addition(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 146
def transform_addition(object)
  left = select_transformer(object[:addition][:left])
  right = select_transformer(object[:addition][:right])

  "#{left} + #{right}"
end
transform_and(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 369
def transform_and(object)
  left = select_transformer(object[:and][:left])
  right = select_transformer(object[:and][:right])

  "#{left} && #{right}"
end
transform_argument_list(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 137
def transform_argument_list(object)
  list = []
  object[:argument_list].each do |arg|
    list << select_transformer(arg)
  end

  list.join(', ')
end
transform_assignment(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 174
def transform_assignment(object)
  left = select_transformer(object[:assignment][:left])
  right = select_transformer(object[:assignment][:right])

  @last_variable = left
  "#{left} = #{right}"
end
transform_break(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 95
def transform_break(object)
  raise KaiserRuby::RockstarSyntaxError, 'Break used outside of a loop' if object[:nesting].to_i.zero?

  'break'
end
transform_continue(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 89
def transform_continue(object)
  raise KaiserRuby::RockstarSyntaxError, 'Continue used outside of a loop' if object[:nesting].to_i.zero?

  'next'
end
transform_decrement(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 182
def transform_decrement(object)
  argument = select_transformer(object[:decrement])
  amount = object.dig(:decrement, :amount)

  "#{argument} -= #{amount}"
end
transform_division(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 167
def transform_division(object)
  left = select_transformer(object[:division][:left])
  right = select_transformer(object[:division][:right])

  "#{left} / #{right}"
end
transform_else(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 289
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

  @else_already = object[:nesting_start_line]

  'else'
end
transform_empty_line(_object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 261
def transform_empty_line(_object)
  if @nesting.zero?
    ''
  elsif @nesting == 1
    "end\n"
  else
    @else_already = nil
    "end\n"
  end
end
transform_equality(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 312
def transform_equality(object)
  left = select_transformer(object[:equality][:left])
  right = select_transformer(object[:equality][:right])

  "#{left} == #{right}"
end
transform_function(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 354
def transform_function(object)
  funcname = transform_function_name(object[:function][:name])
  argument = select_transformer(object[:function][:argument])

  if @current_scope.last.nil?
    @method_names << funcname
    "def #{funcname}(#{argument})"
  else
    @nested_functions[@current_scope.last] ||= []
    @nested_functions[@current_scope.last].push funcname

    "#{funcname} = ->(#{argument}) do"
  end
end
transform_function_call(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 196
def transform_function_call(object)
  func_name = select_transformer(object[:function_call][:left])
  argument = select_transformer(object[:function_call][:right])

  if @nested_functions[@current_scope.last]&.include?(func_name)
    "#{func_name}.call(#{argument})"
  else
    "#{func_name}(#{argument})"
  end
end
transform_function_name(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 121
def transform_function_name(object)
  object[:function_name]
end
transform_gt(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 326
def transform_gt(object)
  left = select_transformer(object[:gt][:left])
  right = select_transformer(object[:gt][:right])

  "#{left} > #{right}"
end
transform_gte(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 333
def transform_gte(object)
  left = select_transformer(object[:gte][:left])
  right = select_transformer(object[:gte][:right])

  "#{left} >= #{right}"
end
transform_if(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 282
def transform_if(object)
  argument = select_transformer(object[:if][:argument])
  argument = additional_argument_transformation(argument)

  "if #{argument}"
end
transform_increment(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 189
def transform_increment(object)
  argument = select_transformer(object[:increment])
  amount = object.dig(:increment, :amount)

  "#{argument} += #{amount}"
end
transform_inequality(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 319
def transform_inequality(object)
  left = select_transformer(object[:inequality][:left])
  right = select_transformer(object[:inequality][:right])

  "#{left} != #{right}"
end
transform_listen(_object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 78
def transform_listen(_object)
  "print '> '\n$stdin.gets.chomp"
end
transform_listen_to(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 73
def transform_listen_to(object)
  var = select_transformer(object[:listen_to])
  "print '> '\n__input = $stdin.gets.chomp\n#{var} = Float(__input) rescue __input"
end
transform_local_variable_name(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 117
def transform_local_variable_name(object)
  object[:local_variable_name]
end
transform_lt(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 340
def transform_lt(object)
  left = select_transformer(object[:lt][:left])
  right = select_transformer(object[:lt][:right])

  "#{left} < #{right}"
end
transform_lte(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 347
def transform_lte(object)
  left = select_transformer(object[:lte][:left])
  right = select_transformer(object[:lte][:right])

  "#{left} <= #{right}"
end
transform_multiplication(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 153
def transform_multiplication(object)
  left = select_transformer(object[:multiplication][:left])
  right = select_transformer(object[:multiplication][:right])

  "#{left} * #{right}"
end
transform_nor(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 389
def transform_nor(object)
  left = select_transformer(object[:nor][:left])
  right = select_transformer(object[:nor][:right])

  "!(#{left} || #{right})"
end
transform_not(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 376
def transform_not(object)
  arg = select_transformer(object[:not])

  "!#{arg}"
end
transform_number(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 133
def transform_number(object)
  normalize_num(object[:number])
end
transform_number_literal(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 235
def transform_number_literal(object)
  string = object[:number_literal]
  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
transform_or(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 382
def transform_or(object)
  left = select_transformer(object[:or][:left])
  right = select_transformer(object[:or][:right])

  "#{left} || #{right}"
end
transform_passed_function_call(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 207
def transform_passed_function_call(object)
  transform_function_call(object[:passed_function_call])
end
transform_poetic_number(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 227
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
transform_poetic_string(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 211
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
transform_poetic_type(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 219
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
transform_print(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 67
def transform_print(object)
  var = select_transformer(object[:print])

  "puts (#{var}).to_s"
end
transform_pronoun(_object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 125
def transform_pronoun(_object)
  @last_variable
end
transform_return(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 82
def transform_return(object)
  raise KaiserRuby::RockstarSyntaxError, 'Return used outside of a function' if object[:nesting].to_i.zero?

  var = select_transformer(object[:return])
  "return #{var}"
end
transform_string(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 129
def transform_string(object)
  object[:string]
end
transform_subtraction(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 160
def transform_subtraction(object)
  left = select_transformer(object[:subtraction][:left])
  right = select_transformer(object[:subtraction][:right])

  "#{left} - #{right}"
end
transform_type(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 248
def transform_type(object)
  case object[:type]
  when 'mysterious'
    'KaiserRuby::Mysterious.new'
  when 'null'
    'nil'
  when 'true'
    'true'
  when 'false'
    'false'
  end
end
transform_until(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 305
def transform_until(object)
  argument = select_transformer(object[:until][:argument])
  argument = additional_argument_transformation(argument)

  "until #{argument}"
end
transform_variable_name(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 101
def transform_variable_name(object)
  varname = object[:variable_name]

  if object[:type] == :assignment
    varname = "@#{varname}" if @global_variables&.include?(varname)
  elsif @global_variables.include?(varname)
    varname = @method_names.include?(varname) ? varname : "@#{varname}"
  end

  # 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
transform_while(object) click to toggle source
# File lib/kaiser_ruby/transformer.rb, line 298
def transform_while(object)
  argument = select_transformer(object[:while][:argument])
  argument = additional_argument_transformation(argument)

  "while #{argument}"
end