loxxy 0.2.02 → 0.3.00
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -0
- data/README.md +19 -2
- data/lib/loxxy/ast/all_lox_nodes.rb +0 -1
- data/lib/loxxy/ast/ast_builder.rb +27 -4
- data/lib/loxxy/ast/ast_visitee.rb +53 -0
- data/lib/loxxy/ast/ast_visitor.rb +2 -10
- data/lib/loxxy/ast/lox_assign_expr.rb +1 -5
- data/lib/loxxy/ast/lox_binary_expr.rb +1 -6
- data/lib/loxxy/ast/lox_block_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_call_expr.rb +1 -5
- data/lib/loxxy/ast/lox_class_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_fun_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_get_expr.rb +1 -6
- data/lib/loxxy/ast/lox_grouping_expr.rb +1 -5
- data/lib/loxxy/ast/lox_if_stmt.rb +2 -6
- data/lib/loxxy/ast/lox_literal_expr.rb +1 -5
- data/lib/loxxy/ast/lox_logical_expr.rb +1 -6
- data/lib/loxxy/ast/lox_node.rb +9 -1
- data/lib/loxxy/ast/lox_print_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_return_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_seq_decl.rb +1 -5
- data/lib/loxxy/ast/lox_set_expr.rb +1 -5
- data/lib/loxxy/ast/lox_super_expr.rb +2 -6
- data/lib/loxxy/ast/lox_this_expr.rb +1 -5
- data/lib/loxxy/ast/lox_unary_expr.rb +1 -6
- data/lib/loxxy/ast/lox_var_stmt.rb +1 -5
- data/lib/loxxy/ast/lox_variable_expr.rb +1 -5
- data/lib/loxxy/ast/lox_while_stmt.rb +2 -6
- data/lib/loxxy/back_end/engine.rb +28 -26
- data/lib/loxxy/back_end/lox_instance.rb +1 -1
- data/lib/loxxy/back_end/resolver.rb +17 -21
- data/lib/loxxy/datatype/number.rb +19 -4
- data/lib/loxxy/front_end/parser.rb +1 -1
- data/lib/loxxy/front_end/scanner.rb +11 -10
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/environment_spec.rb +0 -14
- data/spec/back_end/symbol_table_spec.rb +0 -19
- data/spec/back_end/variable_spec.rb +0 -35
- data/spec/front_end/scanner_spec.rb +35 -7
- data/spec/interpreter_spec.rb +36 -0
- metadata +3 -3
- data/lib/loxxy/ast/lox_for_stmt.rb +0 -41
@@ -11,11 +11,7 @@ module Loxxy
|
|
11
11
|
super(aPosition, [anExpression])
|
12
12
|
end
|
13
13
|
|
14
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
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
|
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
|
180
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
@@ -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
|
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,27 +96,30 @@ 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
|
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
|
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
|
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
|
115
|
+
aWhileStmt.condition&.accept(aVisitor)
|
127
116
|
end
|
128
117
|
|
129
118
|
# A variable declaration adds a new variable to current scope
|
130
119
|
def before_var_stmt(aVarStmt)
|
120
|
+
# Oddly enough, Lox allows the re-definition of a variable at top-level scope
|
121
|
+
return if scopes.size == 1 && scopes.last[aVarStmt.name]
|
122
|
+
|
131
123
|
declare(aVarStmt.name)
|
132
124
|
end
|
133
125
|
|
@@ -149,7 +141,7 @@ module Loxxy
|
|
149
141
|
def before_variable_expr(aVarExpr)
|
150
142
|
var_name = aVarExpr.name
|
151
143
|
if !scopes.empty? && (scopes.last[var_name] == false)
|
152
|
-
raise
|
144
|
+
raise Loxxy::RuntimeError, "Can't read variable #{var_name} in its own initializer"
|
153
145
|
end
|
154
146
|
end
|
155
147
|
|
@@ -171,7 +163,7 @@ module Loxxy
|
|
171
163
|
def before_this_expr(_thisExpr)
|
172
164
|
if current_class == :none
|
173
165
|
msg = "Error at 'this': Can't use 'this' outside of a class."
|
174
|
-
raise
|
166
|
+
raise Loxxy::RuntimeError, msg
|
175
167
|
end
|
176
168
|
end
|
177
169
|
|
@@ -186,11 +178,11 @@ module Loxxy
|
|
186
178
|
msg_prefix = "Error at 'super': Can't use 'super' "
|
187
179
|
if current_class == :none
|
188
180
|
err_msg = msg_prefix + 'outside of a class.'
|
189
|
-
raise
|
181
|
+
raise Loxxy::RuntimeError, err_msg
|
190
182
|
|
191
183
|
elsif current_class == :class
|
192
184
|
err_msg = msg_prefix + 'in a class without superclass.'
|
193
|
-
raise
|
185
|
+
raise Loxxy::RuntimeError, err_msg
|
194
186
|
|
195
187
|
end
|
196
188
|
# 'super' behaves closely to a local variable
|
@@ -220,9 +212,13 @@ module Loxxy
|
|
220
212
|
return if scopes.empty?
|
221
213
|
|
222
214
|
curr_scope = scopes.last
|
215
|
+
# Oddly enough, Lox allows variable re-declaration at top-level
|
223
216
|
if curr_scope.include?(aVarName)
|
224
217
|
msg = "Error at '#{aVarName}': Already variable with this name in this scope."
|
225
|
-
raise
|
218
|
+
raise Loxxy::RuntimeError, msg
|
219
|
+
elsif curr_scope.size == 255 && current_function != :none
|
220
|
+
msg = "Error at '#{aVarName}': Too many local variables in function."
|
221
|
+
raise Loxxy::RuntimeError, msg
|
226
222
|
end
|
227
223
|
|
228
224
|
# The initializer is not yet processed.
|
@@ -7,6 +7,10 @@ module Loxxy
|
|
7
7
|
module Datatype
|
8
8
|
# Class for representing a Lox numeric value.
|
9
9
|
class Number < BuiltinDatatype
|
10
|
+
def zero?
|
11
|
+
value.zero?
|
12
|
+
end
|
13
|
+
|
10
14
|
# Perform the addition of two Lox numbers or
|
11
15
|
# one Lox number and a Ruby Numeric
|
12
16
|
# @param other [Loxxy::Datatype::Number, Numeric]
|
@@ -59,17 +63,28 @@ module Loxxy
|
|
59
63
|
# one Lox number and a Ruby Numeric
|
60
64
|
# @param other [Loxxy::Datatype::Number, Numeric]
|
61
65
|
# @return [Loxxy::Datatype::Number]
|
66
|
+
# rubocop: disable Lint/BinaryOperatorWithIdenticalOperands
|
62
67
|
def /(other)
|
63
68
|
case other
|
64
|
-
when Number
|
65
|
-
|
66
|
-
|
67
|
-
|
69
|
+
when Number, Numeric
|
70
|
+
if other.zero?
|
71
|
+
if zero?
|
72
|
+
# NaN case detected
|
73
|
+
self.class.new(0.0 / 0.0)
|
74
|
+
else
|
75
|
+
raise ZeroDivisionError
|
76
|
+
end
|
77
|
+
elsif other.kind_of?(Number)
|
78
|
+
self.class.new(value / other.value)
|
79
|
+
else
|
80
|
+
self.class.new(value / other)
|
81
|
+
end
|
68
82
|
else
|
69
83
|
err_msg = "'/': Operands must be numbers."
|
70
84
|
raise TypeError, err_msg
|
71
85
|
end
|
72
86
|
end
|
87
|
+
# rubocop: enable Lint/BinaryOperatorWithIdenticalOperands
|
73
88
|
|
74
89
|
# Unary minus (return value with changed sign)
|
75
90
|
# @return [Loxxy::Datatype::Number]
|