loxxy 0.3.01 → 0.3.02
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 +15 -0
- data/lib/loxxy/ast/ast_builder.rb +1 -1
- data/lib/loxxy/ast/ast_visitor.rb +0 -1
- data/lib/loxxy/ast/lox_set_expr.rb +5 -2
- data/lib/loxxy/back_end/engine.rb +141 -49
- data/lib/loxxy/back_end/environment.rb +4 -0
- data/lib/loxxy/back_end/lox_class.rb +6 -8
- data/lib/loxxy/back_end/lox_function.rb +13 -7
- data/lib/loxxy/back_end/lox_instance.rb +1 -1
- data/lib/loxxy/back_end/resolver.rb +1 -0
- data/lib/loxxy/front_end/scanner.rb +0 -2
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/engine_spec.rb +13 -3
- data/spec/front_end/parser_spec.rb +4 -4
- data/spec/interpreter_spec.rb +44 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0e398714d64763c288f06c315611c86bf3dae2e3403055ae3ca240a10bd292f
|
4
|
+
data.tar.gz: 8db95e837824181e1794277dc3e67e4005432006877124bbd62ffd07def57591
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: caf2aab9a8c03997fbde67467b7cb66a786c2fb8ef46fc24869c42ac7da76831f5e771a282c938ec83585666de801602ffc9818fc489454ec93dec9bd76bf5a7
|
7
|
+
data.tar.gz: 9d98db7d9bddec915929dc1cbaed057248ccb5e0cc63efc3d0d7e99536ea9b330925d91d6b1ffd6805194dc3805b0025228f66d11f11b5455cb8f52d7753e6eb
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## [0.3.02] - 2021-05-22
|
2
|
+
- New built-in expressions `getc`, `chr`, `exit` and `print_eeror` , fixes with deeply nested returns, set expressions
|
3
|
+
|
4
|
+
### New
|
5
|
+
- Class `BackEnd::Engine` new built-in functions `getc`, `chr`, `exit`, and `print_error`
|
6
|
+
|
7
|
+
### Changed
|
8
|
+
- Class `Ast::LoxSetExpr` value to assign is now a distinct atrrbute instead of a subnode
|
9
|
+
- Class `BackEnd::Engine` two distinct stacks: one of expression evaluation, another for argument passing
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
- Class `BackEnd::LoxClass`: in some contexts the init method returned twice 'this'.
|
13
|
+
- Method `LoxFunction#call` mismatch between deeply nested returns and environments
|
14
|
+
|
15
|
+
|
1
16
|
## [0.3.01] - 2021-05-08
|
2
17
|
- Fix in `Scanner` class, added more tests in test suite.
|
3
18
|
|
@@ -318,7 +318,7 @@ module Loxxy
|
|
318
318
|
name_assignee = theChildren[1].token.lexeme.dup
|
319
319
|
if theChildren[0].kind_of?(Ast::LoxSetExpr)
|
320
320
|
theChildren[0].property = name_assignee
|
321
|
-
theChildren[0].
|
321
|
+
theChildren[0].value = theChildren[3]
|
322
322
|
theChildren[0]
|
323
323
|
else
|
324
324
|
Ast::LoxAssignExpr.new(tokens[1].position, name_assignee, theChildren[3])
|
@@ -126,7 +126,6 @@ module Loxxy
|
|
126
126
|
# @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
|
127
127
|
def visit_set_expr(aSetExpr)
|
128
128
|
broadcast(:before_set_expr, aSetExpr, self)
|
129
|
-
traverse_subnodes(aSetExpr)
|
130
129
|
broadcast(:after_set_expr, aSetExpr, self)
|
131
130
|
end
|
132
131
|
|
@@ -4,17 +4,20 @@ require_relative 'lox_compound_expr'
|
|
4
4
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
|
-
class LoxSetExpr <
|
7
|
+
class LoxSetExpr < LoxNode
|
8
8
|
# @return [Ast::LoxNode] the object to which the property belongs to
|
9
9
|
attr_reader :object
|
10
10
|
|
11
11
|
# @return [String] Name of an object property
|
12
12
|
attr_accessor :property
|
13
13
|
|
14
|
+
# @return [LoxNode, Datatype] value to assign
|
15
|
+
attr_accessor :value
|
16
|
+
|
14
17
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
18
|
# @param anObject [Ast::LoxNode] The object which the given property is being set
|
16
19
|
def initialize(aPosition, anObject)
|
17
|
-
super(aPosition
|
20
|
+
super(aPosition)
|
18
21
|
@object = anObject
|
19
22
|
end
|
20
23
|
|
@@ -20,7 +20,7 @@ module Loxxy
|
|
20
20
|
# @return [BackEnd::SymbolTable]
|
21
21
|
attr_reader :symbol_table
|
22
22
|
|
23
|
-
# @return [Array<Datatype::BuiltinDatatype>] Data stack for
|
23
|
+
# @return [Array<Datatype::BuiltinDatatype>] Data stack for arguments and return results
|
24
24
|
attr_reader :stack
|
25
25
|
|
26
26
|
# @return [Hash { Symbol => UnaryOperator}]
|
@@ -35,17 +35,31 @@ module Loxxy
|
|
35
35
|
# @param theOptions [Hash]
|
36
36
|
def initialize(theOptions)
|
37
37
|
@config = theOptions
|
38
|
+
@istream = config.include?(:istream) ? config[:istream] : $stdin
|
38
39
|
@ostream = config.include?(:ostream) ? config[:ostream] : $stdout
|
39
40
|
@symbol_table = SymbolTable.new
|
40
41
|
@stack = []
|
41
|
-
@unary_operators = {}
|
42
|
-
@binary_operators = {}
|
43
42
|
|
43
|
+
reset_expr_stack
|
44
44
|
init_unary_operators
|
45
45
|
init_binary_operators
|
46
46
|
init_globals
|
47
47
|
end
|
48
48
|
|
49
|
+
# Returns the current environment
|
50
|
+
# @return [Loxxy::BackEnd::Environment]
|
51
|
+
def current_env
|
52
|
+
symbol_table.current_env
|
53
|
+
end
|
54
|
+
|
55
|
+
def expr_stack
|
56
|
+
current_env.expr_stack
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset_expr_stack
|
60
|
+
current_env.expr_stack.clear
|
61
|
+
end
|
62
|
+
|
49
63
|
# Given an abstract syntax parse tree visitor, launch the visit
|
50
64
|
# and execute the visit events in the output stream.
|
51
65
|
# @param aVisitor [AST::ASTVisitor]
|
@@ -58,7 +72,7 @@ module Loxxy
|
|
58
72
|
aVisitor.subscribe(self)
|
59
73
|
aVisitor.start
|
60
74
|
aVisitor.unsubscribe(self)
|
61
|
-
|
75
|
+
expr_stack.empty? ? Datatype::Nil.instance : expr_stack.pop
|
62
76
|
end
|
63
77
|
|
64
78
|
##########################################################################
|
@@ -69,10 +83,14 @@ module Loxxy
|
|
69
83
|
# Do nothing, subnodes were already evaluated
|
70
84
|
end
|
71
85
|
|
86
|
+
def before_class_stmt(_class_stmt)
|
87
|
+
reset_expr_stack
|
88
|
+
end
|
89
|
+
|
72
90
|
def after_class_stmt(aClassStmt, aVisitor)
|
73
91
|
if aClassStmt.superclass
|
74
92
|
aClassStmt.superclass.accept(aVisitor)
|
75
|
-
parent =
|
93
|
+
parent = expr_stack.pop
|
76
94
|
unless parent.kind_of?(LoxClass)
|
77
95
|
raise Loxxy::RuntimeError, 'Superclass must be a class.'
|
78
96
|
end
|
@@ -89,7 +107,7 @@ module Loxxy
|
|
89
107
|
meths = aClassStmt.body.map do |func_node|
|
90
108
|
func_node.is_method = true
|
91
109
|
func_node.accept(aVisitor)
|
92
|
-
mth =
|
110
|
+
mth = expr_stack.pop
|
93
111
|
mth.is_initializer = true if mth.name == 'init'
|
94
112
|
mth
|
95
113
|
end
|
@@ -104,11 +122,15 @@ module Loxxy
|
|
104
122
|
symbol_table.insert(new_var)
|
105
123
|
end
|
106
124
|
|
125
|
+
def before_var_stmt(_var_stmt)
|
126
|
+
reset_expr_stack
|
127
|
+
end
|
128
|
+
|
107
129
|
def after_var_stmt(aVarStmt)
|
108
130
|
new_var = Variable.new(aVarStmt.name, Datatype::Nil.instance)
|
109
131
|
symbol_table.insert(new_var)
|
110
132
|
|
111
|
-
value =
|
133
|
+
value = expr_stack.pop
|
112
134
|
new_var.assign(value)
|
113
135
|
end
|
114
136
|
|
@@ -116,9 +138,14 @@ module Loxxy
|
|
116
138
|
before_block_stmt(aForStmt)
|
117
139
|
end
|
118
140
|
|
141
|
+
def before_if_stmt(_if_stmt)
|
142
|
+
reset_expr_stack
|
143
|
+
end
|
144
|
+
|
119
145
|
def after_if_stmt(anIfStmt, aVisitor)
|
120
146
|
# Retrieve the result of the condition evaluation
|
121
|
-
condition = stack.pop
|
147
|
+
# condition = stack.pop
|
148
|
+
condition = expr_stack.pop
|
122
149
|
if condition.truthy?
|
123
150
|
anIfStmt.then_stmt.accept(aVisitor)
|
124
151
|
elsif anIfStmt.else_stmt
|
@@ -126,18 +153,31 @@ module Loxxy
|
|
126
153
|
end
|
127
154
|
end
|
128
155
|
|
156
|
+
def before_print_stmt(_print_stmt)
|
157
|
+
reset_expr_stack
|
158
|
+
end
|
159
|
+
|
129
160
|
def after_print_stmt(_printStmt)
|
130
|
-
tos =
|
161
|
+
tos = expr_stack.pop
|
131
162
|
@ostream.print tos ? tos.to_str : 'nil'
|
132
163
|
end
|
133
164
|
|
165
|
+
def before_return_stmt(_return_stmt)
|
166
|
+
reset_expr_stack
|
167
|
+
end
|
168
|
+
|
134
169
|
def after_return_stmt(_returnStmt, _aVisitor)
|
170
|
+
stack.push(expr_stack.pop)
|
135
171
|
throw(:return)
|
136
172
|
end
|
137
173
|
|
174
|
+
def before_while_stmt(_while_stmt)
|
175
|
+
reset_expr_stack
|
176
|
+
end
|
177
|
+
|
138
178
|
def after_while_stmt(aWhileStmt, aVisitor)
|
139
179
|
loop do
|
140
|
-
condition =
|
180
|
+
condition = expr_stack.pop
|
141
181
|
break unless condition.truthy?
|
142
182
|
|
143
183
|
aWhileStmt.body.accept(aVisitor)
|
@@ -146,6 +186,7 @@ module Loxxy
|
|
146
186
|
end
|
147
187
|
|
148
188
|
def before_block_stmt(_aBlockStmt)
|
189
|
+
reset_expr_stack
|
149
190
|
new_env = Environment.new
|
150
191
|
symbol_table.enter_environment(new_env)
|
151
192
|
end
|
@@ -159,40 +200,42 @@ module Loxxy
|
|
159
200
|
variable = variable_lookup(anAssignExpr)
|
160
201
|
raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless variable
|
161
202
|
|
162
|
-
value =
|
203
|
+
value = expr_stack.last # ToS remains since an assignment produces a value
|
163
204
|
variable.assign(value)
|
164
205
|
end
|
165
206
|
|
166
|
-
def before_set_expr(
|
167
|
-
|
168
|
-
aSetExpr.object.accept(aVisitor)
|
207
|
+
def before_set_expr(_set_expr, _visitor)
|
208
|
+
reset_expr_stack
|
169
209
|
end
|
170
210
|
|
171
|
-
def after_set_expr(aSetExpr,
|
172
|
-
|
173
|
-
|
211
|
+
def after_set_expr(aSetExpr, aVisitor)
|
212
|
+
# Evaluate receiver object part (i.e. 'this')
|
213
|
+
aSetExpr.object.accept(aVisitor)
|
214
|
+
assignee = expr_stack.pop
|
174
215
|
unless assignee.kind_of?(LoxInstance)
|
175
216
|
raise Loxxy::RuntimeError, 'Only instances have fields.'
|
176
217
|
end
|
177
218
|
|
219
|
+
aSetExpr.value.accept(aVisitor)
|
220
|
+
value = expr_stack.pop
|
221
|
+
|
178
222
|
assignee.set(aSetExpr.property, value)
|
179
|
-
|
223
|
+
expr_stack.push value
|
180
224
|
end
|
181
225
|
|
182
226
|
def after_logical_expr(aLogicalExpr, visitor)
|
183
227
|
op = aLogicalExpr.operator
|
184
|
-
operand1 =
|
228
|
+
operand1 = expr_stack.pop # only first operand was evaluated
|
185
229
|
result = nil
|
186
230
|
if ((op == :and) && operand1.falsey?) || ((op == :or) && operand1.truthy?)
|
187
231
|
result = operand1
|
188
232
|
else
|
189
233
|
raw_operand2 = aLogicalExpr.subnodes[1]
|
190
234
|
raw_operand2.accept(visitor) # Visit means operand2 is evaluated
|
191
|
-
operand2 =
|
235
|
+
operand2 = expr_stack.pop
|
192
236
|
result = logical_2nd_arg(operand2)
|
193
237
|
end
|
194
|
-
|
195
|
-
stack.push result
|
238
|
+
expr_stack.push result
|
196
239
|
end
|
197
240
|
|
198
241
|
def logical_2nd_arg(operand2)
|
@@ -212,14 +255,14 @@ module Loxxy
|
|
212
255
|
end
|
213
256
|
|
214
257
|
def after_binary_expr(aBinaryExpr)
|
215
|
-
operand2 =
|
216
|
-
operand1 =
|
258
|
+
operand2 = expr_stack.pop
|
259
|
+
operand1 = expr_stack.pop
|
217
260
|
op = aBinaryExpr.operator
|
218
261
|
operator = binary_operators[op]
|
219
262
|
operator.validate_operands(operand1, operand2)
|
220
263
|
if operand1.respond_to?(op)
|
221
264
|
result = operand1.send(op, operand2)
|
222
|
-
|
265
|
+
expr_stack.push convert2lox_datatype(result)
|
223
266
|
else
|
224
267
|
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
225
268
|
raise StandardError, msg1
|
@@ -227,13 +270,13 @@ module Loxxy
|
|
227
270
|
end
|
228
271
|
|
229
272
|
def after_unary_expr(anUnaryExpr)
|
230
|
-
operand =
|
273
|
+
operand = expr_stack.pop
|
231
274
|
op = anUnaryExpr.operator
|
232
275
|
operator = unary_operators[op]
|
233
276
|
operator.validate_operand(operand)
|
234
277
|
if operand.respond_to?(op)
|
235
278
|
result = operand.send(op)
|
236
|
-
|
279
|
+
expr_stack.push convert2lox_datatype(result)
|
237
280
|
else
|
238
281
|
msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
|
239
282
|
raise StandardError, msg1
|
@@ -243,12 +286,17 @@ module Loxxy
|
|
243
286
|
def after_call_expr(aCallExpr, aVisitor)
|
244
287
|
# Evaluate callee part
|
245
288
|
aCallExpr.callee.accept(aVisitor)
|
246
|
-
callee =
|
289
|
+
callee = expr_stack.pop
|
290
|
+
before_size = expr_stack.size
|
247
291
|
aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
|
292
|
+
after_size = expr_stack.size
|
293
|
+
if after_size > before_size
|
294
|
+
stack.concat(expr_stack.pop(after_size - before_size))
|
295
|
+
end
|
248
296
|
|
249
297
|
case callee
|
250
298
|
when NativeFunction
|
251
|
-
|
299
|
+
expr_stack.push callee.call # Pass arguments
|
252
300
|
when LoxFunction, LoxClass
|
253
301
|
arg_count = aCallExpr.arguments.size
|
254
302
|
if arg_count != callee.arity
|
@@ -263,12 +311,12 @@ module Loxxy
|
|
263
311
|
|
264
312
|
def after_get_expr(aGetExpr, aVisitor)
|
265
313
|
aGetExpr.object.accept(aVisitor)
|
266
|
-
instance =
|
314
|
+
instance = expr_stack.pop
|
267
315
|
unless instance.kind_of?(LoxInstance)
|
268
316
|
raise Loxxy::RuntimeError, 'Only instances have properties.'
|
269
317
|
end
|
270
318
|
|
271
|
-
|
319
|
+
expr_stack.push instance.get(aGetExpr.property)
|
272
320
|
end
|
273
321
|
|
274
322
|
def after_grouping_expr(_groupingExpr)
|
@@ -285,7 +333,7 @@ module Loxxy
|
|
285
333
|
|
286
334
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
287
335
|
def before_literal_expr(literalExpr)
|
288
|
-
|
336
|
+
expr_stack.push(literalExpr.literal)
|
289
337
|
end
|
290
338
|
|
291
339
|
def after_this_expr(aThisExpr, aVisitor)
|
@@ -304,18 +352,22 @@ module Loxxy
|
|
304
352
|
raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
|
305
353
|
end
|
306
354
|
|
307
|
-
|
355
|
+
expr_stack.push method.bind(instance)
|
308
356
|
end
|
309
357
|
|
310
358
|
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
311
359
|
def before_visit_builtin(aValue)
|
312
|
-
|
360
|
+
expr_stack.push(aValue)
|
361
|
+
end
|
362
|
+
|
363
|
+
def before_fun_stmt(_fun_stmt, _visitor)
|
364
|
+
reset_expr_stack
|
313
365
|
end
|
314
366
|
|
315
367
|
def after_fun_stmt(aFunStmt, _visitor)
|
316
368
|
function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
|
317
369
|
if aFunStmt.is_method
|
318
|
-
|
370
|
+
expr_stack.push function
|
319
371
|
else
|
320
372
|
new_var = Variable.new(aFunStmt.name, function)
|
321
373
|
symbol_table.insert(new_var)
|
@@ -337,21 +389,9 @@ module Loxxy
|
|
337
389
|
env.defns[aVarNode.name]
|
338
390
|
end
|
339
391
|
|
340
|
-
NativeFunction = Struct.new(:callable, :interp) do
|
341
|
-
def accept(_visitor)
|
342
|
-
interp.stack.push self
|
343
|
-
end
|
344
|
-
|
345
|
-
def call
|
346
|
-
callable.call
|
347
|
-
end
|
348
|
-
|
349
|
-
def to_str
|
350
|
-
'<native fn>'
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
392
|
def init_unary_operators
|
393
|
+
@unary_operators = {}
|
394
|
+
|
355
395
|
negate_op = UnaryOperator.new('-', [Datatype::Number])
|
356
396
|
unary_operators[:-@] = negate_op
|
357
397
|
|
@@ -361,6 +401,8 @@ module Loxxy
|
|
361
401
|
end
|
362
402
|
|
363
403
|
def init_binary_operators
|
404
|
+
@binary_operators = {}
|
405
|
+
|
364
406
|
plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
|
365
407
|
[Datatype::LXString, :idem]])
|
366
408
|
binary_operators[:+] = plus_op
|
@@ -395,6 +437,24 @@ module Loxxy
|
|
395
437
|
|
396
438
|
def init_globals
|
397
439
|
add_native_fun('clock', native_clock)
|
440
|
+
add_native_fun('getc', native_getc)
|
441
|
+
add_native_fun('chr', native_chr)
|
442
|
+
add_native_fun('exit', native_exit)
|
443
|
+
add_native_fun('print_error', native_print_error)
|
444
|
+
end
|
445
|
+
|
446
|
+
NativeFunction = Struct.new(:callable, :interp) do
|
447
|
+
def accept(_visitor)
|
448
|
+
interp.expr_stack.push self
|
449
|
+
end
|
450
|
+
|
451
|
+
def call
|
452
|
+
callable.call
|
453
|
+
end
|
454
|
+
|
455
|
+
def to_str
|
456
|
+
'<native fn>'
|
457
|
+
end
|
398
458
|
end
|
399
459
|
|
400
460
|
def add_native_fun(aName, aProc)
|
@@ -411,6 +471,38 @@ module Loxxy
|
|
411
471
|
end
|
412
472
|
end
|
413
473
|
|
474
|
+
# Read a single character and return the character code as an integer.
|
475
|
+
def native_getc
|
476
|
+
proc do
|
477
|
+
ch = @istream.getc
|
478
|
+
Datatype::Number.new(ch.codepoints[0])
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
# chr(ch): Convert given character code number to a single-character string.
|
483
|
+
def native_chr
|
484
|
+
proc do
|
485
|
+
codepoint = stack.pop
|
486
|
+
Datatype::LXString.new(codepoint.value.chr)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# exit(status): Exit with given status code.
|
491
|
+
def native_exit
|
492
|
+
proc do
|
493
|
+
status = stack.pop
|
494
|
+
exit(status.value)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
# print_error(message): Print message string on stderr.
|
499
|
+
def native_print_error
|
500
|
+
proc do
|
501
|
+
message = stack.pop
|
502
|
+
$stderr.print message.value
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
414
506
|
def convert2lox_datatype(item)
|
415
507
|
case item
|
416
508
|
when TrueClass then Datatype::True.instance
|
@@ -25,11 +25,15 @@ module Loxxy
|
|
25
25
|
# @return [Hash{String => Variable}] Pairs of the kind
|
26
26
|
attr_reader :defns
|
27
27
|
|
28
|
+
# @return [Array<LoxNode>] stack of values needed in evaluating an expression
|
29
|
+
attr_reader :expr_stack
|
30
|
+
|
28
31
|
# Construct a environment instance.
|
29
32
|
# @param aParent [Environment, NilClass] Parent environment to this one.
|
30
33
|
def initialize(aParent = nil)
|
31
34
|
@enclosing = aParent
|
32
35
|
@defns = {}
|
36
|
+
@expr_stack = []
|
33
37
|
end
|
34
38
|
|
35
39
|
# Add a new variable to the environment.
|
@@ -7,17 +7,15 @@ module Loxxy
|
|
7
7
|
module BackEnd
|
8
8
|
# Runtime representation of a Lox class.
|
9
9
|
class LoxClass
|
10
|
-
# rubocop: disable Style/AccessorGrouping
|
11
|
-
|
12
10
|
# @return [String] The name of the class
|
13
11
|
attr_reader :name
|
14
12
|
attr_reader :superclass
|
15
13
|
|
16
14
|
# @return [Hash{String => LoxFunction}] the list of methods
|
17
15
|
attr_reader :meths
|
18
|
-
attr_reader :stack
|
19
16
|
|
20
|
-
#
|
17
|
+
# @return [Loxxy::BackEnd::Engine]
|
18
|
+
attr_reader :engine
|
21
19
|
|
22
20
|
# Create a class with given name
|
23
21
|
# @param aName [String] The name of the class
|
@@ -28,11 +26,11 @@ module Loxxy
|
|
28
26
|
theMethods.each do |func|
|
29
27
|
meths[func.name] = func
|
30
28
|
end
|
31
|
-
@
|
29
|
+
@engine = anEngine
|
32
30
|
end
|
33
31
|
|
34
32
|
def accept(_visitor)
|
35
|
-
|
33
|
+
engine.expr_stack.push self
|
36
34
|
end
|
37
35
|
|
38
36
|
def arity
|
@@ -46,9 +44,9 @@ module Loxxy
|
|
46
44
|
if initializer
|
47
45
|
constructor = initializer.bind(instance)
|
48
46
|
constructor.call(engine, visitor)
|
47
|
+
else
|
48
|
+
engine.expr_stack.push(instance)
|
49
49
|
end
|
50
|
-
|
51
|
-
engine.stack.push(instance)
|
52
50
|
end
|
53
51
|
|
54
52
|
# @param aName [String] the method name to search for
|
@@ -13,7 +13,7 @@ module Loxxy
|
|
13
13
|
# @return [Array<>] the parameters
|
14
14
|
attr_reader :parameters
|
15
15
|
attr_reader :body
|
16
|
-
attr_reader :
|
16
|
+
attr_reader :engine
|
17
17
|
attr_reader :closure
|
18
18
|
attr_accessor :is_initializer
|
19
19
|
|
@@ -23,7 +23,7 @@ module Loxxy
|
|
23
23
|
@name = aName.dup
|
24
24
|
@parameters = parameterList
|
25
25
|
@body = aBody.kind_of?(Ast::LoxNoopExpr) ? aBody : aBody.subnodes[0]
|
26
|
-
@
|
26
|
+
@engine = anEngine
|
27
27
|
@closure = anEngine.symbol_table.current_env
|
28
28
|
@is_initializer = false
|
29
29
|
anEngine.symbol_table.current_env.embedding = true
|
@@ -34,28 +34,34 @@ module Loxxy
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def accept(_visitor)
|
37
|
-
|
37
|
+
engine.expr_stack.push self
|
38
38
|
end
|
39
39
|
|
40
|
-
def call(
|
40
|
+
def call(_engine, aVisitor)
|
41
41
|
new_env = Environment.new(closure)
|
42
42
|
engine.symbol_table.enter_environment(new_env)
|
43
43
|
|
44
44
|
parameters&.each do |param_name|
|
45
|
-
local = Variable.new(param_name, stack.pop)
|
45
|
+
local = Variable.new(param_name, engine.stack.pop)
|
46
46
|
engine.symbol_table.insert(local)
|
47
47
|
end
|
48
48
|
|
49
49
|
catch(:return) do
|
50
|
-
(body.nil? || body.kind_of?(Ast::LoxNoopExpr)
|
51
|
-
|
50
|
+
body.accept(aVisitor) unless body.nil? || body.kind_of?(Ast::LoxNoopExpr)
|
51
|
+
# implicit return at end of function...
|
52
|
+
engine.stack.push(Datatype::Nil.instance) unless is_initializer
|
52
53
|
end
|
54
|
+
# Compensate for deeply nested return
|
55
|
+
engine.symbol_table.leave_environment while engine.current_env != new_env
|
56
|
+
|
53
57
|
if is_initializer
|
54
58
|
enclosing_env = engine.symbol_table.current_env.enclosing
|
55
59
|
engine.stack.push(enclosing_env.defns['this'].value)
|
56
60
|
end
|
57
61
|
|
58
62
|
engine.symbol_table.leave_environment
|
63
|
+
# engine.expr_stack.clear
|
64
|
+
engine.expr_stack.push(engine.stack.pop) unless engine.stack.empty?
|
59
65
|
end
|
60
66
|
|
61
67
|
def bind(anInstance)
|
@@ -91,7 +91,6 @@ module Loxxy
|
|
91
91
|
|
92
92
|
private
|
93
93
|
|
94
|
-
# rubocop: disable Lint/DuplicateBranch
|
95
94
|
def _next_token
|
96
95
|
skip_intertoken_spaces
|
97
96
|
curr_ch = scanner.peek(1)
|
@@ -125,7 +124,6 @@ module Loxxy
|
|
125
124
|
|
126
125
|
return token
|
127
126
|
end
|
128
|
-
# rubocop: enable Lint/DuplicateBranch
|
129
127
|
|
130
128
|
def build_token(aSymbolName, aLexeme)
|
131
129
|
begin
|
data/lib/loxxy/version.rb
CHANGED
@@ -35,8 +35,8 @@ module Loxxy
|
|
35
35
|
let(:lit_expr) { Ast::LoxLiteralExpr.new(sample_pos, greeting) }
|
36
36
|
|
37
37
|
it "should react to 'after_var_stmt' event" do
|
38
|
-
# Precondition: value to assign is on top of stack
|
39
|
-
subject.
|
38
|
+
# Precondition: value to assign is on top of expr stack
|
39
|
+
subject.expr_stack.push(greeting)
|
40
40
|
|
41
41
|
expect { subject.after_var_stmt(var_decl) }.not_to raise_error
|
42
42
|
current_env = subject.symbol_table.current_env
|
@@ -46,7 +46,17 @@ module Loxxy
|
|
46
46
|
|
47
47
|
it "should react to 'before_literal_expr' event" do
|
48
48
|
expect { subject.before_literal_expr(lit_expr) }.not_to raise_error
|
49
|
-
expect(subject.
|
49
|
+
expect(subject.expr_stack.pop).to eq(greeting)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'Built-in functions:' do
|
54
|
+
it 'should provide built-in functions' do
|
55
|
+
symb_table = subject.symbol_table
|
56
|
+
%w[clock getc chr exit print_error].each do |name|
|
57
|
+
fun_var = symb_table.current_env.defns[name]
|
58
|
+
expect(fun_var.value).to be_kind_of(BackEnd::Engine::NativeFunction)
|
59
|
+
end
|
50
60
|
end
|
51
61
|
end
|
52
62
|
end # describe
|
@@ -353,8 +353,8 @@ LOX_END
|
|
353
353
|
expect(expr).to be_kind_of(Ast::LoxSetExpr)
|
354
354
|
expect(expr.object.name).to eq('someObject')
|
355
355
|
expect(expr.property).to eq('someProperty')
|
356
|
-
expect(expr.
|
357
|
-
expect(expr.
|
356
|
+
expect(expr.value).to be_kind_of(Ast::LoxVariableExpr)
|
357
|
+
expect(expr.value.name).to eq('value')
|
358
358
|
end
|
359
359
|
|
360
360
|
it 'should parse complex set access' do
|
@@ -372,8 +372,8 @@ LOX_END
|
|
372
372
|
expr = ptree.root.subnodes[0]
|
373
373
|
expect(expr).to be_kind_of(Ast::LoxSetExpr)
|
374
374
|
expect(expr.property).to eq('meat')
|
375
|
-
expect(expr.
|
376
|
-
expect(expr.
|
375
|
+
expect(expr.value).to be_kind_of(Ast::LoxVariableExpr)
|
376
|
+
expect(expr.value.name).to eq('ham')
|
377
377
|
expect(expr.object).to be_kind_of(Ast::LoxGetExpr)
|
378
378
|
expect(expr.object.property).to eq('filling')
|
379
379
|
expect(expr.object.object).to be_kind_of(Ast::LoxGetExpr)
|
data/spec/interpreter_spec.rb
CHANGED
@@ -430,6 +430,50 @@ LOX_END
|
|
430
430
|
expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
|
431
431
|
end
|
432
432
|
|
433
|
+
it "should implement 'getc' function" do
|
434
|
+
input_str = 'Abc'
|
435
|
+
cfg = { istream: StringIO.new(input_str) }
|
436
|
+
interpreter = Loxxy::Interpreter.new(cfg)
|
437
|
+
source = 'getc();'
|
438
|
+
result = interpreter.evaluate(source)
|
439
|
+
expect(result.value).to eq(65) # codepoint for letter 'A'
|
440
|
+
end
|
441
|
+
|
442
|
+
it "should implement 'chr' function" do
|
443
|
+
source = 'chr(65); // => "A"'
|
444
|
+
result = subject.evaluate(source)
|
445
|
+
expect(result.value).to eq('A')
|
446
|
+
end
|
447
|
+
|
448
|
+
# This test is disabled since it causes RSpec to stop immediately
|
449
|
+
# it "should implement 'exit' function" do
|
450
|
+
# source = 'exit(100); // Process halts with exit code 100'
|
451
|
+
# expect { subject.evaluate(source) }.to raise(SystemExit)
|
452
|
+
# end
|
453
|
+
|
454
|
+
it "should implement 'print_error' function" do
|
455
|
+
source = 'print_error("Some error"); // => Some error on stderr'
|
456
|
+
stderr_backup = $stderr
|
457
|
+
$stderr = StringIO.new
|
458
|
+
expect { subject.evaluate(source) }.not_to raise_error
|
459
|
+
expect($stderr.string).to eq('Some error')
|
460
|
+
$stderr = stderr_backup
|
461
|
+
end
|
462
|
+
|
463
|
+
# rubocop: disable Style/StringConcatenation
|
464
|
+
it 'should return in absence of explicit return statement' do
|
465
|
+
program = <<-LOX_END
|
466
|
+
fun foo() {
|
467
|
+
print "foo";
|
468
|
+
}
|
469
|
+
|
470
|
+
print foo();
|
471
|
+
LOX_END
|
472
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
473
|
+
expect(sample_cfg[:ostream].string).to eq('foo' + 'nil')
|
474
|
+
end
|
475
|
+
# rubocop: enable Style/StringConcatenation
|
476
|
+
|
433
477
|
it 'should support return statements' do
|
434
478
|
program = <<-LOX_END
|
435
479
|
fun max(a, b) {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: loxxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.02
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|