Class: Loxxy::BackEnd::Engine
- Inherits:
-
Object
- Object
- Loxxy::BackEnd::Engine
- 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
- #binary_operators ⇒ Hash { Symbol => BinaryOperator} readonly
-
#config ⇒ Hash
readonly
A set of configuration options.
- #resolver ⇒ BackEnd::Resolver readonly
-
#stack ⇒ Array<Datatype::BuiltinDatatype>
readonly
Data stack for the expression results.
- #symbol_table ⇒ BackEnd::SymbolTable readonly
- #unary_operators ⇒ Hash { Symbol => UnaryOperator} readonly
Instance Method Summary collapse
- #after_assign_expr(anAssignExpr, _visitor) ⇒ Object
- #after_binary_expr(aBinaryExpr) ⇒ Object
- #after_block_stmt(_aBlockStmt) ⇒ Object
- #after_call_expr(aCallExpr, aVisitor) ⇒ Object
- #after_class_stmt(aClassStmt, aVisitor) ⇒ Object
- #after_for_stmt(aForStmt, aVisitor) ⇒ Object
- #after_fun_stmt(aFunStmt, _visitor) ⇒ Object
- #after_get_expr(aGetExpr, aVisitor) ⇒ Object
- #after_grouping_expr(_groupingExpr) ⇒ Object
- #after_if_stmt(anIfStmt, aVisitor) ⇒ Object
- #after_logical_expr(aLogicalExpr, visitor) ⇒ Object
- #after_print_stmt(_printStmt) ⇒ Object
- #after_return_stmt(_returnStmt, _aVisitor) ⇒ Object
-
#after_seq_decl(aSeqDecls) ⇒ Object
Visit event handling.
- #after_set_expr(aSetExpr, aVisitor) ⇒ Object
- #after_super_expr(aSuperExpr, aVisitor) ⇒ Object
- #after_this_expr(aThisExpr, aVisitor) ⇒ Object
- #after_unary_expr(anUnaryExpr) ⇒ Object
- #after_var_stmt(aVarStmt) ⇒ Object
- #after_variable_expr(aVarExpr, aVisitor) ⇒ Object
- #after_while_stmt(aWhileStmt, aVisitor) ⇒ Object
- #before_block_stmt(_aBlockStmt) ⇒ Object
- #before_for_stmt(aForStmt) ⇒ Object
- #before_literal_expr(literalExpr) ⇒ Object
- #before_visit_builtin(aValue) ⇒ Object
-
#execute(aVisitor) ⇒ Loxxy::Datatype::BuiltinDatatype
Given an abstract syntax parse tree visitor, launch the visit and execute the visit events in the output stream.
-
#initialize(theOptions) ⇒ Engine
constructor
A new instance of Engine.
- #logical_2nd_arg(operand2) ⇒ Object
Constructor Details
#initialize(theOptions) ⇒ Engine
Returns a new instance of Engine.
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_operators ⇒ Hash { Symbol => BinaryOperator} (readonly)
30 31 32 |
# File 'lib/loxxy/back_end/engine.rb', line 30 def binary_operators @binary_operators end |
#config ⇒ Hash (readonly)
Returns A set of configuration options.
18 19 20 |
# File 'lib/loxxy/back_end/engine.rb', line 18 def config @config end |
#resolver ⇒ BackEnd::Resolver (readonly)
33 34 35 |
# File 'lib/loxxy/back_end/engine.rb', line 33 def resolver @resolver end |
#stack ⇒ Array<Datatype::BuiltinDatatype> (readonly)
Returns Data stack for the expression results.
24 25 26 |
# File 'lib/loxxy/back_end/engine.rb', line 24 def stack @stack end |
#symbol_table ⇒ BackEnd::SymbolTable (readonly)
21 22 23 |
# File 'lib/loxxy/back_end/engine.rb', line 21 def symbol_table @symbol_table end |
#unary_operators ⇒ Hash { Symbol => UnaryOperator} (readonly)
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
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
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
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
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.
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 |