loxxy 0.0.23 → 0.0.28
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 +49 -0
- data/README.md +55 -15
- data/lib/loxxy/ast/all_lox_nodes.rb +5 -0
- data/lib/loxxy/ast/ast_builder.rb +63 -77
- data/lib/loxxy/ast/ast_visitor.rb +45 -3
- data/lib/loxxy/ast/lox_assign_expr.rb +27 -0
- data/lib/loxxy/ast/lox_block_stmt.rb +23 -0
- data/lib/loxxy/ast/lox_call_expr.rb +25 -0
- data/lib/loxxy/ast/lox_for_stmt.rb +42 -0
- data/lib/loxxy/ast/lox_while_stmt.rb +32 -0
- data/lib/loxxy/back_end/engine.rb +85 -0
- data/lib/loxxy/back_end/environment.rb +3 -3
- data/lib/loxxy/back_end/symbol_table.rb +3 -3
- data/lib/loxxy/back_end/variable.rb +6 -1
- data/lib/loxxy/front_end/grammar.rb +30 -30
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/environment_spec.rb +1 -1
- data/spec/back_end/symbol_table_spec.rb +1 -1
- data/spec/datatype/lx_string_spec.rb +2 -0
- data/spec/datatype/number_spec.rb +3 -1
- data/spec/front_end/scanner_spec.rb +2 -0
- data/spec/interpreter_spec.rb +112 -2
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b9202e7a755cffecb3226da5764a3cb6f75fd3179803eb25779ae42822bbf15
|
4
|
+
data.tar.gz: 49767873134c5c124aab3e6b20a49c60f968df180dfd6e945f3da2808eee59c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29c481015e414ea71eb55c0b4dcc4d793b011a8f16fa7f9218ec704b2baea820e47ba758ba9a2e30e5bdfe08bfd2ad8fa892e6d67e247938b8bcda7b005ff2a9
|
7
|
+
data.tar.gz: cbf671e54aa98e799b6d565cac52fb19fb3f5d7b3ae84841f27067ae427e5a7c25a7d40a07d39645150f73176709fbcc6e3bbf36e9cada14d8b329f335c621d5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,52 @@
|
|
1
|
+
## [0.0.27] - 2021-01-24
|
2
|
+
- The interpreter implements `while` loops.
|
3
|
+
|
4
|
+
### Added
|
5
|
+
- Class `Ast::LoxForStmt` a node that represents a `for` statement
|
6
|
+
- Method `Ast::ASTBuilder#reduce_for_stmt`
|
7
|
+
- Method `Ast::ASTBuilder#reduce_for_control` creates an `Ast::LoxForStmt` node
|
8
|
+
- Method `Ast::ASTVisitor#visit_for_stmt` for visiting an `Ast::LoxWhileStmt` node
|
9
|
+
- Method `BackEnd::Engine#before_for_stmt` builds a new environment for the loop variable
|
10
|
+
- Method `BackEnd::Engine#after_for_stmt` implements most of the `for` control flow
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- File `README.md` updated.
|
14
|
+
|
15
|
+
## [0.0.26] - 2021-01-22
|
16
|
+
- The interpreter implements `while` loops.
|
17
|
+
|
18
|
+
### Added
|
19
|
+
- Class `Ast::LoxWhileStmt` a node that represents a `while` statement
|
20
|
+
- Method `Ast::ASTBuilder#reduce_while_stmt` creates an `Ast::LoxWhileStmt` node
|
21
|
+
- Method `Ast::ASTVisitor#visit_while_stmt` for visiting an `Ast::LoxWhileStmt` node
|
22
|
+
- Method `BackEnd::Engine#after_while_stmt` implements the while looping structure
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
- File `README.md` updated.
|
26
|
+
|
27
|
+
## [0.0.25] - 2021-01-21
|
28
|
+
- The interpreter implements blocks of code.
|
29
|
+
|
30
|
+
### Added
|
31
|
+
- Class `Ast::LoxBlockStmt` a node that represents a block of code
|
32
|
+
- Method `Ast::ASTBuilder#reduce_block_stmt` creates an `Ast::LoxBlockStmt` node
|
33
|
+
- Method `Ast::ASTVisitor#visit_block_stmt` for visiting an `Ast::LoxBlockStmt` node
|
34
|
+
- Method `BackEnd::Engine#before_block_stmt` creates an new enclosed Environment
|
35
|
+
- Method `BackEnd::Engine#after_block_stmt` close enclosed Environment and make parent Environment the current one
|
36
|
+
|
37
|
+
### Changed
|
38
|
+
- File `README.md` updated.
|
39
|
+
|
40
|
+
## [0.0.24] - 2021-01-20
|
41
|
+
- The interpreter implements the assignment of variables.
|
42
|
+
|
43
|
+
### Added
|
44
|
+
- Class `Ast::LoxAssignExpr` a node that represents the assignment of a value to a variable
|
45
|
+
- Method `Ast::ASTBuilder#reduce_assign_expr` creates an `Ast::LoxAssignExpr` node
|
46
|
+
- Method `Ast::ASTVisitor#visit_assign_expr` for visiting an `Ast::LoxAssignExpr` node
|
47
|
+
- Method `BackEnd::Engine#after_assign_expr` implementation of the assignment
|
48
|
+
- Method `BackEnd::Variable#assign` to assign a value to a variable
|
49
|
+
|
1
50
|
## [0.0.23] - 2021-01-20
|
2
51
|
- Fix for variables without explicit initialization.
|
3
52
|
|
data/README.md
CHANGED
@@ -14,9 +14,13 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
|
|
14
14
|
|
15
15
|
### Current status
|
16
16
|
The project is still in inception and the interpreter is being implemented...
|
17
|
-
Currently it can execute
|
17
|
+
Currently it can execute all allowed __Lox__ expressions and statement except:
|
18
|
+
- Functions and closures,
|
19
|
+
- Classes and objects.
|
20
|
+
|
21
|
+
These will be implemented soon.
|
22
|
+
|
18
23
|
|
19
|
-
But the __loxxy__ gem hosts also a parser class `RawPaser` that can parse, in principle, any valid Lox input.
|
20
24
|
|
21
25
|
## What's the fuss about Lox?
|
22
26
|
... Nothing...
|
@@ -131,7 +135,7 @@ program
|
|
131
135
|
On one hand, the parser covers the complete Lox grammar and should therefore, in principle,
|
132
136
|
parse any valid Lox program.
|
133
137
|
|
134
|
-
On the other hand, the interpreter is under development and currently it can evaluate only a
|
138
|
+
On the other hand, the interpreter is under development and currently it can evaluate only a subset of __Lox__.
|
135
139
|
But the situation is changing almost daily, stay tuned...
|
136
140
|
|
137
141
|
Here are the language features currently supported by the interpreter:
|
@@ -141,9 +145,12 @@ Here are the language features currently supported by the interpreter:
|
|
141
145
|
- [Datatypes](#datatypes)
|
142
146
|
- [Statements](#statements)
|
143
147
|
-[Expressions](#expressions)
|
144
|
-
- [Variable declarations](#var-statement)
|
148
|
+
- [Variable declarations](#var-statement)
|
149
|
+
- [For statement](#for-statement)
|
145
150
|
- [If Statement](#if-statement)
|
146
151
|
- [Print Statement](#print-statement)
|
152
|
+
- [While Statement](#while-statement)
|
153
|
+
- [Block Statement](#block-statement)
|
147
154
|
|
148
155
|
### Comments
|
149
156
|
|
@@ -176,11 +183,13 @@ Loxxy supports the following statements:
|
|
176
183
|
-[Comparison expressions](#comparison-expressions)
|
177
184
|
-[Logical expressions](#logical-expressions)
|
178
185
|
-[Grouping expressions](#grouping-expressions)
|
179
|
-
-[Variable expressions](#variable-expressions)
|
186
|
+
-[Variable expressions and assignments](#variable-expressions)
|
180
187
|
|
181
188
|
-[Variable declarations](#var-statement)
|
182
189
|
-[If Statement](#if-statement)
|
183
|
-
-[Print Statement](#print-statement)
|
190
|
+
-[Print Statement](#print-statement)
|
191
|
+
-[While Statement](#while-statement)
|
192
|
+
-[Block Statement](#block-statement)
|
184
193
|
|
185
194
|
#### Expressions
|
186
195
|
|
@@ -259,11 +268,13 @@ print 3 + 4 * 5; // => 23
|
|
259
268
|
print (3 + 4) * 5; // => 35
|
260
269
|
```
|
261
270
|
|
262
|
-
#### Variable expressions
|
271
|
+
#### Variable expressions and assignments
|
263
272
|
In __Lox__, a variable expression is nothing than retrieving the value of a variable.
|
264
273
|
``` javascript
|
265
274
|
var foo = "bar;" // Variable declaration
|
266
|
-
foo; //
|
275
|
+
print foo; // Variable expression (= use its value)
|
276
|
+
foo = "baz"; // Variable assignment
|
277
|
+
print foo; // Output: baz
|
267
278
|
```
|
268
279
|
|
269
280
|
#### Variable declarations
|
@@ -273,9 +284,14 @@ var iAmNil; // __Lox__ initializes variables to nil by default;
|
|
273
284
|
print iAmNil; // output: nil
|
274
285
|
```
|
275
286
|
|
276
|
-
|
277
|
-
Expect this capability to be implemented in the coming days.
|
287
|
+
#### For statement
|
278
288
|
|
289
|
+
Similar to the `for` statement in `C` language
|
290
|
+
``` javascript
|
291
|
+
for (var a = 1; a < 10; a = a + 1) {
|
292
|
+
print a; // Output: 123456789
|
293
|
+
}
|
294
|
+
```
|
279
295
|
|
280
296
|
#### If statement
|
281
297
|
|
@@ -297,20 +313,21 @@ The problem in a nutshell: in a nested if ... else ... statement like this:
|
|
297
313
|
``` javascript
|
298
314
|
'if (true) if (false) print "bad"; else print "good";
|
299
315
|
```
|
300
|
-
... there is an ambiguity.
|
316
|
+
... there is an ambiguity.
|
317
|
+
Indeed, according to the __Lox__ grammar, the `else` could be bound
|
301
318
|
either to the first `if` or to the second one.
|
302
319
|
This ambiguity is usually lifted by applying an ad-hoc rule: an `else` is aways bound to the most
|
303
320
|
recent (rightmost) `if`.
|
304
|
-
Being a generic parsing library, `Rley` doesn't apply any of these supplemental rules.
|
321
|
+
Being a generic parsing library, `Rley` doesn't apply any of these supplemental rules.
|
305
322
|
As a consequence,it complains about the found ambiguity and stops the parsing...
|
306
323
|
Although `Rley` can cope with ambiguities, this requires the use of an advanced data structure
|
307
324
|
called `Shared Packed Parse Forest (SPPF)`.
|
308
|
-
SPPF are much more complex to handle than the
|
325
|
+
SPPF are much more complex to handle than the "common" parse trees present in most compiler or interpreter books.
|
309
326
|
Therefore, a future version of `Rley` will incorporate the capability to define disambuiguation rules.
|
310
327
|
|
311
328
|
In the meantime, the `Loxxy` will progress on other __Lox__ features like:
|
312
|
-
- Variables,
|
313
329
|
- Block structures...
|
330
|
+
- Iteration structures (`for` and `while` loops)
|
314
331
|
|
315
332
|
|
316
333
|
#### Print Statement
|
@@ -322,6 +339,29 @@ print "Hello, world!"; // Output: Hello, world!
|
|
322
339
|
|
323
340
|
```
|
324
341
|
|
342
|
+
#### While Statement
|
343
|
+
|
344
|
+
``` javascript
|
345
|
+
var a = 1;
|
346
|
+
while (a < 10) {
|
347
|
+
print a;
|
348
|
+
a = a + 1;
|
349
|
+
} // Output: 123456789
|
350
|
+
```
|
351
|
+
|
352
|
+
#### Block Statement
|
353
|
+
__Lox__ has code blocks.
|
354
|
+
``` javascript
|
355
|
+
var a = "outer";
|
356
|
+
|
357
|
+
{
|
358
|
+
var a = "inner";
|
359
|
+
print a; // output: inner
|
360
|
+
}
|
361
|
+
|
362
|
+
print a; // output: outer
|
363
|
+
```
|
364
|
+
|
325
365
|
## Installation
|
326
366
|
|
327
367
|
Add this line to your application's Gemfile:
|
@@ -344,7 +384,7 @@ TODO: Write usage instructions here
|
|
344
384
|
|
345
385
|
## Other Lox implementations in Ruby
|
346
386
|
|
347
|
-
For Ruby, there is the [lox](https://github.com/rdodson41/ruby-lox) gem.
|
387
|
+
For Ruby, there is the [lox](https://github.com/rdodson41/ruby-lox) gem.
|
348
388
|
There are other Ruby-based projects as well:
|
349
389
|
- [SlowLox](https://github.com/ArminKleinert/SlowLox), described as a "1-to-1 conversion of JLox to Ruby"
|
350
390
|
- [rulox](https://github.com/LevitatingBusinessMan/rulox)
|
@@ -3,11 +3,16 @@
|
|
3
3
|
require_relative 'lox_variable_expr'
|
4
4
|
require_relative 'lox_literal_expr'
|
5
5
|
require_relative 'lox_noop_expr'
|
6
|
+
require_relative 'lox_call_expr'
|
6
7
|
require_relative 'lox_grouping_expr'
|
7
8
|
require_relative 'lox_unary_expr'
|
8
9
|
require_relative 'lox_binary_expr'
|
9
10
|
require_relative 'lox_logical_expr'
|
11
|
+
require_relative 'lox_assign_expr'
|
12
|
+
require_relative 'lox_block_stmt'
|
13
|
+
require_relative 'lox_while_stmt'
|
10
14
|
require_relative 'lox_print_stmt'
|
11
15
|
require_relative 'lox_if_stmt'
|
16
|
+
require_relative 'lox_for_stmt'
|
12
17
|
require_relative 'lox_var_stmt'
|
13
18
|
require_relative 'lox_seq_decl'
|
@@ -180,6 +180,29 @@ module Loxxy
|
|
180
180
|
Ast::LoxVarStmt.new(tokens[1].position, var_name, theChildren[3])
|
181
181
|
end
|
182
182
|
|
183
|
+
# rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
|
184
|
+
def reduce_for_stmt(_production, _range, _tokens, theChildren)
|
185
|
+
for_stmt = theChildren[2]
|
186
|
+
for_stmt.body_stmt = theChildren[4]
|
187
|
+
for_stmt
|
188
|
+
end
|
189
|
+
|
190
|
+
# rule('forControl' => 'forInitialization forTest forUpdate')
|
191
|
+
def reduce_for_control(_production, _range, tokens, theChildren)
|
192
|
+
(init, test, update) = theChildren
|
193
|
+
Ast::LoxForStmt.new(tokens[0].position, init, test, update)
|
194
|
+
end
|
195
|
+
|
196
|
+
# rule('forInitialization' => 'SEMICOLON')
|
197
|
+
def reduce_empty_for_initialization(_production, _range, _tokens, _theChildren)
|
198
|
+
nil
|
199
|
+
end
|
200
|
+
|
201
|
+
# rule('forTest' => 'expression_opt SEMICOLON')
|
202
|
+
def reduce_for_test(_production, range, tokens, theChildren)
|
203
|
+
return_first_child(range, tokens, theChildren)
|
204
|
+
end
|
205
|
+
|
183
206
|
# rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
|
184
207
|
def reduce_if_stmt(_production, _range, tokens, theChildren)
|
185
208
|
condition = theChildren[1]
|
@@ -193,99 +216,52 @@ module Loxxy
|
|
193
216
|
Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
|
194
217
|
end
|
195
218
|
|
196
|
-
# rule('
|
197
|
-
def
|
198
|
-
|
199
|
-
end
|
200
|
-
|
201
|
-
# rule('disjunct_plus' => 'disjunct_plus OR logic_and')
|
202
|
-
def reduce_logic_or_plus_more(production, range, tokens, theChildren)
|
203
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
204
|
-
end
|
205
|
-
|
206
|
-
# rule('disjunct_plus' => 'OR logic_and')
|
207
|
-
def reduce_logic_or_plus_end(production, range, tokens, theChildren)
|
208
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
219
|
+
# rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as ''
|
220
|
+
def reduce_while_stmt(_production, _range, tokens, theChildren)
|
221
|
+
Ast::LoxWhileStmt.new(tokens[1].position, theChildren[2], theChildren[4])
|
209
222
|
end
|
210
223
|
|
211
|
-
# rule('
|
212
|
-
def
|
213
|
-
|
224
|
+
# rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
|
225
|
+
def reduce_block_stmt(_production, _range, tokens, theChildren)
|
226
|
+
decls = LoxSeqDecl.new(tokens[1].position, theChildren[1])
|
227
|
+
Ast::LoxBlockStmt.new(tokens[1].position, decls)
|
214
228
|
end
|
215
229
|
|
216
|
-
# rule('
|
217
|
-
def
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
# rule('conjunct_plus' => 'AND equality')
|
222
|
-
def reduce_logic_and_plus_end(production, range, tokens, theChildren)
|
223
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
224
|
-
end
|
225
|
-
|
226
|
-
# rule('equality' => 'comparison equalityTest_plus')
|
227
|
-
def reduce_equality_plus(production, range, tokens, theChildren)
|
228
|
-
reduce_binary_operator(production, range, tokens, theChildren)
|
229
|
-
end
|
230
|
-
|
231
|
-
# rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison')
|
232
|
-
def reduce_equality_t_plus_more(production, range, tokens, theChildren)
|
233
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
234
|
-
end
|
235
|
-
|
236
|
-
# rule('equalityTest_star' => 'equalityTest comparison')
|
237
|
-
def reduce_equality_t_plus_end(production, range, tokens, theChildren)
|
238
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
239
|
-
end
|
240
|
-
|
241
|
-
# rule('comparison' => 'term comparisonTest_plus')
|
242
|
-
def reduce_comparison_plus(production, range, tokens, theChildren)
|
243
|
-
reduce_binary_operator(production, range, tokens, theChildren)
|
230
|
+
# rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
|
231
|
+
def reduce_assign_expr(_production, _range, tokens, theChildren)
|
232
|
+
var_name = theChildren[1].token.lexeme.dup
|
233
|
+
Ast::LoxAssignExpr.new(tokens[1].position, var_name, theChildren[3])
|
244
234
|
end
|
245
235
|
|
246
236
|
# rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
|
247
237
|
# TODO: is it meaningful to implement this rule?
|
248
238
|
|
249
|
-
# rule('
|
250
|
-
def
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
# rule('term' => 'factor additive_plus')
|
255
|
-
def reduce_term_additive(production, range, tokens, theChildren)
|
256
|
-
reduce_binary_operator(production, range, tokens, theChildren)
|
257
|
-
end
|
258
|
-
|
259
|
-
# rule('additive_star' => 'additive_star additionOp factor').as 'additionOp_expr'
|
260
|
-
def reduce_additive_plus_more(production, range, tokens, theChildren)
|
261
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
262
|
-
end
|
263
|
-
|
264
|
-
# rule('additive_plus' => 'additionOp factor')
|
265
|
-
def reduce_additive_plus_end(production, range, tokens, theChildren)
|
266
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
239
|
+
# rule('unary' => 'unaryOp unary')
|
240
|
+
def reduce_unary_expr(_production, _range, tokens, theChildren)
|
241
|
+
operator = Name2unary[theChildren[0].symbol.name].to_sym
|
242
|
+
operand = theChildren[1]
|
243
|
+
LoxUnaryExpr.new(tokens[0].position, operator, operand)
|
267
244
|
end
|
268
245
|
|
269
|
-
# rule('
|
270
|
-
def
|
271
|
-
|
246
|
+
# rule('call' => 'primary refinement_plus').as 'call_expr'
|
247
|
+
def reduce_call_expr(_production, _range, _tokens, theChildren)
|
248
|
+
theChildren[1].callee = theChildren[0]
|
249
|
+
theChildren[1]
|
272
250
|
end
|
273
251
|
|
274
|
-
# rule('
|
275
|
-
def
|
276
|
-
|
252
|
+
# rule('refinement_plus' => 'refinement').
|
253
|
+
def reduce_refinement_plus_end(_production, _range, _tokens, theChildren)
|
254
|
+
theChildren[0]
|
277
255
|
end
|
278
256
|
|
279
|
-
# rule('
|
280
|
-
def
|
281
|
-
|
282
|
-
|
257
|
+
# rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN')
|
258
|
+
def reduce_call_arglist(_production, _range, tokens, theChildren)
|
259
|
+
args = theChildren[1] || []
|
260
|
+
if args.size > 255
|
261
|
+
raise StandardError, "Can't have more than 255 arguments."
|
262
|
+
end
|
283
263
|
|
284
|
-
|
285
|
-
def reduce_unary_expr(_production, _range, tokens, theChildren)
|
286
|
-
operator = Name2unary[theChildren[0].symbol.name].to_sym
|
287
|
-
operand = theChildren[1]
|
288
|
-
LoxUnaryExpr.new(tokens[0].position, operator, operand)
|
264
|
+
LoxCallExpr.new(tokens[0].position, args)
|
289
265
|
end
|
290
266
|
|
291
267
|
# rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
|
@@ -307,6 +283,16 @@ module Loxxy
|
|
307
283
|
var_name = theChildren[0].token.lexeme
|
308
284
|
LoxVariableExpr.new(tokens[0].position, var_name)
|
309
285
|
end
|
286
|
+
|
287
|
+
# rule('arguments' => 'arguments COMMA expression')
|
288
|
+
def reduce_arguments_plus_more(_production, _range, _tokens, theChildren)
|
289
|
+
theChildren[0] << theChildren[2]
|
290
|
+
end
|
291
|
+
|
292
|
+
# rule('arguments' => 'expression')
|
293
|
+
def reduce_arguments_plus_end(_production, _range, _tokens, theChildren)
|
294
|
+
theChildren
|
295
|
+
end
|
310
296
|
end # class
|
311
297
|
end # module
|
312
298
|
end # module
|
@@ -67,6 +67,14 @@ module Loxxy
|
|
67
67
|
broadcast(:after_var_stmt, aVarStmt)
|
68
68
|
end
|
69
69
|
|
70
|
+
# Visit event. The visitor is about to visit a for statement.
|
71
|
+
# @param aForStmt [AST::LOXForStmt] the for statement node to visit
|
72
|
+
def visit_for_stmt(aForStmt)
|
73
|
+
broadcast(:before_for_stmt, aForStmt)
|
74
|
+
traverse_subnodes(aForStmt) # The condition is visited/evaluated here...
|
75
|
+
broadcast(:after_for_stmt, aForStmt, self)
|
76
|
+
end
|
77
|
+
|
70
78
|
# Visit event. The visitor is about to visit a if statement.
|
71
79
|
# @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
|
72
80
|
def visit_if_stmt(anIfStmt)
|
@@ -83,6 +91,30 @@ module Loxxy
|
|
83
91
|
broadcast(:after_print_stmt, aPrintStmt)
|
84
92
|
end
|
85
93
|
|
94
|
+
# Visit event. The visitor is about to visit a while statement node.
|
95
|
+
# @param aWhileStmt [AST::LOXWhileStmt] the while statement node to visit
|
96
|
+
def visit_while_stmt(aWhileStmt)
|
97
|
+
broadcast(:before_while_stmt, aWhileStmt)
|
98
|
+
traverse_subnodes(aWhileStmt) # The condition is visited/evaluated here...
|
99
|
+
broadcast(:after_while_stmt, aWhileStmt, self)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Visit event. The visitor is about to visit a block statement.
|
103
|
+
# @param aBlockStmt [AST::LOXBlockStmt] the print statement node to visit
|
104
|
+
def visit_block_stmt(aBlockStmt)
|
105
|
+
broadcast(:before_block_stmt, aBlockStmt)
|
106
|
+
traverse_subnodes(aBlockStmt)
|
107
|
+
broadcast(:after_block_stmt, aBlockStmt)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Visit event. The visitor is visiting an assignment node
|
111
|
+
# @param aLiteralExpr [AST::LoxAssignExpr] the variable assignment node to visit.
|
112
|
+
def visit_assign_expr(anAssignExpr)
|
113
|
+
broadcast(:before_assign_expr, anAssignExpr)
|
114
|
+
traverse_subnodes(anAssignExpr)
|
115
|
+
broadcast(:after_assign_expr, anAssignExpr)
|
116
|
+
end
|
117
|
+
|
86
118
|
# Visit event. The visitor is about to visit a logical expression.
|
87
119
|
# Since logical expressions may take shorcuts by not evaluating all their
|
88
120
|
# sub-expressiosns, they are responsible for visiting or not their children.
|
@@ -113,6 +145,14 @@ module Loxxy
|
|
113
145
|
broadcast(:after_unary_expr, anUnaryExpr)
|
114
146
|
end
|
115
147
|
|
148
|
+
# Visit event. The visitor is about to visit a call expression.
|
149
|
+
# @param aCallExpr [AST::LoxCallExpr] call expression to visit
|
150
|
+
def visit_call_expr(aCallExpr)
|
151
|
+
broadcast(:before_call_expr, aCallExpr)
|
152
|
+
traverse_subnodes(aCallExpr)
|
153
|
+
broadcast(:after_call_expr, aCallExpr, self)
|
154
|
+
end
|
155
|
+
|
116
156
|
# Visit event. The visitor is about to visit a grouping expression.
|
117
157
|
# @param aGroupingExpr [AST::LoxGroupingExpr] grouping expression to visit
|
118
158
|
def visit_grouping_expr(aGroupingExpr)
|
@@ -129,7 +169,7 @@ module Loxxy
|
|
129
169
|
broadcast(:after_literal_expr, aLiteralExpr)
|
130
170
|
end
|
131
171
|
|
132
|
-
# Visit event. The visitor is visiting a variable
|
172
|
+
# Visit event. The visitor is visiting a variable usage node
|
133
173
|
# @param aLiteralExpr [AST::LoxVariableExpr] the variable reference node to visit.
|
134
174
|
def visit_variable_expr(aVariableExpr)
|
135
175
|
broadcast(:before_variable_expr, aVariableExpr)
|
@@ -145,10 +185,12 @@ module Loxxy
|
|
145
185
|
|
146
186
|
# Visit event. The visitor is about to visit the given non terminal node.
|
147
187
|
# @param aNonTerminalNode [Rley::PTree::NonTerminalNode] the node to visit.
|
148
|
-
def visit_nonterminal(
|
188
|
+
def visit_nonterminal(non_terminal_node)
|
149
189
|
# Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
|
150
190
|
# that it cannot handle.
|
151
|
-
|
191
|
+
symb = non_terminal_node.symbol.name
|
192
|
+
msg = "Loxxy cannot execute this code yet for non-terminal symbol '#{symb}'."
|
193
|
+
raise NotImplementedError, msg
|
152
194
|
end
|
153
195
|
|
154
196
|
private
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
# This AST node represents the assignment of a value to a variable
|
8
|
+
class LoxAssignExpr < LoxCompoundExpr
|
9
|
+
# @return [String] variable name
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
13
|
+
# @param aName [String] name of the variable
|
14
|
+
# @param aValue [Loxxy::Ast::LoxNode, NilClass] value to assign
|
15
|
+
def initialize(aPosition, aName, aValue)
|
16
|
+
super(aPosition, [aValue])
|
17
|
+
@name = aName
|
18
|
+
end
|
19
|
+
|
20
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
21
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
22
|
+
def accept(visitor)
|
23
|
+
visitor.visit_assign_expr(self)
|
24
|
+
end
|
25
|
+
end # class
|
26
|
+
end # module
|
27
|
+
end # module
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxBlockStmt < LoxCompoundExpr
|
8
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
9
|
+
# @param operand [Loxxy::Ast::LoxSeqDecl]
|
10
|
+
def initialize(aPosition, decls)
|
11
|
+
super(aPosition, [decls])
|
12
|
+
end
|
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_block_stmt(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
alias operands subnodes
|
21
|
+
end # class
|
22
|
+
end # module
|
23
|
+
end # module
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxCallExpr < LoxCompoundExpr
|
8
|
+
attr_accessor :callee
|
9
|
+
attr_reader :arguments
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param argList [Array<Loxxy::Ast::LoxNode>]
|
13
|
+
def initialize(aPosition, argList)
|
14
|
+
super(aPosition, [])
|
15
|
+
@arguments = argList
|
16
|
+
end
|
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_call_expr(self)
|
22
|
+
end
|
23
|
+
end # class
|
24
|
+
end # module
|
25
|
+
end # module
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxForStmt < LoxCompoundExpr
|
8
|
+
# @return [LoxNode] test expression
|
9
|
+
attr_reader :test_expr
|
10
|
+
|
11
|
+
# @return [LoxNode] update expression
|
12
|
+
attr_reader :update_expr
|
13
|
+
|
14
|
+
# @return [LoxNode] body statement
|
15
|
+
attr_accessor :body_stmt
|
16
|
+
|
17
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
18
|
+
# @param initialization [Loxxy::Ast::LoxNode]
|
19
|
+
# @param testExpr [Loxxy::Ast::LoxNode]
|
20
|
+
# @param updateExpr [Loxxy::Ast::LoxNode]
|
21
|
+
# @param body [Loxxy::Ast::LoxNode]
|
22
|
+
def initialize(aPosition, initialization, testExpr, updateExpr)
|
23
|
+
child = initialization ? [initialization] : []
|
24
|
+
super(aPosition, child)
|
25
|
+
@test_expr = testExpr
|
26
|
+
@update_expr = updateExpr
|
27
|
+
end
|
28
|
+
|
29
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
30
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
31
|
+
def accept(visitor)
|
32
|
+
visitor.visit_for_stmt(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Accessor to the condition expression
|
36
|
+
# @return [LoxNode]
|
37
|
+
def condition
|
38
|
+
subnodes[0]
|
39
|
+
end
|
40
|
+
end # class
|
41
|
+
end # module
|
42
|
+
end # module
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxWhileStmt < LoxCompoundExpr
|
8
|
+
# @return [LoxNode] body of the while loop (as a statement)
|
9
|
+
attr_reader :body
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param condExpr [Loxxy::Ast::LoxNode] iteration condition
|
13
|
+
# @param theBody [Loxxy::Ast::LoxNode]
|
14
|
+
def initialize(aPosition, condExpr, theBody)
|
15
|
+
super(aPosition, [condExpr])
|
16
|
+
@body = theBody
|
17
|
+
end
|
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
|
+
# Accessor to the condition expression
|
26
|
+
# @return [LoxNode]
|
27
|
+
def condition
|
28
|
+
subnodes[0]
|
29
|
+
end
|
30
|
+
end # class
|
31
|
+
end # module
|
32
|
+
end # module
|
@@ -25,6 +25,8 @@ module Loxxy
|
|
25
25
|
@ostream = config.include?(:ostream) ? config[:ostream] : $stdout
|
26
26
|
@symbol_table = SymbolTable.new
|
27
27
|
@stack = []
|
28
|
+
|
29
|
+
init_globals
|
28
30
|
end
|
29
31
|
|
30
32
|
# Given an abstract syntax parse tree visitor, launch the visit
|
@@ -51,6 +53,23 @@ module Loxxy
|
|
51
53
|
symbol_table.insert(new_var)
|
52
54
|
end
|
53
55
|
|
56
|
+
def before_for_stmt(aForStmt)
|
57
|
+
before_block_stmt(aForStmt)
|
58
|
+
end
|
59
|
+
|
60
|
+
def after_for_stmt(aForStmt, aVisitor)
|
61
|
+
loop do
|
62
|
+
aForStmt.test_expr.accept(aVisitor)
|
63
|
+
condition = stack.pop
|
64
|
+
break unless condition.truthy?
|
65
|
+
|
66
|
+
aForStmt.body_stmt.accept(aVisitor)
|
67
|
+
aForStmt.update_expr&.accept(aVisitor)
|
68
|
+
stack.pop
|
69
|
+
end
|
70
|
+
after_block_stmt(aForStmt)
|
71
|
+
end
|
72
|
+
|
54
73
|
def after_if_stmt(anIfStmt, aVisitor)
|
55
74
|
# Retrieve the result of the condition evaluation
|
56
75
|
condition = stack.pop
|
@@ -66,6 +85,35 @@ module Loxxy
|
|
66
85
|
@ostream.print tos.to_str
|
67
86
|
end
|
68
87
|
|
88
|
+
def after_while_stmt(aWhileStmt, aVisitor)
|
89
|
+
loop do
|
90
|
+
condition = stack.pop
|
91
|
+
break unless condition.truthy?
|
92
|
+
|
93
|
+
aWhileStmt.body.accept(aVisitor)
|
94
|
+
aWhileStmt.condition.accept(aVisitor)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def before_block_stmt(_aBlockStmt)
|
99
|
+
new_env = Environment.new
|
100
|
+
symbol_table.enter_environment(new_env)
|
101
|
+
end
|
102
|
+
|
103
|
+
def after_block_stmt(_aBlockStmt)
|
104
|
+
symbol_table.leave_environment
|
105
|
+
end
|
106
|
+
|
107
|
+
def after_assign_expr(anAssignExpr)
|
108
|
+
var_name = anAssignExpr.name
|
109
|
+
variable = symbol_table.lookup(var_name)
|
110
|
+
raise StandardError, "Unknown variable #{var_name}" unless variable
|
111
|
+
|
112
|
+
value = stack.pop
|
113
|
+
variable.assign(value)
|
114
|
+
stack.push value # An expression produces a value
|
115
|
+
end
|
116
|
+
|
69
117
|
def after_logical_expr(aLogicalExpr, visitor)
|
70
118
|
op = aLogicalExpr.operator
|
71
119
|
operand1 = stack.pop # only first operand was evaluated
|
@@ -121,6 +169,17 @@ module Loxxy
|
|
121
169
|
end
|
122
170
|
end
|
123
171
|
|
172
|
+
def after_call_expr(aCallExpr, aVisitor)
|
173
|
+
# Evaluate callee part
|
174
|
+
aCallExpr.callee.accept(aVisitor)
|
175
|
+
callee = stack.pop
|
176
|
+
# TODO: reverse order?
|
177
|
+
aCallExpr.arguments.each do |arg|
|
178
|
+
arg.evaluate(aVisitor)
|
179
|
+
end
|
180
|
+
stack.push callee.call # Pass arguments
|
181
|
+
end
|
182
|
+
|
124
183
|
def after_grouping_expr(_groupingExpr)
|
125
184
|
# Do nothing: work was already done by visiting /evaluating the subexpression
|
126
185
|
end
|
@@ -142,6 +201,32 @@ module Loxxy
|
|
142
201
|
def before_visit_builtin(aValue)
|
143
202
|
stack.push(aValue)
|
144
203
|
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
NativeFunction = Struct.new(:callable, :interp) do
|
208
|
+
def accept(_visitor)
|
209
|
+
interp.stack.push callable
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def init_globals
|
214
|
+
add_native_fun('clock', native_clock)
|
215
|
+
end
|
216
|
+
|
217
|
+
def add_native_fun(aName, aProc)
|
218
|
+
native_fun = Variable.new(aName, NativeFunction.new(aProc, self))
|
219
|
+
symbol_table.insert(native_fun)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Ruby-native function that returns (as float) the number of seconds since
|
223
|
+
# a given time reference.
|
224
|
+
def native_clock
|
225
|
+
proc do
|
226
|
+
now = Time.now.to_f
|
227
|
+
Datatype::Number.new(now)
|
228
|
+
end
|
229
|
+
end
|
145
230
|
end # class
|
146
231
|
end # module
|
147
232
|
end # module
|
@@ -9,9 +9,9 @@ module Loxxy
|
|
9
9
|
# of a relation or a relation definition.
|
10
10
|
# It contains a map of names to the objects they name (e.g. logical var)
|
11
11
|
class Environment
|
12
|
-
# The parent
|
12
|
+
# The enclosing (parent) environment.
|
13
13
|
# @return [Environment, NilClass]
|
14
|
-
attr_accessor :
|
14
|
+
attr_accessor :enclosing
|
15
15
|
|
16
16
|
# Mapping from user-defined name to related definition
|
17
17
|
# (say, a variable object)
|
@@ -21,7 +21,7 @@ module Loxxy
|
|
21
21
|
# Construct a environment instance.
|
22
22
|
# @param aParent [Environment, NilClass] Parent environment to this one.
|
23
23
|
def initialize(aParent = nil)
|
24
|
-
@
|
24
|
+
@enclosing = aParent
|
25
25
|
@defns = {}
|
26
26
|
end
|
27
27
|
|
@@ -44,7 +44,7 @@ module Loxxy
|
|
44
44
|
# to be a child of current environment and to be itself the new current environment.
|
45
45
|
# @param anEnv [BackEnd::Environment] the Environment that
|
46
46
|
def enter_environment(anEnv)
|
47
|
-
anEnv.
|
47
|
+
anEnv.enclosing = current_env
|
48
48
|
@current_env = anEnv
|
49
49
|
end
|
50
50
|
|
@@ -60,7 +60,7 @@ module Loxxy
|
|
60
60
|
end
|
61
61
|
raise StandardError, 'Cannot remove root environment.' if current_env == root
|
62
62
|
|
63
|
-
@current_env = current_env.
|
63
|
+
@current_env = current_env.enclosing
|
64
64
|
end
|
65
65
|
|
66
66
|
# Add an entry with given name to current environment.
|
@@ -114,7 +114,7 @@ module Loxxy
|
|
114
114
|
while skope
|
115
115
|
vars_of_environment = skope.defns.select { |_, item| item.kind_of?(Variable) }
|
116
116
|
vars = vars_of_environment.values.concat(vars)
|
117
|
-
skope = skope.
|
117
|
+
skope = skope.enclosing
|
118
118
|
end
|
119
119
|
|
120
120
|
vars
|
@@ -11,13 +11,18 @@ module Loxxy
|
|
11
11
|
include Entry # Add expected behaviour for symbol table entries
|
12
12
|
|
13
13
|
# @return [Datatype::BuiltinDatatype] the value assigned to the variable
|
14
|
-
|
14
|
+
attr_reader :value
|
15
15
|
|
16
16
|
# Create a variable with given name and initial value
|
17
17
|
# @param aName [String] The name of the variable
|
18
18
|
# @param aValue [Datatype::BuiltinDatatype] the initial assigned value
|
19
19
|
def initialize(aName, aValue = Datatype::Nil.instance)
|
20
20
|
init_name(aName)
|
21
|
+
assign(aValue)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param aValue [Datatype::BuiltinDatatype] the assigned value
|
25
|
+
def assign(aValue)
|
21
26
|
@value = aValue
|
22
27
|
end
|
23
28
|
end # class
|
@@ -60,12 +60,12 @@ module Loxxy
|
|
60
60
|
|
61
61
|
rule('exprStmt' => 'expression SEMICOLON').as 'exprStmt'
|
62
62
|
|
63
|
-
rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
|
64
|
-
rule('forControl' => 'forInitialization forTest forUpdate')
|
63
|
+
rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement').as 'for_stmt'
|
64
|
+
rule('forControl' => 'forInitialization forTest forUpdate').as 'for_control'
|
65
65
|
rule('forInitialization' => 'varDecl')
|
66
66
|
rule('forInitialization' => 'exprStmt')
|
67
|
-
rule('forInitialization' => 'SEMICOLON')
|
68
|
-
rule('forTest' => 'expression_opt SEMICOLON')
|
67
|
+
rule('forInitialization' => 'SEMICOLON').as 'empty_for_initialization'
|
68
|
+
rule('forTest' => 'expression_opt SEMICOLON').as 'for_test'
|
69
69
|
rule('forUpdate' => 'expression_opt')
|
70
70
|
|
71
71
|
rule('ifStmt' => 'IF ifCondition statement elsePart_opt').as 'if_stmt'
|
@@ -75,50 +75,50 @@ module Loxxy
|
|
75
75
|
|
76
76
|
rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
|
77
77
|
rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
|
78
|
-
rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement')
|
79
|
-
rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
|
78
|
+
rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as 'while_stmt'
|
79
|
+
rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE').as 'block_stmt'
|
80
80
|
rule('block' => 'LEFT_BRACE RIGHT_BRACE')
|
81
81
|
|
82
82
|
# Expressions: produce values
|
83
83
|
rule('expression_opt' => 'expression')
|
84
84
|
rule('expression_opt' => [])
|
85
85
|
rule('expression' => 'assignment')
|
86
|
-
rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
|
86
|
+
rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
|
87
87
|
rule('assignment' => 'logic_or')
|
88
88
|
rule('owner_opt' => 'call DOT')
|
89
89
|
rule('owner_opt' => [])
|
90
90
|
rule('logic_or' => 'logic_and')
|
91
|
-
rule('logic_or' => 'logic_and disjunct_plus').as '
|
92
|
-
rule('disjunct_plus' => 'disjunct_plus OR logic_and').as '
|
93
|
-
rule('disjunct_plus' => 'OR logic_and').as '
|
91
|
+
rule('logic_or' => 'logic_and disjunct_plus').as 'logical_expr'
|
92
|
+
rule('disjunct_plus' => 'disjunct_plus OR logic_and').as 'binary_plus_more'
|
93
|
+
rule('disjunct_plus' => 'OR logic_and').as 'binary_plus_end'
|
94
94
|
rule('logic_and' => 'equality')
|
95
|
-
rule('logic_and' => 'equality conjunct_plus').as '
|
96
|
-
rule('conjunct_plus' => 'conjunct_plus AND equality').as '
|
97
|
-
rule('conjunct_plus' => 'AND equality').as '
|
95
|
+
rule('logic_and' => 'equality conjunct_plus').as 'logical_expr'
|
96
|
+
rule('conjunct_plus' => 'conjunct_plus AND equality').as 'binary_plus_more'
|
97
|
+
rule('conjunct_plus' => 'AND equality').as 'binary_plus_end'
|
98
98
|
rule('equality' => 'comparison')
|
99
|
-
rule('equality' => 'comparison equalityTest_plus').as '
|
100
|
-
rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison').as '
|
101
|
-
rule('equalityTest_plus' => 'equalityTest comparison').as '
|
99
|
+
rule('equality' => 'comparison equalityTest_plus').as 'binary_operator'
|
100
|
+
rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison').as 'binary_plus_more'
|
101
|
+
rule('equalityTest_plus' => 'equalityTest comparison').as 'binary_plus_end'
|
102
102
|
rule('equalityTest' => 'BANG_EQUAL')
|
103
103
|
rule('equalityTest' => 'EQUAL_EQUAL')
|
104
104
|
rule('comparison' => 'term')
|
105
|
-
rule('comparison' => 'term comparisonTest_plus').as '
|
105
|
+
rule('comparison' => 'term comparisonTest_plus').as 'binary_operator'
|
106
106
|
rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
|
107
|
-
rule('comparisonTest_plus' => 'comparisonTest term').as '
|
107
|
+
rule('comparisonTest_plus' => 'comparisonTest term').as 'binary_plus_end'
|
108
108
|
rule('comparisonTest' => 'GREATER')
|
109
109
|
rule('comparisonTest' => 'GREATER_EQUAL')
|
110
110
|
rule('comparisonTest' => 'LESS')
|
111
111
|
rule('comparisonTest' => 'LESS_EQUAL')
|
112
112
|
rule('term' => 'factor')
|
113
|
-
rule('term' => 'factor additive_plus').as '
|
114
|
-
rule('additive_plus' => 'additive_plus additionOp factor').as '
|
115
|
-
rule('additive_plus' => 'additionOp factor').as '
|
113
|
+
rule('term' => 'factor additive_plus').as 'binary_operator'
|
114
|
+
rule('additive_plus' => 'additive_plus additionOp factor').as 'binary_plus_more'
|
115
|
+
rule('additive_plus' => 'additionOp factor').as 'binary_plus_end'
|
116
116
|
rule('additionOp' => 'MINUS')
|
117
117
|
rule('additionOp' => 'PLUS')
|
118
118
|
rule('factor' => 'unary')
|
119
|
-
rule('factor' => 'unary multiplicative_plus').as '
|
120
|
-
rule('multiplicative_plus' => 'multiplicative_plus multOp unary').as '
|
121
|
-
rule('multiplicative_plus' => 'multOp unary').as '
|
119
|
+
rule('factor' => 'unary multiplicative_plus').as 'binary_operator'
|
120
|
+
rule('multiplicative_plus' => 'multiplicative_plus multOp unary').as 'binary_plus_more'
|
121
|
+
rule('multiplicative_plus' => 'multOp unary').as 'binary_plus_end'
|
122
122
|
rule('multOp' => 'SLASH')
|
123
123
|
rule('multOp' => 'STAR')
|
124
124
|
rule('unary' => 'unaryOp unary').as 'unary_expr'
|
@@ -126,10 +126,10 @@ module Loxxy
|
|
126
126
|
rule('unaryOp' => 'BANG')
|
127
127
|
rule('unaryOp' => 'MINUS')
|
128
128
|
rule('call' => 'primary')
|
129
|
-
rule('call' => 'primary refinement_plus')
|
130
|
-
rule('refinement_plus' => 'refinement_plus refinement')
|
131
|
-
rule('refinement_plus' => 'refinement')
|
132
|
-
rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN')
|
129
|
+
rule('call' => 'primary refinement_plus').as 'call_expr'
|
130
|
+
rule('refinement_plus' => 'refinement_plus refinement') # .as 'refinement_plus_more'
|
131
|
+
rule('refinement_plus' => 'refinement').as 'refinement_plus_end'
|
132
|
+
rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN').as 'call_arglist'
|
133
133
|
rule('refinement' => 'DOT IDENTIFIER')
|
134
134
|
rule('primary' => 'TRUE').as 'literal_expr'
|
135
135
|
rule('primary' => 'FALSE').as 'literal_expr'
|
@@ -149,8 +149,8 @@ module Loxxy
|
|
149
149
|
rule('parameters' => 'IDENTIFIER')
|
150
150
|
rule('arguments_opt' => 'arguments')
|
151
151
|
rule('arguments_opt' => [])
|
152
|
-
rule('arguments' => 'arguments COMMA expression')
|
153
|
-
rule('arguments' => 'expression')
|
152
|
+
rule('arguments' => 'arguments COMMA expression').as 'arguments_plus_more'
|
153
|
+
rule('arguments' => 'expression').as 'arguments_plus_end'
|
154
154
|
end
|
155
155
|
|
156
156
|
unless defined?(Grammar)
|
data/lib/loxxy/version.rb
CHANGED
@@ -60,7 +60,7 @@ module Loxxy
|
|
60
60
|
new_env = BackEnd::Environment.new
|
61
61
|
expect { subject.enter_environment(new_env) }.not_to raise_error
|
62
62
|
expect(subject.current_env).to eq(new_env)
|
63
|
-
expect(subject.current_env.
|
63
|
+
expect(subject.current_env.enclosing).to eq(subject.root)
|
64
64
|
expect(subject.name2envs['q']).to eq([subject.root])
|
65
65
|
end
|
66
66
|
|
@@ -26,6 +26,7 @@ module Loxxy
|
|
26
26
|
expect(subject.to_str).to eq(sample_text)
|
27
27
|
end
|
28
28
|
|
29
|
+
# rubocop: disable Lint/BinaryOperatorWithIdenticalOperands
|
29
30
|
it 'compares with another Lox string' do
|
30
31
|
result = subject == LXString.new(sample_text.dup)
|
31
32
|
expect(result).to be_true
|
@@ -40,6 +41,7 @@ module Loxxy
|
|
40
41
|
result = LXString.new('') == LXString.new('')
|
41
42
|
expect(result).to be_true
|
42
43
|
end
|
44
|
+
# rubocop: enable Lint/BinaryOperatorWithIdenticalOperands
|
43
45
|
|
44
46
|
it 'compares with a Ruby string' do
|
45
47
|
result = subject == sample_text.dup
|
@@ -21,6 +21,7 @@ module Loxxy
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
# rubocop: disable Lint/FloatComparison
|
24
25
|
context 'Provided services:' do
|
25
26
|
it 'should compare with other Lox numbers' do
|
26
27
|
result = subject == Number.new(sample_value)
|
@@ -51,7 +52,8 @@ module Loxxy
|
|
51
52
|
subtraction = subject - Number.new(10)
|
52
53
|
expect(subtraction == -22.34).to be_true
|
53
54
|
end
|
54
|
-
end
|
55
|
+
end # context
|
56
|
+
# rubocop: enable Lint/FloatComparison
|
55
57
|
end # describe
|
56
58
|
end # module
|
57
59
|
end # module
|
@@ -208,6 +208,7 @@ LOX_END
|
|
208
208
|
expect(eof_token.terminal).to eq('EOF')
|
209
209
|
end
|
210
210
|
|
211
|
+
# rubocop: disable Lint/PercentStringArray
|
211
212
|
it 'should skip end of line comments' do
|
212
213
|
input = <<-LOX_END
|
213
214
|
// first comment
|
@@ -223,6 +224,7 @@ LOX_END
|
|
223
224
|
]
|
224
225
|
match_expectations(subject, expectations)
|
225
226
|
end
|
227
|
+
# rubocop: enable Lint/PercentStringArray
|
226
228
|
|
227
229
|
it 'should cope with single slash (divide) expression' do
|
228
230
|
subject.start_with('8 / 2')
|
data/spec/interpreter_spec.rb
CHANGED
@@ -277,13 +277,123 @@ LOX_END
|
|
277
277
|
|
278
278
|
it 'should set uninitialized variables to nil' do
|
279
279
|
program = <<-LOX_END
|
280
|
-
var
|
281
|
-
print
|
280
|
+
var a;
|
281
|
+
print a; // => nil
|
282
282
|
LOX_END
|
283
283
|
expect { subject.evaluate(program) }.not_to raise_error
|
284
284
|
expect(sample_cfg[:ostream].string).to eq('nil')
|
285
285
|
end
|
286
286
|
|
287
|
+
it 'should accept assignments to a global variable' do
|
288
|
+
program = <<-LOX_END
|
289
|
+
var a = "before";
|
290
|
+
print a; // output: before
|
291
|
+
|
292
|
+
a = "after";
|
293
|
+
print a; // output: after
|
294
|
+
|
295
|
+
print a = "arg"; // output: arg
|
296
|
+
print a; // output: arg
|
297
|
+
LOX_END
|
298
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
299
|
+
expect(sample_cfg[:ostream].string).to eq('beforeafterargarg')
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'should support variables local to a block' do
|
303
|
+
program = <<-LOX_END
|
304
|
+
{
|
305
|
+
var a = "first";
|
306
|
+
print a;
|
307
|
+
}
|
308
|
+
{
|
309
|
+
var a = "second";
|
310
|
+
print a;
|
311
|
+
}
|
312
|
+
LOX_END
|
313
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
314
|
+
expect(sample_cfg[:ostream].string).to eq('firstsecond')
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'should support the shadowing of variables in a block' do
|
318
|
+
program = <<-LOX_END
|
319
|
+
var a = "outer";
|
320
|
+
|
321
|
+
{
|
322
|
+
var a = "inner";
|
323
|
+
print a; // output: inner
|
324
|
+
}
|
325
|
+
|
326
|
+
print a; // output: outer
|
327
|
+
LOX_END
|
328
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
329
|
+
expect(sample_cfg[:ostream].string).to eq('innerouter')
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'should implement single statement while loops' do
|
333
|
+
program = <<-LOX_END
|
334
|
+
// Single-expression body.
|
335
|
+
var c = 0;
|
336
|
+
while (c < 3) print c = c + 1;
|
337
|
+
// output: 1
|
338
|
+
// output: 2
|
339
|
+
// output: 3
|
340
|
+
LOX_END
|
341
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
342
|
+
expect(sample_cfg[:ostream].string).to eq('123')
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'should implement block body while loops' do
|
346
|
+
program = <<-LOX_END
|
347
|
+
// Block body.
|
348
|
+
var a = 0;
|
349
|
+
while (a < 3) {
|
350
|
+
print a;
|
351
|
+
a = a + 1;
|
352
|
+
}
|
353
|
+
// output: 0
|
354
|
+
// output: 1
|
355
|
+
// output: 2
|
356
|
+
LOX_END
|
357
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
358
|
+
expect(sample_cfg[:ostream].string).to eq('012')
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'should implement single statement for loops' do
|
362
|
+
program = <<-LOX_END
|
363
|
+
// Single-expression body.
|
364
|
+
for (var c = 0; c < 3;) print c = c + 1;
|
365
|
+
// output: 1
|
366
|
+
// output: 2
|
367
|
+
// output: 3
|
368
|
+
LOX_END
|
369
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
370
|
+
expect(sample_cfg[:ostream].string).to eq('123')
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'should implement for loops with block body' do
|
374
|
+
program = <<-LOX_END
|
375
|
+
// Block body.
|
376
|
+
for (var a = 0; a < 3; a = a + 1) {
|
377
|
+
print a;
|
378
|
+
}
|
379
|
+
// output: 0
|
380
|
+
// output: 1
|
381
|
+
// output: 2
|
382
|
+
LOX_END
|
383
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
384
|
+
expect(sample_cfg[:ostream].string).to eq('012')
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'should implement nullary function calls' do
|
388
|
+
program = <<-LOX_END
|
389
|
+
print clock(); // Lox expect the 'clock' predefined native function
|
390
|
+
LOX_END
|
391
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
392
|
+
tick = sample_cfg[:ostream].string
|
393
|
+
expect(Time.now.to_f - tick.to_f).to be < 0.1
|
394
|
+
end
|
395
|
+
|
396
|
+
|
287
397
|
it 'should print the hello world message' do
|
288
398
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
289
399
|
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|
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.0.
|
4
|
+
version: 0.0.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-02-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -87,8 +87,12 @@ files:
|
|
87
87
|
- lib/loxxy/ast/all_lox_nodes.rb
|
88
88
|
- lib/loxxy/ast/ast_builder.rb
|
89
89
|
- lib/loxxy/ast/ast_visitor.rb
|
90
|
+
- lib/loxxy/ast/lox_assign_expr.rb
|
90
91
|
- lib/loxxy/ast/lox_binary_expr.rb
|
92
|
+
- lib/loxxy/ast/lox_block_stmt.rb
|
93
|
+
- lib/loxxy/ast/lox_call_expr.rb
|
91
94
|
- lib/loxxy/ast/lox_compound_expr.rb
|
95
|
+
- lib/loxxy/ast/lox_for_stmt.rb
|
92
96
|
- lib/loxxy/ast/lox_grouping_expr.rb
|
93
97
|
- lib/loxxy/ast/lox_if_stmt.rb
|
94
98
|
- lib/loxxy/ast/lox_literal_expr.rb
|
@@ -100,6 +104,7 @@ files:
|
|
100
104
|
- lib/loxxy/ast/lox_unary_expr.rb
|
101
105
|
- lib/loxxy/ast/lox_var_stmt.rb
|
102
106
|
- lib/loxxy/ast/lox_variable_expr.rb
|
107
|
+
- lib/loxxy/ast/lox_while_stmt.rb
|
103
108
|
- lib/loxxy/back_end/engine.rb
|
104
109
|
- lib/loxxy/back_end/entry.rb
|
105
110
|
- lib/loxxy/back_end/environment.rb
|