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
  @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
  @symbol_table = SymbolTable.new
  @stack = []
  @unary_operators = {}
  @binary_operators = {}

  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 the expression 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

Raises:

  • (StandardError)


170
171
172
173
174
175
176
177
# File 'lib/loxxy/back_end/engine.rb', line 170

def after_assign_expr(anAssignExpr, _visitor)
  var_name = anAssignExpr.name
  variable = variable_lookup(anAssignExpr)
  raise StandardError, "Unknown variable #{var_name}" unless variable

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

#after_binary_expr(aBinaryExpr) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/loxxy/back_end/engine.rb', line 223

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

#after_block_stmt(_aBlockStmt) ⇒ Object



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

def after_block_stmt(_aBlockStmt)
  symbol_table.leave_environment
end

#after_call_expr(aCallExpr, aVisitor) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/loxxy/back_end/engine.rb', line 250

def after_call_expr(aCallExpr, aVisitor)
  # Evaluate callee part
  aCallExpr.callee.accept(aVisitor)
  callee = stack.pop
  aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }

  case callee
  when NativeFunction
    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



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/loxxy/back_end/engine.rb', line 72

def after_class_stmt(aClassStmt, aVisitor)
  if aClassStmt.superclass
    aClassStmt.superclass.accept(aVisitor)
    parent = stack.pop
    unless parent.kind_of?(LoxClass)
      raise StandardError, '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 = 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_for_stmt(aForStmt, aVisitor) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/loxxy/back_end/engine.rb', line 119

def after_for_stmt(aForStmt, aVisitor)
  loop do
    aForStmt.test_expr.accept(aVisitor)
    condition = stack.pop
    break unless condition.truthy?

    aForStmt.body_stmt.accept(aVisitor)
    aForStmt.update_expr&.accept(aVisitor)
    stack.pop
  end
  after_block_stmt(aForStmt)
end

#after_fun_stmt(aFunStmt, _visitor) ⇒ Object



322
323
324
325
326
327
328
329
330
# File 'lib/loxxy/back_end/engine.rb', line 322

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

#after_get_expr(aGetExpr, aVisitor) ⇒ Object



271
272
273
274
275
276
277
278
279
# File 'lib/loxxy/back_end/engine.rb', line 271

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

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

#after_grouping_expr(_groupingExpr) ⇒ Object



281
282
283
# File 'lib/loxxy/back_end/engine.rb', line 281

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

#after_if_stmt(anIfStmt, aVisitor) ⇒ Object



132
133
134
135
136
137
138
139
140
# File 'lib/loxxy/back_end/engine.rb', line 132

def after_if_stmt(anIfStmt, aVisitor)
  # Retrieve the result of the condition evaluation
  condition = 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



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/loxxy/back_end/engine.rb', line 191

def after_logical_expr(aLogicalExpr, visitor)
  op = aLogicalExpr.operator
  operand1 = 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 = stack.pop
    result = logical_2nd_arg(operand2)
  end

  stack.push result
end

#after_print_stmt(_printStmt) ⇒ Object



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

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

#after_return_stmt(_returnStmt, _aVisitor) ⇒ Object



147
148
149
# File 'lib/loxxy/back_end/engine.rb', line 147

def after_return_stmt(_returnStmt, _aVisitor)
  throw(:return)
end

#after_seq_decl(aSeqDecls) ⇒ Object

Visit event handling



68
69
70
# File 'lib/loxxy/back_end/engine.rb', line 68

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

#after_set_expr(aSetExpr, aVisitor) ⇒ Object



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

def after_set_expr(aSetExpr, aVisitor)
  value = stack.pop
  # Evaluate object part
  aSetExpr.object.accept(aVisitor)
  assignee = stack.pop
  unless assignee.kind_of?(LoxInstance)
    raise StandardError, 'Only instances have fields.'
  end

  assignee.set(aSetExpr.property, value)
end

#after_super_expr(aSuperExpr, aVisitor) ⇒ Object



303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/loxxy/back_end/engine.rb', line 303

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 StandardError, "Undefined property '#{aSuperExpr.property}'."
  end

  stack.push method.bind(instance)
end

#after_this_expr(aThisExpr, aVisitor) ⇒ Object



298
299
300
301
# File 'lib/loxxy/back_end/engine.rb', line 298

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



237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/loxxy/back_end/engine.rb', line 237

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

#after_var_stmt(aVarStmt) ⇒ Object



107
108
109
110
111
112
113
# File 'lib/loxxy/back_end/engine.rb', line 107

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

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

#after_variable_expr(aVarExpr, aVisitor) ⇒ Object

Raises:

  • (StandardError)


285
286
287
288
289
290
291
# File 'lib/loxxy/back_end/engine.rb', line 285

def after_variable_expr(aVarExpr, aVisitor)
  var_name = aVarExpr.name
  var = variable_lookup(aVarExpr)
  raise StandardError, "Undefined variable '#{var_name}'." unless var

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

#after_while_stmt(aWhileStmt, aVisitor) ⇒ Object



151
152
153
154
155
156
157
158
159
# File 'lib/loxxy/back_end/engine.rb', line 151

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

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

#before_block_stmt(_aBlockStmt) ⇒ Object



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

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

#before_for_stmt(aForStmt) ⇒ Object



115
116
117
# File 'lib/loxxy/back_end/engine.rb', line 115

def before_for_stmt(aForStmt)
  before_block_stmt(aForStmt)
end

#before_literal_expr(literalExpr) ⇒ Object

Parameters:



294
295
296
# File 'lib/loxxy/back_end/engine.rb', line 294

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

#before_visit_builtin(aValue) ⇒ Object

Parameters:

  • aValue (Ast::BuiltinDattype)

    the built-in datatype value



318
319
320
# File 'lib/loxxy/back_end/engine.rb', line 318

def before_visit_builtin(aValue)
  stack.push(aValue)
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:



53
54
55
56
57
58
59
60
61
62
# File 'lib/loxxy/back_end/engine.rb', line 53

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

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

#logical_2nd_arg(operand2) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/loxxy/back_end/engine.rb', line 207

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