loxxy 0.3.01 → 0.3.02
Sign up to get free protection for your applications and to get access to all the features.
- 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
|