loxxy 0.2.01 → 0.2.06

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -0
  3. data/README.md +25 -11
  4. data/bin/loxxy +9 -5
  5. data/lib/loxxy/ast/all_lox_nodes.rb +0 -1
  6. data/lib/loxxy/ast/ast_builder.rb +27 -4
  7. data/lib/loxxy/ast/ast_visitee.rb +53 -0
  8. data/lib/loxxy/ast/ast_visitor.rb +2 -10
  9. data/lib/loxxy/ast/lox_assign_expr.rb +1 -5
  10. data/lib/loxxy/ast/lox_binary_expr.rb +1 -6
  11. data/lib/loxxy/ast/lox_block_stmt.rb +1 -5
  12. data/lib/loxxy/ast/lox_call_expr.rb +1 -5
  13. data/lib/loxxy/ast/lox_class_stmt.rb +1 -5
  14. data/lib/loxxy/ast/lox_fun_stmt.rb +1 -5
  15. data/lib/loxxy/ast/lox_get_expr.rb +1 -6
  16. data/lib/loxxy/ast/lox_grouping_expr.rb +1 -5
  17. data/lib/loxxy/ast/lox_if_stmt.rb +2 -6
  18. data/lib/loxxy/ast/lox_literal_expr.rb +1 -5
  19. data/lib/loxxy/ast/lox_logical_expr.rb +1 -6
  20. data/lib/loxxy/ast/lox_node.rb +9 -1
  21. data/lib/loxxy/ast/lox_print_stmt.rb +1 -5
  22. data/lib/loxxy/ast/lox_return_stmt.rb +1 -5
  23. data/lib/loxxy/ast/lox_seq_decl.rb +1 -5
  24. data/lib/loxxy/ast/lox_set_expr.rb +1 -5
  25. data/lib/loxxy/ast/lox_super_expr.rb +2 -6
  26. data/lib/loxxy/ast/lox_this_expr.rb +1 -5
  27. data/lib/loxxy/ast/lox_unary_expr.rb +1 -6
  28. data/lib/loxxy/ast/lox_var_stmt.rb +1 -5
  29. data/lib/loxxy/ast/lox_variable_expr.rb +1 -5
  30. data/lib/loxxy/ast/lox_while_stmt.rb +2 -6
  31. data/lib/loxxy/back_end/engine.rb +28 -26
  32. data/lib/loxxy/back_end/lox_instance.rb +1 -1
  33. data/lib/loxxy/back_end/resolver.rb +16 -22
  34. data/lib/loxxy/datatype/number.rb +19 -4
  35. data/lib/loxxy/error.rb +3 -0
  36. data/lib/loxxy/front_end/parser.rb +1 -1
  37. data/lib/loxxy/front_end/scanner.rb +43 -17
  38. data/lib/loxxy/version.rb +1 -1
  39. data/spec/front_end/scanner_spec.rb +69 -7
  40. data/spec/interpreter_spec.rb +36 -0
  41. metadata +3 -3
  42. data/lib/loxxy/ast/lox_for_stmt.rb +0 -41
@@ -15,11 +15,7 @@ module Loxxy
15
15
  @literal = aLiteral
16
16
  end
17
17
 
18
- # Part of the 'visitee' role in Visitor design pattern.
19
- # @param visitor [ASTVisitor] the visitor
20
- def accept(visitor)
21
- visitor.visit_literal_expr(self)
22
- end
18
+ define_accept # Add `accept` method as found in Visitor design pattern
23
19
  end # class
24
20
  end # module
25
21
  end # module
@@ -16,12 +16,7 @@ module Loxxy
16
16
  @operator = anOperator
17
17
  end
18
18
 
19
- # Part of the 'visitee' role in Visitor design pattern.
20
- # @param visitor [Ast::ASTVisitor] the visitor
21
- def accept(visitor)
22
- visitor.visit_logical_expr(self)
23
- end
24
-
19
+ define_accept # Add `accept` method as found in Visitor design pattern
25
20
  alias operands subnodes
26
21
  end # class
27
22
  end # module
@@ -1,8 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'ast_visitee'
4
+
3
5
  module Loxxy
4
6
  module Ast
7
+ # Abstract class.
8
+ # Instances of its subclasses represent nodes of an abstract syntax tree
9
+ # that is the product of the parse of an input text.
5
10
  class LoxNode
11
+ # Let nodes take `visitee` role as defined in the Visitor design pattern
12
+ extend ASTVisitee
13
+
6
14
  # return [Rley::Lexical::Position] Position of the entry in the input stream.
7
15
  attr_reader :position
8
16
 
@@ -16,7 +24,7 @@ module Loxxy
16
24
  # Default: do nothing ...
17
25
  end
18
26
 
19
- # Abstract method.
27
+ # Abstract method (must be overriden in subclasses).
20
28
  # Part of the 'visitee' role in Visitor design pattern.
21
29
  # @param _visitor [LoxxyTreeVisitor] the visitor
22
30
  def accept(_visitor)
@@ -11,11 +11,7 @@ module Loxxy
11
11
  super(aPosition, [anExpression])
12
12
  end
13
13
 
14
- # Part of the 'visitee' role in Visitor design pattern.
15
- # @param visitor [Ast::ASTVisitor] the visitor
16
- def accept(visitor)
17
- visitor.visit_print_stmt(self)
18
- end
14
+ define_accept # Add `accept` method as found in Visitor design pattern
19
15
  end # class
20
16
  end # module
21
17
  end # module
@@ -12,11 +12,7 @@ module Loxxy
12
12
  super(aPosition, [expr])
13
13
  end
14
14
 
15
- # Part of the 'visitee' role in Visitor design pattern.
16
- # @param visitor [Ast::ASTVisitor] the visitor
17
- def accept(visitor)
18
- visitor.visit_return_stmt(self)
19
- end
15
+ define_accept # Add `accept` method as found in Visitor design pattern
20
16
  end # class
21
17
  end # module
22
18
  end # module
@@ -5,11 +5,7 @@ require_relative 'lox_compound_expr'
5
5
  module Loxxy
6
6
  module Ast
7
7
  class LoxSeqDecl < LoxCompoundExpr
8
- # Part of the 'visitee' role in Visitor design pattern.
9
- # @param visitor [Ast::ASTVisitor] the visitor
10
- def accept(visitor)
11
- visitor.visit_seq_decl(self)
12
- end
8
+ define_accept # Add `accept` method as found in Visitor design pattern
13
9
  end # class
14
10
  end # module
15
11
  end # module
@@ -18,11 +18,7 @@ module Loxxy
18
18
  @object = anObject
19
19
  end
20
20
 
21
- # Part of the 'visitee' role in Visitor design pattern.
22
- # @param visitor [ASTVisitor] the visitor
23
- def accept(visitor)
24
- visitor.visit_set_expr(self)
25
- end
21
+ define_accept # Add `accept` method as found in Visitor design pattern
26
22
  end # class
27
23
  end # module
28
24
  end # module
@@ -18,17 +18,13 @@ module Loxxy
18
18
  @property = aMethodName
19
19
  end
20
20
 
21
- # Part of the 'visitee' role in Visitor design pattern.
22
- # @param visitor [ASTVisitor] the visitor
23
- def accept(visitor)
24
- visitor.visit_super_expr(self)
25
- end
26
-
27
21
  # Quack like a LoxVariableExpr
28
22
  # @return [String] the `super` keyword
29
23
  def name
30
24
  'super'
31
25
  end
26
+
27
+ define_accept # Add `accept` method as found in Visitor design pattern
32
28
  alias callee= object=
33
29
  end # class
34
30
  end # module
@@ -12,11 +12,7 @@ module Loxxy
12
12
  'this'
13
13
  end
14
14
 
15
- # Part of the 'visitee' role in Visitor design pattern.
16
- # @param _visitor [LoxxyTreeVisitor] the visitor
17
- def accept(aVisitor)
18
- aVisitor.visit_this_expr(self)
19
- end
15
+ define_accept # Add `accept` method as found in Visitor design pattern
20
16
  end # class
21
17
  end # module
22
18
  end # module
@@ -15,12 +15,7 @@ module Loxxy
15
15
  @operator = anOperator
16
16
  end
17
17
 
18
- # Part of the 'visitee' role in Visitor design pattern.
19
- # @param visitor [Ast::ASTVisitor] the visitor
20
- def accept(visitor)
21
- visitor.visit_unary_expr(self)
22
- end
23
-
18
+ define_accept # Add `accept` method as found in Visitor design pattern
24
19
  alias operands subnodes
25
20
  end # class
26
21
  end # module
@@ -18,11 +18,7 @@ module Loxxy
18
18
  @name = aName
19
19
  end
20
20
 
21
- # Part of the 'visitee' role in Visitor design pattern.
22
- # @param visitor [Ast::ASTVisitor] the visitor
23
- def accept(visitor)
24
- visitor.visit_var_stmt(self)
25
- end
21
+ define_accept # Add `accept` method as found in Visitor design pattern
26
22
  end # class
27
23
  end # module
28
24
  end # module
@@ -16,11 +16,7 @@ module Loxxy
16
16
  @name = aName
17
17
  end
18
18
 
19
- # Part of the 'visitee' role in Visitor design pattern.
20
- # @param visitor [Ast::ASTVisitor] the visitor
21
- def accept(visitor)
22
- visitor.visit_variable_expr(self)
23
- end
19
+ define_accept # Add `accept` method as found in Visitor design pattern
24
20
  end # class
25
21
  end # module
26
22
  end # module
@@ -16,17 +16,13 @@ module Loxxy
16
16
  @body = theBody
17
17
  end
18
18
 
19
- # Part of the 'visitee' role in Visitor design pattern.
20
- # @param visitor [Ast::ASTVisitor] the visitor
21
- def accept(visitor)
22
- visitor.visit_while_stmt(self)
23
- end
24
-
25
19
  # Accessor to the condition expression
26
20
  # @return [LoxNode]
27
21
  def condition
28
22
  subnodes[0]
29
23
  end
24
+
25
+ define_accept # Add `accept` method as found in Visitor design pattern
30
26
  end # class
31
27
  end # module
32
28
  end # module
@@ -74,7 +74,7 @@ module Loxxy
74
74
  aClassStmt.superclass.accept(aVisitor)
75
75
  parent = stack.pop
76
76
  unless parent.kind_of?(LoxClass)
77
- raise StandardError, 'Superclass must be a class.'
77
+ raise Loxxy::RuntimeError, 'Superclass must be a class.'
78
78
  end
79
79
  else
80
80
  parent = nil
@@ -116,19 +116,6 @@ module Loxxy
116
116
  before_block_stmt(aForStmt)
117
117
  end
118
118
 
119
- def after_for_stmt(aForStmt, aVisitor)
120
- loop do
121
- aForStmt.test_expr.accept(aVisitor)
122
- condition = stack.pop
123
- break unless condition.truthy?
124
-
125
- aForStmt.body_stmt.accept(aVisitor)
126
- aForStmt.update_expr&.accept(aVisitor)
127
- stack.pop
128
- end
129
- after_block_stmt(aForStmt)
130
- end
131
-
132
119
  def after_if_stmt(anIfStmt, aVisitor)
133
120
  # Retrieve the result of the condition evaluation
134
121
  condition = stack.pop
@@ -154,7 +141,7 @@ module Loxxy
154
141
  break unless condition.truthy?
155
142
 
156
143
  aWhileStmt.body.accept(aVisitor)
157
- aWhileStmt.condition.accept(aVisitor)
144
+ aWhileStmt.condition&.accept(aVisitor)
158
145
  end
159
146
  end
160
147
 
@@ -170,22 +157,26 @@ module Loxxy
170
157
  def after_assign_expr(anAssignExpr, _visitor)
171
158
  var_name = anAssignExpr.name
172
159
  variable = variable_lookup(anAssignExpr)
173
- raise StandardError, "Unknown variable #{var_name}" unless variable
160
+ raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless variable
174
161
 
175
162
  value = stack.last # ToS remains since an assignment produces a value
176
163
  variable.assign(value)
177
164
  end
178
165
 
179
- def after_set_expr(aSetExpr, aVisitor)
180
- value = stack.pop
181
- # Evaluate object part
166
+ def before_set_expr(aSetExpr, aVisitor)
167
+ # Evaluate receiver object part
182
168
  aSetExpr.object.accept(aVisitor)
169
+ end
170
+
171
+ def after_set_expr(aSetExpr, _visitor)
172
+ value = stack.pop
183
173
  assignee = stack.pop
184
174
  unless assignee.kind_of?(LoxInstance)
185
- raise StandardError, 'Only instances have fields.'
175
+ raise Loxxy::RuntimeError, 'Only instances have fields.'
186
176
  end
187
177
 
188
178
  assignee.set(aSetExpr.property, value)
179
+ stack.push value
189
180
  end
190
181
 
191
182
  def after_logical_expr(aLogicalExpr, visitor)
@@ -227,7 +218,8 @@ module Loxxy
227
218
  operator = binary_operators[op]
228
219
  operator.validate_operands(operand1, operand2)
229
220
  if operand1.respond_to?(op)
230
- stack.push operand1.send(op, operand2)
221
+ result = operand1.send(op, operand2)
222
+ stack.push convert2lox_datatype(result)
231
223
  else
232
224
  msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
233
225
  raise StandardError, msg1
@@ -240,7 +232,8 @@ module Loxxy
240
232
  operator = unary_operators[op]
241
233
  operator.validate_operand(operand)
242
234
  if operand.respond_to?(op)
243
- stack.push operand.send(op)
235
+ result = operand.send(op)
236
+ stack.push convert2lox_datatype(result)
244
237
  else
245
238
  msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
246
239
  raise StandardError, msg1
@@ -272,7 +265,7 @@ module Loxxy
272
265
  aGetExpr.object.accept(aVisitor)
273
266
  instance = stack.pop
274
267
  unless instance.kind_of?(LoxInstance)
275
- raise StandardError, 'Only instances have properties.'
268
+ raise Loxxy::RuntimeError, 'Only instances have properties.'
276
269
  end
277
270
 
278
271
  stack.push instance.get(aGetExpr.property)
@@ -285,7 +278,7 @@ module Loxxy
285
278
  def after_variable_expr(aVarExpr, aVisitor)
286
279
  var_name = aVarExpr.name
287
280
  var = variable_lookup(aVarExpr)
288
- raise StandardError, "Undefined variable '#{var_name}'." unless var
281
+ raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless var
289
282
 
290
283
  var.value.accept(aVisitor) # Evaluate variable value then push on stack
291
284
  end
@@ -308,7 +301,7 @@ module Loxxy
308
301
  superklass = variable_lookup(aSuperExpr).value.superclass
309
302
  method = superklass.find_method(aSuperExpr.property)
310
303
  unless method
311
- raise StandardError, "Undefined property '#{aSuperExpr.property}'."
304
+ raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
312
305
  end
313
306
 
314
307
  stack.push method.bind(instance)
@@ -363,7 +356,7 @@ module Loxxy
363
356
  unary_operators[:-@] = negate_op
364
357
 
365
358
  negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype,
366
- BackEnd::LoxFunction])
359
+ BackEnd::LoxInstance, BackEnd::LoxFunction, BackEnd::LoxClass])
367
360
  unary_operators[:!] = negation_op
368
361
  end
369
362
 
@@ -417,6 +410,15 @@ module Loxxy
417
410
  Datatype::Number.new(now)
418
411
  end
419
412
  end
413
+
414
+ def convert2lox_datatype(item)
415
+ case item
416
+ when TrueClass then Datatype::True.instance
417
+ when FalseClass then Datatype::False.instance
418
+ else
419
+ item
420
+ end
421
+ end
420
422
  end # class
421
423
  end # module
422
424
  end # module
@@ -38,7 +38,7 @@ module Loxxy
38
38
 
39
39
  method = klass.find_method(aName)
40
40
  unless method
41
- raise StandardError, "Undefined property '#{aName}'."
41
+ raise Loxxy::RuntimeError, "Undefined property '#{aName}'."
42
42
  end
43
43
 
44
44
  method.bind(self)
@@ -69,7 +69,7 @@ module Loxxy
69
69
  define(aClassStmt.name)
70
70
  if aClassStmt.superclass
71
71
  if aClassStmt.name == aClassStmt.superclass.name
72
- raise StandardError, "'A class can't inherit from itself."
72
+ raise Loxxy::RuntimeError, "'A class can't inherit from itself."
73
73
  end
74
74
 
75
75
  @current_class = :subclass
@@ -88,17 +88,6 @@ module Loxxy
88
88
  @current_class = previous_class
89
89
  end
90
90
 
91
- def before_for_stmt(aForStmt)
92
- before_block_stmt(aForStmt)
93
- end
94
-
95
- def after_for_stmt(aForStmt, aVisitor)
96
- aForStmt.test_expr.accept(aVisitor)
97
- aForStmt.body_stmt.accept(aVisitor)
98
- aForStmt.update_expr&.accept(aVisitor)
99
- after_block_stmt(aForStmt)
100
- end
101
-
102
91
  def after_if_stmt(anIfStmt, aVisitor)
103
92
  anIfStmt.then_stmt.accept(aVisitor)
104
93
  anIfStmt.else_stmt&.accept(aVisitor)
@@ -107,23 +96,23 @@ module Loxxy
107
96
  def before_return_stmt(returnStmt)
108
97
  if scopes.size < 2
109
98
  msg = "Error at 'return': Can't return from top-level code."
110
- raise StandardError, msg
99
+ raise Loxxy::RuntimeError, msg
111
100
  end
112
101
 
113
102
  if current_function == :none
114
103
  msg = "Error at 'return': Can't return from outside a function."
115
- raise StandardError, msg
104
+ raise Loxxy::RuntimeError, msg
116
105
  end
117
106
 
118
107
  if current_function == :initializer
119
108
  msg = "Error at 'return': Can't return a value from an initializer."
120
- raise StandardError, msg unless returnStmt.subnodes[0].kind_of?(Datatype::Nil)
109
+ raise Loxxy::RuntimeError, msg unless returnStmt.subnodes[0].kind_of?(Datatype::Nil)
121
110
  end
122
111
  end
123
112
 
124
113
  def after_while_stmt(aWhileStmt, aVisitor)
125
114
  aWhileStmt.body.accept(aVisitor)
126
- aWhileStmt.condition.accept(aVisitor)
115
+ aWhileStmt.condition&.accept(aVisitor)
127
116
  end
128
117
 
129
118
  # A variable declaration adds a new variable to current scope
@@ -171,7 +160,7 @@ module Loxxy
171
160
  def before_this_expr(_thisExpr)
172
161
  if current_class == :none
173
162
  msg = "Error at 'this': Can't use 'this' outside of a class."
174
- raise StandardError, msg
163
+ raise Loxxy::RuntimeError, msg
175
164
  end
176
165
  end
177
166
 
@@ -186,11 +175,11 @@ module Loxxy
186
175
  msg_prefix = "Error at 'super': Can't use 'super' "
187
176
  if current_class == :none
188
177
  err_msg = msg_prefix + 'outside of a class.'
189
- raise StandardError, err_msg
178
+ raise Loxxy::RuntimeError, err_msg
190
179
 
191
180
  elsif current_class == :class
192
181
  err_msg = msg_prefix + 'in a class without superclass.'
193
- raise StandardError, err_msg
182
+ raise Loxxy::RuntimeError, err_msg
194
183
 
195
184
  end
196
185
  # 'super' behaves closely to a local variable
@@ -216,14 +205,19 @@ module Loxxy
216
205
  scopes.pop
217
206
  end
218
207
 
208
+ # rubocop: disable Style/SoleNestedConditional
219
209
  def declare(aVarName)
220
210
  return if scopes.empty?
221
211
 
222
212
  curr_scope = scopes.last
223
- if curr_scope.include?(aVarName)
224
- msg = "Error at '#{aVarName}': Already variable with this name in this scope."
225
- raise StandardError, msg
213
+ if scopes.size > 1 # Not at top-level?
214
+ # Oddly enough, Lox allows variable re-declaration at top-level
215
+ if curr_scope.include?(aVarName)
216
+ msg = "Error at '#{aVarName}': Already variable with this name in this scope."
217
+ raise Loxxy::RuntimeError, msg
218
+ end
226
219
  end
220
+ # rubocop: enable Style/SoleNestedConditional
227
221
 
228
222
  # The initializer is not yet processed.
229
223
  # Mark the variable as 'not yet ready' = exists but may not be referenced yet