Class: KaiserRuby::Transformer

Inherits:
Object
  • Object
show all
Defined in:
lib/kaiser_ruby/transformer.rb

Overview

taking the intermediate tree output of parsing, output Ruby code

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tree) ⇒ Transformer

Returns a new instance of Transformer.



8
9
10
11
12
13
14
15
16
17
18
# 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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(rule, *args, &_block) ⇒ Object

Raises:

  • (ArgumentError)


63
64
65
# File 'lib/kaiser_ruby/transformer.rb', line 63

def method_missing(rule, *args, &_block)
  raise ArgumentError, "missing Transform rule: #{rule}, #{args}"
end

Instance Attribute Details

#outputObject (readonly)

Returns the value of attribute output.



6
7
8
# File 'lib/kaiser_ruby/transformer.rb', line 6

def output
  @output
end

#parsed_treeObject (readonly)

Returns the value of attribute parsed_tree.



6
7
8
# File 'lib/kaiser_ruby/transformer.rb', line 6

def parsed_tree
  @parsed_tree
end

Instance Method Details

#additional_argument_transformation(argument) ⇒ Object



272
273
274
275
276
277
278
279
280
# 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:]]/) ⇒ Object



402
403
404
# 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

#normalize_num(num) ⇒ Object



406
407
408
# File 'lib/kaiser_ruby/transformer.rb', line 406

def normalize_num(num)
  num.modulo(1).zero? ? num.to_i : num
end

#select_transformer(object) ⇒ Object



58
59
60
61
# 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) ⇒ Object

private



398
399
400
# File 'lib/kaiser_ruby/transformer.rb', line 398

def str_to_num(string)
  filter_string(string).map { |e| e.length % 10 }.join
end

#transformObject



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 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) ⇒ Object



146
147
148
149
150
151
# 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) ⇒ Object



369
370
371
372
373
374
# 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) ⇒ Object



137
138
139
140
141
142
143
144
# 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) ⇒ Object



174
175
176
177
178
179
180
# 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) ⇒ Object



95
96
97
98
99
# 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) ⇒ Object



89
90
91
92
93
# 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) ⇒ Object



182
183
184
185
186
187
# 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) ⇒ Object



167
168
169
170
171
172
# 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) ⇒ Object



289
290
291
292
293
294
295
296
# 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) ⇒ Object



261
262
263
264
265
266
267
268
269
270
# 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) ⇒ Object



312
313
314
315
316
317
# 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) ⇒ Object



354
355
356
357
358
359
360
361
362
363
364
365
366
367
# 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) ⇒ Object



196
197
198
199
200
201
202
203
204
205
# 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) ⇒ Object



121
122
123
# File 'lib/kaiser_ruby/transformer.rb', line 121

def transform_function_name(object)
  object[:function_name]
end

#transform_gt(object) ⇒ Object



326
327
328
329
330
331
# 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) ⇒ Object



333
334
335
336
337
338
# 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) ⇒ Object



282
283
284
285
286
287
# 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) ⇒ Object



189
190
191
192
193
194
# 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) ⇒ Object



319
320
321
322
323
324
# 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) ⇒ Object



78
79
80
# File 'lib/kaiser_ruby/transformer.rb', line 78

def transform_listen(_object)
  "print '> '\n$stdin.gets.chomp"
end

#transform_listen_to(object) ⇒ Object



73
74
75
76
# 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) ⇒ Object



117
118
119
# File 'lib/kaiser_ruby/transformer.rb', line 117

def transform_local_variable_name(object)
  object[:local_variable_name]
end

#transform_lt(object) ⇒ Object



340
341
342
343
344
345
# 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) ⇒ Object



347
348
349
350
351
352
# 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) ⇒ Object



153
154
155
156
157
158
# 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) ⇒ Object



389
390
391
392
393
394
# 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) ⇒ Object



376
377
378
379
380
# File 'lib/kaiser_ruby/transformer.rb', line 376

def transform_not(object)
  arg = select_transformer(object[:not])

  "!#{arg}"
end

#transform_number(object) ⇒ Object



133
134
135
# File 'lib/kaiser_ruby/transformer.rb', line 133

def transform_number(object)
  normalize_num(object[:number])
end

#transform_number_literal(object) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
# 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) ⇒ Object



382
383
384
385
386
387
# 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) ⇒ Object



207
208
209
# 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) ⇒ Object



227
228
229
230
231
232
233
# 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) ⇒ Object



211
212
213
214
215
216
217
# 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) ⇒ Object



219
220
221
222
223
224
225
# 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) ⇒ Object



67
68
69
70
71
# 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) ⇒ Object



125
126
127
# File 'lib/kaiser_ruby/transformer.rb', line 125

def transform_pronoun(_object)
  @last_variable
end

#transform_return(object) ⇒ Object



82
83
84
85
86
87
# 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) ⇒ Object



129
130
131
# File 'lib/kaiser_ruby/transformer.rb', line 129

def transform_string(object)
  object[:string]
end

#transform_subtraction(object) ⇒ Object



160
161
162
163
164
165
# 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) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
# 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) ⇒ Object



305
306
307
308
309
310
# 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) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# 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) ⇒ Object



298
299
300
301
302
303
# 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