Class: Layo::BaseInterpreter

Inherits:
Object
  • Object
show all
Defined in:
lib/layo/base_interpreter.rb

Direct Known Subclasses

InteractiveInterpreter, Interpreter

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input = STDIN, output = STDOUT) ⇒ BaseInterpreter

Returns a new instance of BaseInterpreter.



5
6
7
# File 'lib/layo/base_interpreter.rb', line 5

def initialize(input = STDIN, output = STDOUT)
  @input, @output = input, output
end

Instance Attribute Details

#inputObject

Returns the value of attribute input.



3
4
5
# File 'lib/layo/base_interpreter.rb', line 3

def input
  @input
end

#outputObject

Returns the value of attribute output.



3
4
5
# File 'lib/layo/base_interpreter.rb', line 3

def output
  @output
end

Instance Method Details

#call_func(name, arguments) ⇒ Object



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/layo/base_interpreter.rb', line 285

def call_func(name, arguments)
  function = @functions[name]
  # Replace variable table by 'clean' variable table inside functions
  old_table = @vtable
  @vtable = create_variable_table
  function[:args].each_index do |index|
    @vtable[function[:args][index]] = arguments[index]
  end
  retval = nil
  retval = catch :return do
    breaked = true
    catch(:break) do
      eval_block(function[:block])
      breaked = false
    end
    retval = { type: :noob, value: nil } if breaked
  end
  retval = @vtable['IT'] if retval.nil?
  @vtable = old_table
  retval
end

#cast(var, to, implicit = true) ⇒ Object

Casts given variable ‘var’ into type ‘to’ Returns only value part of the variable, type will be ‘to’ anyway



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/layo/base_interpreter.rb', line 188

def cast(var, to, implicit = true)
  return var[:value] if var[:type] == to
  return nil if to == :noob
  case var[:type]
    when :noob
      if implicit && to != :troof
        raise RuntimeError, "NOOB cannot be implicitly cast into #{to.to_s.upcase}"
      end
      return false if to == :troof
      return 0 if to == :numbr
      return 0.0 if to == :numbar
      return ''
    when :troof
      return (var[:value] ? 1 : 0) if to == :numbr
      return (var[:value] ? 1.0 : 0.0) if to == :numbar
      return (var[:value] ? 'WIN' : 'FAIL')
    when :numbr
      return (var[:value].zero? ? false : true) if to == :troof
      return var[:value].to_f if to == :numbar
      return var[:value].to_s
    when :numbar
      return (var[:value].zero? ? false : true) if to == :troof
      return var[:value].to_int if to == :numbr
      # Truncate to 2 digits after decimal point
      return ((var[:value] * 100).floor / 100.0).to_s
    else
      return !var[:value].empty? if to == :troof
      if to == :numbr
        return var[:value].to_i if var[:value].lol_integer?
        raise RuntimeError, "'#{var[:value]}' is not a valid integer"
      end
      return var[:value].to_f if var[:value].lol_float?
      raise RuntimeError, "'#{var[:value]}' is not a valid float"
  end
end

#create_variable_tableObject



9
10
11
12
13
14
15
# File 'lib/layo/base_interpreter.rb', line 9

def create_variable_table
  table = Hash.new do |hash, key|
    raise RuntimeError, "Variable '#{key}' is not declared"
  end
  table['IT'] = { type: :noob, value: nil}
  table
end

#eval_assignment_stmt(stmt) ⇒ Object



49
50
51
52
53
# File 'lib/layo/base_interpreter.rb', line 49

def eval_assignment_stmt(stmt)
  # We should access by variable name first to ensure that it is defined
  @vtable[stmt.identifier]
  @vtable[stmt.identifier] = eval_expr(stmt.expression)
end

#eval_binary_expr(expr) ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/layo/base_interpreter.rb', line 228

def eval_binary_expr(expr)
  l = eval_expr(expr.left)
  r = eval_expr(expr.right)
  methods = {
    :sum_of => :+, :diff_of => :-, :produkt_of => :*, :quoshunt_of => :/,
    :mod_of => :modulo, :both_of => :&, :either_of => :|, :won_of => :^,
    :both_saem => :==, :diffrint => :!=
  }
  case expr.operator
    when :sum_of, :diff_of, :produkt_of, :quoshunt_of, :mod_of, :biggr_of, :smallr_of
      type = l[:type] == :numbar || r[:type] == :numbar ||
        (l[:type] == :yarn && l[:value].lol_float?) ||
        (r[:type] == :yarn && r[:value].lol_float?) ? :numbar : :numbr
      l, r = cast(l, type), cast(r, type)
      if expr.operator == :biggr_of
        value = [l, r].max
      elsif expr.operator == :smallr_of
        value = [l, r].min
      else
        value = l.send(methods[expr.operator], r)
      end
    when :both_saem, :diffrint
      type = :troof
      if (l[:type] == :numbr && r[:type] == :numbar) ||
        (l[:type] == :numbar && r[:type] == :numbr)
        l, r = cast(l, :numbar), cast(r, :numbar)
      elsif l[:type] != r[:type]
        raise RuntimeError, 'Operands must have same type'
      end
      value = l.send(methods[expr.operator], r)
    else
      type = :troof
      l, r = cast(l, :troof), cast(r, :troof)
      value = l.send(methods[expr.operator], r)
  end
  { type: type, value: value }
end

#eval_block(block) ⇒ Object



42
43
44
45
46
47
# File 'lib/layo/base_interpreter.rb', line 42

def eval_block(block)
  block.each do |stmt|
    @stmt_line = stmt.line
    send("eval_#{stmt.type}_stmt", stmt)
  end
end

#eval_break_stmt(stmt) ⇒ Object



55
56
57
# File 'lib/layo/base_interpreter.rb', line 55

def eval_break_stmt(stmt)
  throw :break
end

#eval_cast_expr(expr) ⇒ Object



266
267
268
269
# File 'lib/layo/base_interpreter.rb', line 266

def eval_cast_expr(expr)
  casted_expr = eval_expr(expr.being_casted)
  { type: expr.to, value: cast(casted_expr, expr.to, false) }
end

#eval_cast_stmt(stmt) ⇒ Object



59
60
61
62
63
# File 'lib/layo/base_interpreter.rb', line 59

def eval_cast_stmt(stmt)
  var = @vtable[stmt.identifier]
  var[:value] = cast(var, stmt.to, false)
  var[:type] = stmt.to
end

#eval_condition_stmt(stmt) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/layo/base_interpreter.rb', line 80

def eval_condition_stmt(stmt)
  if cast(@vtable['IT'], :troof)
    # if block
    eval_block(stmt.then)
  else
    # else if blocks
    condition_met = false
    stmt.elseif.each do |elseif|
      condition = eval_expr(elseif[:condition])
      if condition_met = cast(condition, :troof)
        eval_block(elseif[:block])
        break
      end
    end
    unless condition_met || stmt.else.nil?
      # else block
      eval_block(stmt.else)
    end
  end
end

#eval_constant_expr(expr) ⇒ Object



271
272
273
274
275
# File 'lib/layo/base_interpreter.rb', line 271

def eval_constant_expr(expr)
  mapping = { boolean: :troof, string: :yarn, integer: :numbr, float: :numbar }
  value = expr.vtype == :string ? interpolate_string(expr.value) : expr.value
  { type: mapping[expr.vtype], value: value }
end

#eval_declaration_stmt(stmt) ⇒ Object



65
66
67
68
69
70
71
72
73
# File 'lib/layo/base_interpreter.rb', line 65

def eval_declaration_stmt(stmt)
  if @vtable.has_key?(stmt.identifier)
    raise RuntimeError, "Variable '#{stmt.identifier}' is already declared"
  end
  @vtable[stmt.identifier] = { type: :noob, value: nil }
  unless stmt.initialization.nil?
    @vtable[stmt.identifier] = eval_expr(stmt.initialization)
  end
end

#eval_expr(expr) ⇒ Object



224
225
226
# File 'lib/layo/base_interpreter.rb', line 224

def eval_expr(expr)
  send("eval_#{expr.type}_expr", expr)
end

#eval_expression_stmt(stmt) ⇒ Object



75
76
77
# File 'lib/layo/base_interpreter.rb', line 75

def eval_expression_stmt(stmt)
  @vtable['IT'] = eval_expr(stmt.expression)
end

#eval_function_expr(expr) ⇒ Object



277
278
279
280
281
282
283
# File 'lib/layo/base_interpreter.rb', line 277

def eval_function_expr(expr)
  parameters = []
  expr.parameters.each do |param|
    parameters << eval_expr(param)
  end
  call_func(expr.name, parameters)
end

#eval_input_stmt(stmt) ⇒ Object



101
102
103
# File 'lib/layo/base_interpreter.rb', line 101

def eval_input_stmt(stmt)
  @vtable[stmt.identifier] = { type: :yarn, value: @input.gets }
end

#eval_loop_stmt(stmt) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/layo/base_interpreter.rb', line 105

def eval_loop_stmt(stmt)
  unless stmt.op.nil?
    # Backup any local variable if its name is the same as the counter
    # variable's name
    if @vtable.has_key?(stmt.counter)
      var_backup = @vtable[stmt.counter]
    end
    @vtable[stmt.counter] = { type: :numbr, value: 0 }
    update_op = if stmt.op == :uppin
      lambda { @vtable[stmt.counter][:value] += 1 }
    elsif stmt.op == :nerfin
      lambda { @vtable[stmt.counter][:value] -= 1 }
    else
      lambda {
        @vtable[stmt.counter] = call_func(stmt.op, [@vtable[stmt.counter]])
      }
    end
  end

  catch :break do
    while true
      unless stmt.guard.nil?
        condition_met = cast(eval_expr(stmt.guard[:expression]), :troof)
        if (stmt.guard[:type] == :wile && !condition_met) or
           (stmt.guard[:type] == :til && condition_met)
           throw :break
        end
      end
      eval_block(stmt.block)
      update_op.call if update_op
    end
  end
  # Restore backed up variable
  unless stmt.op.nil? || var_backup.nil?
    @vtable[stmt.counter] = var_backup
  end
end

#eval_nary_expr(expr) ⇒ Object



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/layo/base_interpreter.rb', line 307

def eval_nary_expr(expr)
  case expr.operator
    when :all_of
      type, value = :troof, true
      expr.expressions.each do |operand|
        unless cast(eval_expr(operand), :troof)
          value = false
          break
        end
      end
    when :any_of
      type, value = :troof, false
      expr.expressions.each do |operand|
        if cast(eval_expr(operand), :troof)
          value = true
          break
        end
      end
    when :smoosh
      type, value = :yarn, ''
      expr.expressions.each do |operand|
        value << cast(eval_expr(operand), :yarn)
      end
  end
  { type: type, value: value }
end

#eval_print_stmt(stmt) ⇒ Object



143
144
145
146
147
148
149
150
151
# File 'lib/layo/base_interpreter.rb', line 143

def eval_print_stmt(stmt)
  text = ''
  # todo rewrite using map or similar
  stmt.expressions.each do |expr|
    text << cast(eval_expr(expr), :yarn)
  end
  text << "\n" unless stmt.suppress
  @output.print(text)
end

#eval_return_stmt(stmt) ⇒ Object



153
154
155
# File 'lib/layo/base_interpreter.rb', line 153

def eval_return_stmt(stmt)
  throw :return, eval_expr(stmt.expression)
end

#eval_switch_stmt(stmt) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/layo/base_interpreter.rb', line 157

def eval_switch_stmt(stmt)
  stmt.cases.combination(2) do |c|
    raise RuntimeError, 'Literals must be unique' if c[0] == c[1]
  end
  case_found = false
  it = @vtable['IT']
  stmt.cases.each do |kase|
    unless case_found
      literal = eval_expr(kase[:expression])
      if it == literal
        case_found = true
      end
    end
    if case_found
      breaked = true
      catch :break do
        eval_block(kase[:block])
        breaked = false
      end
      break if breaked
    end
  end
  unless case_found || stmt.default.nil?
    catch :break do
      eval_block(stmt.default)
    end
  end
end

#eval_unary_expr(expr) ⇒ Object



334
335
336
337
# File 'lib/layo/base_interpreter.rb', line 334

def eval_unary_expr(expr)
  # the only unary op in LOLCODE is NOT
  { type: :troof, value: !cast(eval_expr(expr.expression), :troof) }
end

#eval_variable_expr(expr) ⇒ Object



339
340
341
# File 'lib/layo/base_interpreter.rb', line 339

def eval_variable_expr(expr)
  @vtable[expr.name]
end

#init_tablesObject

Initializes empty function and variable tables



18
19
20
21
# File 'lib/layo/base_interpreter.rb', line 18

def init_tables
  @functions = {}
  @vtable = create_variable_table
end

#interpolate_string(str) ⇒ Object

Interpolates values of variables in the string



344
345
346
# File 'lib/layo/base_interpreter.rb', line 344

def interpolate_string(str)
  str.gsub(/:\{([a-zA-Z]\w*)\}/) { cast(@vtable[$1], :yarn, false) }
end

#with_guard(&block) ⇒ Object

Runs piece of code inside guard to catch :break and :return thrown by illegal statements. Also assigns line numbers to RuntimeError’s



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/layo/base_interpreter.rb', line 25

def with_guard(&block)
  begin
    illegal = true
    catch(:break) do
      catch(:return) do
        block.call
        illegal = false
      end
      raise RuntimeError, "Illegal return statement" if illegal
    end
    raise RuntimeError, "Illegal break statement" if illegal
  rescue RuntimeError => e
    e.line = @stmt_line
    raise e
  end
end