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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +63 -0
- data/README.md +25 -11
- data/bin/loxxy +9 -5
- 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 +16 -22
- data/lib/loxxy/datatype/number.rb +19 -4
- data/lib/loxxy/error.rb +3 -0
- data/lib/loxxy/front_end/parser.rb +1 -1
- data/lib/loxxy/front_end/scanner.rb +43 -17
- data/lib/loxxy/version.rb +1 -1
- data/spec/front_end/scanner_spec.rb +69 -7
- data/spec/interpreter_spec.rb +36 -0
- metadata +3 -3
- 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
|
-
#
|
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
|
-
#
|
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
|
data/lib/loxxy/ast/lox_node.rb
CHANGED
@@ -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
|
-
#
|
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,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
|
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
|
@@ -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
|
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
|
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
|
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
|
224
|
-
|
225
|
-
|
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
|