Class: Loxxy::BackEnd::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/loxxy/back_end/engine.rb

Overview

An instance of this class executes the statements as when they occur during the abstract syntax tree walking.

Defined Under Namespace

Classes: NativeFunction

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(theOptions) ⇒ Engine

Returns a new instance of Engine.

Parameters:

  • theOptions (Hash)


36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/loxxy/back_end/engine.rb', line 36

def initialize(theOptions)
  @config = theOptions
  @istream = config.include?(:istream) ? config[:istream] : $stdin
  @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
  @symbol_table = SymbolTable.new
  @stack = []

  reset_expr_stack
  init_unary_operators
  init_binary_operators
  init_globals
end

Instance Attribute Details

#binary_operatorsHash { Symbol => BinaryOperator} (readonly)

Returns:



30
31
32
# File 'lib/loxxy/back_end/engine.rb', line 30

def binary_operators
  @binary_operators
end

#configHash (readonly)

Returns A set of configuration options.

Returns:

  • (Hash)

    A set of configuration options



18
19
20
# File 'lib/loxxy/back_end/engine.rb', line 18

def config
  @config
end

#resolverBackEnd::Resolver (readonly)

Returns:



33
34
35
# File 'lib/loxxy/back_end/engine.rb', line 33

def resolver
  @resolver
end

#stackArray<Datatype::BuiltinDatatype> (readonly)

Returns Data stack for arguments and return results.

Returns:



24
25
26
# File 'lib/loxxy/back_end/engine.rb', line 24

def stack
  @stack
end

#symbol_tableBackEnd::SymbolTable (readonly)



21
22
23
# File 'lib/loxxy/back_end/engine.rb', line 21

def symbol_table
  @symbol_table
end

#unary_operatorsHash { Symbol => UnaryOperator} (readonly)

Returns:



27
28
29
# File 'lib/loxxy/back_end/engine.rb', line 27

def unary_operators
  @unary_operators
end

Instance Method Details

#after_assign_expr(anAssignExpr, _visitor) ⇒ Object



198
199
200
201
202
203
204
205
# File 'lib/loxxy/back_end/engine.rb', line 198

def after_assign_expr(anAssignExpr, _visitor)
  var_name = anAssignExpr.name
  variable = variable_lookup(anAssignExpr)
  raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless variable

  value = expr_stack.last # ToS remains since an assignment produces a value
  variable.assign(value)
end

#after_binary_expr(aBinaryExpr) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/loxxy/back_end/engine.rb', line 257

def after_binary_expr(aBinaryExpr)
  operand2 = expr_stack.pop
  operand1 = expr_stack.pop
  op = aBinaryExpr.operator
  operator = binary_operators[op]
  operator.validate_operands(operand1, operand2)
  if operand1.respond_to?(op)
    result = operand1.send(op, operand2)
    expr_stack.push convert2lox_datatype(result)
  else
    msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
    raise StandardError, msg1
  end
end

#after_block_stmt(_aBlockStmt) ⇒ Object



194
195
196
# File 'lib/loxxy/back_end/engine.rb', line 194

def after_block_stmt(_aBlockStmt)
  symbol_table.leave_environment
end

#after_call_expr(aCallExpr, aVisitor) ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/loxxy/back_end/engine.rb', line 286

def after_call_expr(aCallExpr, aVisitor)
  # Evaluate callee part
  aCallExpr.callee.accept(aVisitor)
  callee = expr_stack.pop
  before_size = expr_stack.size
  aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
  after_size = expr_stack.size
  if after_size > before_size
    stack.concat(expr_stack.pop(after_size - before_size))
  end

  case callee
  when NativeFunction
    expr_stack.push callee.call # Pass arguments
  when LoxFunction, LoxClass
    arg_count = aCallExpr.arguments.size
    if arg_count != callee.arity
      msg = "Expected #{callee.arity} arguments but got #{arg_count}."
      raise Loxxy::RuntimeError, msg
    end
    callee.call(self, aVisitor)
  else
    raise Loxxy::RuntimeError, 'Can only call functions and classes.'
  end
end

#after_class_stmt(aClassStmt, aVisitor) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/loxxy/back_end/engine.rb', line 90

def after_class_stmt(aClassStmt, aVisitor)
  if aClassStmt.superclass
    aClassStmt.superclass.accept(aVisitor)
    parent = expr_stack.pop
    unless parent.kind_of?(LoxClass)
      raise Loxxy::RuntimeError, 'Superclass must be a class.'
    end
  else
    parent = nil
  end

  if parent # Create an environment specific for 'super'
    super_env = Environment.new(symbol_table.current_env)
    symbol_table.enter_environment(super_env)
  end

  # Convert LoxFunStmt into LoxFunction
  meths = aClassStmt.body.map do |func_node|
    func_node.is_method = true
    func_node.accept(aVisitor)
    mth = expr_stack.pop
    mth.is_initializer = true if mth.name == 'init'
    mth
  end

  klass = LoxClass.new(aClassStmt.name, parent, meths, self)
  if parent
    super_var = Variable.new('super', klass)
    symbol_table.insert(super_var)
    symbol_table.leave_environment
  end
  new_var = Variable.new(aClassStmt.name, klass)
  symbol_table.insert(new_var)
end

#after_fun_stmt(aFunStmt, _visitor) ⇒ Object



370
371
372
373
374
375
376
377
378
# File 'lib/loxxy/back_end/engine.rb', line 370

def after_fun_stmt(aFunStmt, _visitor)
  function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
  if aFunStmt.is_method
    expr_stack.push function
  else
    new_var = Variable.new(aFunStmt.name, function)
    symbol_table.insert(new_var)
  end
end

#after_get_expr(aGetExpr, aVisitor) ⇒ Object



312
313
314
315
316
317
318
319
320
# File 'lib/loxxy/back_end/engine.rb', line 312

def after_get_expr(aGetExpr, aVisitor)
  aGetExpr.object.accept(aVisitor)
  instance = expr_stack.pop
  unless instance.kind_of?(LoxInstance)
    raise Loxxy::RuntimeError, 'Only instances have properties.'
  end

  expr_stack.push instance.get(aGetExpr.property)
end

#after_grouping_expr(_groupingExpr) ⇒ Object



322
323
324
# File 'lib/loxxy/back_end/engine.rb', line 322

def after_grouping_expr(_groupingExpr)
  # Do nothing: work was already done by visiting /evaluating the subexpression
end

#after_if_stmt(anIfStmt, aVisitor) ⇒ Object



145
146
147
148
149
150
151
152
153
154
# File 'lib/loxxy/back_end/engine.rb', line 145

def after_if_stmt(anIfStmt, aVisitor)
  # Retrieve the result of the condition evaluation
  # condition = stack.pop
  condition = expr_stack.pop
  if condition.truthy?
    anIfStmt.then_stmt.accept(aVisitor)
  elsif anIfStmt.else_stmt
    anIfStmt.else_stmt.accept(aVisitor)
  end
end

#after_logical_expr(aLogicalExpr, visitor) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/loxxy/back_end/engine.rb', line 226

def after_logical_expr(aLogicalExpr, visitor)
  op = aLogicalExpr.operator
  operand1 = expr_stack.pop # only first operand was evaluated
  result = nil
  if ((op == :and) && operand1.falsey?) || ((op == :or) && operand1.truthy?)
    result = operand1
  else
    raw_operand2 = aLogicalExpr.subnodes[1]
    raw_operand2.accept(visitor) # Visit means operand2 is evaluated
    operand2 = expr_stack.pop
    result = logical_2nd_arg(operand2)
  end
  expr_stack.push result
end

#after_print_stmt(_printStmt) ⇒ Object



160
161
162
163
# File 'lib/loxxy/back_end/engine.rb', line 160

def after_print_stmt(_printStmt)
  tos = expr_stack.pop
  @ostream.print tos ? tos.to_str : 'nil'
end

#after_return_stmt(_returnStmt, _aVisitor) ⇒ Object



169
170
171
172
# File 'lib/loxxy/back_end/engine.rb', line 169

def after_return_stmt(_returnStmt, _aVisitor)
  stack.push(expr_stack.pop)
  throw(:return)
end

#after_seq_decl(aSeqDecls) ⇒ Object

Visit event handling



82
83
84
# File 'lib/loxxy/back_end/engine.rb', line 82

def after_seq_decl(aSeqDecls)
  # Do nothing, subnodes were already evaluated
end

#after_set_expr(aSetExpr, aVisitor) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/loxxy/back_end/engine.rb', line 211

def after_set_expr(aSetExpr, aVisitor)
  # Evaluate receiver object part (i.e. 'this')
  aSetExpr.object.accept(aVisitor)
  assignee = expr_stack.pop
  unless assignee.kind_of?(LoxInstance)
    raise Loxxy::RuntimeError, 'Only instances have fields.'
  end

  aSetExpr.value.accept(aVisitor)
  value = expr_stack.pop

  assignee.set(aSetExpr.property, value)
  expr_stack.push value
end

#after_super_expr(aSuperExpr, aVisitor) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/loxxy/back_end/engine.rb', line 347

def after_super_expr(aSuperExpr, aVisitor)
  offset = resolver.locals[aSuperExpr]
  env = symbol_table.current_env
  (offset - 1).times { env = env.enclosing }
  instance = env.defns['this'].value.accept(aVisitor)[0]
  superklass = variable_lookup(aSuperExpr).value.superclass
  method = superklass.find_method(aSuperExpr.property)
  unless method
    raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
  end

  expr_stack.push method.bind(instance)
end

#after_this_expr(aThisExpr, aVisitor) ⇒ Object



342
343
344
345
# File 'lib/loxxy/back_end/engine.rb', line 342

def after_this_expr(aThisExpr, aVisitor)
  var = variable_lookup(aThisExpr)
  var.value.accept(aVisitor) # Evaluate this value then push on stack
end

#after_unary_expr(anUnaryExpr) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/loxxy/back_end/engine.rb', line 272

def after_unary_expr(anUnaryExpr)
  operand = expr_stack.pop
  op = anUnaryExpr.operator
  operator = unary_operators[op]
  operator.validate_operand(operand)
  if operand.respond_to?(op)
    result = operand.send(op)
    expr_stack.push convert2lox_datatype(result)
  else
    msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
    raise StandardError, msg1
  end
end

#after_var_stmt(aVarStmt) ⇒ Object



129
130
131
132
133
134
135
# File 'lib/loxxy/back_end/engine.rb', line 129

def after_var_stmt(aVarStmt)
  new_var = Variable.new(aVarStmt.name, Datatype::Nil.instance)
  symbol_table.insert(new_var)

  value = expr_stack.pop
  new_var.assign(value)
end

#after_variable_expr(aVarExpr, aVisitor) ⇒ Object



326
327
328
329
330
331
332
333
334
335
# File 'lib/loxxy/back_end/engine.rb', line 326

def after_variable_expr(aVarExpr, aVisitor)
  var_name = aVarExpr.name
  var = variable_lookup(aVarExpr)
  unless var
    pos = "line #{aVarExpr.position.line}:#{aVarExpr.position.column}"
    raise Loxxy::RuntimeError, "[#{pos}] Undefined variable '#{var_name}'."
  end

  var.value.accept(aVisitor) # Evaluate variable value then push on stack
end

#after_while_stmt(aWhileStmt, aVisitor) ⇒ Object



178
179
180
181
182
183
184
185
186
# File 'lib/loxxy/back_end/engine.rb', line 178

def after_while_stmt(aWhileStmt, aVisitor)
  loop do
    condition = expr_stack.pop
    break unless condition.truthy?

    aWhileStmt.body.accept(aVisitor)
    aWhileStmt.condition&.accept(aVisitor)
  end
end

#before_block_stmt(_aBlockStmt) ⇒ Object



188
189
190
191
192
# File 'lib/loxxy/back_end/engine.rb', line 188

def before_block_stmt(_aBlockStmt)
  reset_expr_stack
  new_env = Environment.new
  symbol_table.enter_environment(new_env)
end

#before_class_stmt(_class_stmt) ⇒ Object



86
87
88
# File 'lib/loxxy/back_end/engine.rb', line 86

def before_class_stmt(_class_stmt)
  reset_expr_stack
end

#before_for_stmt(aForStmt) ⇒ Object



137
138
139
# File 'lib/loxxy/back_end/engine.rb', line 137

def before_for_stmt(aForStmt)
  before_block_stmt(aForStmt)
end

#before_fun_stmt(_fun_stmt, _visitor) ⇒ Object



366
367
368
# File 'lib/loxxy/back_end/engine.rb', line 366

def before_fun_stmt(_fun_stmt, _visitor)
  reset_expr_stack
end

#before_if_stmt(_if_stmt) ⇒ Object



141
142
143
# File 'lib/loxxy/back_end/engine.rb', line 141

def before_if_stmt(_if_stmt)
  reset_expr_stack
end

#before_literal_expr(literalExpr) ⇒ Object

Parameters:



338
339
340
# File 'lib/loxxy/back_end/engine.rb', line 338

def before_literal_expr(literalExpr)
  expr_stack.push(literalExpr.literal)
end

#before_print_stmt(_print_stmt) ⇒ Object



156
157
158
# File 'lib/loxxy/back_end/engine.rb', line 156

def before_print_stmt(_print_stmt)
  reset_expr_stack
end

#before_return_stmt(_return_stmt) ⇒ Object



165
166
167
# File 'lib/loxxy/back_end/engine.rb', line 165

def before_return_stmt(_return_stmt)
  reset_expr_stack
end

#before_set_expr(_set_expr, _visitor) ⇒ Object



207
208
209
# File 'lib/loxxy/back_end/engine.rb', line 207

def before_set_expr(_set_expr, _visitor)
  reset_expr_stack
end

#before_var_stmt(_var_stmt) ⇒ Object



125
126
127
# File 'lib/loxxy/back_end/engine.rb', line 125

def before_var_stmt(_var_stmt)
  reset_expr_stack
end

#before_visit_builtin(aValue) ⇒ Object

Parameters:

  • aValue (Ast::BuiltinDattype)

    the built-in datatype value



362
363
364
# File 'lib/loxxy/back_end/engine.rb', line 362

def before_visit_builtin(aValue)
  expr_stack.push(aValue)
end

#before_while_stmt(_while_stmt) ⇒ Object



174
175
176
# File 'lib/loxxy/back_end/engine.rb', line 174

def before_while_stmt(_while_stmt)
  reset_expr_stack
end

#current_envLoxxy::BackEnd::Environment

Returns the current environment



51
52
53
# File 'lib/loxxy/back_end/engine.rb', line 51

def current_env
  symbol_table.current_env
end

#execute(aVisitor) ⇒ Loxxy::Datatype::BuiltinDatatype

Given an abstract syntax parse tree visitor, launch the visit and execute the visit events in the output stream.

Parameters:

  • aVisitor (AST::ASTVisitor)

Returns:



67
68
69
70
71
72
73
74
75
76
# File 'lib/loxxy/back_end/engine.rb', line 67

def execute(aVisitor)
  # Do variable resolution pass first
  @resolver = BackEnd::Resolver.new
  resolver.analyze(aVisitor)

  aVisitor.subscribe(self)
  aVisitor.start
  aVisitor.unsubscribe(self)
  expr_stack.empty? ? Datatype::Nil.instance : expr_stack.pop
end

#expr_stackObject



55
56
57
# File 'lib/loxxy/back_end/engine.rb', line 55

def expr_stack
  current_env.expr_stack
end

#logical_2nd_arg(operand2) ⇒ Object



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/loxxy/back_end/engine.rb', line 241

def logical_2nd_arg(operand2)
  case operand2
     when false
       False.instance # Convert to Lox equivalent
     when nil
       Nil.instance # Convert to Lox equivalent
     when true
       True.instance # Convert to Lox equivalent
     when Proc
       # Second operand wasn't yet evaluated...
       operand2.call
     else
       operand2
  end
end

#reset_expr_stackObject



59
60
61
# File 'lib/loxxy/back_end/engine.rb', line 59

def reset_expr_stack
  current_env.expr_stack.clear
end