loxxy 0.0.25 → 0.1.01
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 +67 -1
- data/README.md +30 -4
- data/lib/loxxy/ast/all_lox_nodes.rb +4 -0
- data/lib/loxxy/ast/ast_builder.rb +84 -83
- data/lib/loxxy/ast/ast_visitor.rb +44 -10
- data/lib/loxxy/ast/lox_block_stmt.rb +5 -1
- data/lib/loxxy/ast/lox_call_expr.rb +25 -0
- data/lib/loxxy/ast/lox_for_stmt.rb +41 -0
- data/lib/loxxy/ast/lox_fun_stmt.rb +34 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +3 -1
- data/lib/loxxy/ast/lox_while_stmt.rb +32 -0
- data/lib/loxxy/back_end/engine.rb +92 -1
- data/lib/loxxy/back_end/function.rb +44 -0
- data/lib/loxxy/front_end/grammar.rb +33 -33
- data/lib/loxxy/front_end/literal.rb +1 -1
- data/lib/loxxy/version.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 +96 -0
- 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: c477fc7db959df775fc4656e21d577dbb3fce65724b07c8a7ec020496c196100
|
4
|
+
data.tar.gz: f941317a01b0dbcdca250697a4e84b88f85b572fa7f4f67be3de7c162bc8e2cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b379767ee5cdf986a1b5e704c0254bb8901123811504ba58c59d3996897c29413e84298f6d3c65db849ad289183261d30657e1605d076c4ab9a8e9b0c38318da
|
7
|
+
data.tar.gz: 258f5a4d501bedbbec5f235ca7d858a4195c9088dcef8d63a6a16b9c25306167eecf93ff3b51aafc8ecf17fd6ed40f9dfe220797a033021f377eb62200de99c3
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,71 @@
|
|
1
|
+
## [0.1.01] - 2021-02-20
|
2
|
+
### Fixed
|
3
|
+
- Fixed most offences for Rubocop.
|
4
|
+
|
5
|
+
|
6
|
+
## [0.1.00] - 2021-02-20
|
7
|
+
- Version number bumped, `Loxxy` supports function definitions
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Class `Ast::LoxFunStmt` a node that represents a function declaration
|
11
|
+
- Method `Ast::ASTBuilder#reduce_fun_decl`
|
12
|
+
- Method `Ast::ASTBuilder#reduce_function` creates a `LoxFunStmt` instance
|
13
|
+
- Method `Ast::ASTBuilder#reduce_parameters_plus_more` for dealing with function parameters
|
14
|
+
- Method `Ast::ASTBuilder#reduce_parameters_plus_end`
|
15
|
+
- Method `Ast::ASTVisitor#visit_fun_stmt` for visiting an `Ast::LoxFunStmt` node
|
16
|
+
- Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
|
17
|
+
- Method `BackEnd::Engine#after_fun_stmt`
|
18
|
+
- Method `Backend::NativeFunction#call`
|
19
|
+
- Method `Backend::NativeFunction#to_str`
|
20
|
+
- Method `Backend::Function` implementation of a function object.
|
21
|
+
|
22
|
+
### Changed
|
23
|
+
- Method `BackEnd::Engine#after_call_expr`
|
24
|
+
|
25
|
+
### Fixed
|
26
|
+
- Fixed inconsistencies in documentation comments.
|
27
|
+
|
28
|
+
## [0.0.28] - 2021-02-15
|
29
|
+
- The interpreter implements function calls (to a native function).
|
30
|
+
|
31
|
+
### Added
|
32
|
+
- Class `Ast::LoxCallExpr` a node that represents a function call expression
|
33
|
+
- Method `Ast::ASTBuilder#reduce_call_expr`
|
34
|
+
- Method `Ast::ASTBuilder#reduce_refinement_plus_end`
|
35
|
+
- Method `Ast::ASTBuilder#reduce_call_arglist` creates a `LoxCallExpr` node
|
36
|
+
- Method `Ast::ASTBuilder#reduce_arguments_plus_more` builds the function argument array
|
37
|
+
- Method `Ast::ASTVisitor#visit_call_expr` for visiting an `Ast::LoxCallExpr` node
|
38
|
+
- Method `BackEnd::Engine#after_call_expr`implements the evaluation of a function call.
|
39
|
+
- Method `BackEnd::Engine#after_for_stmt` implements most of the `for` control flow
|
40
|
+
|
41
|
+
## [0.0.27] - 2021-01-24
|
42
|
+
- The interpreter implements `while` loops.
|
43
|
+
|
44
|
+
### Added
|
45
|
+
- Class `Ast::LoxForStmt` a node that represents a `for` statement
|
46
|
+
- Method `Ast::ASTBuilder#reduce_for_stmt`
|
47
|
+
- Method `Ast::ASTBuilder#reduce_for_control` creates an `Ast::LoxForStmt` node
|
48
|
+
- Method `Ast::ASTVisitor#visit_for_stmt` for visiting an `Ast::LoxWhileStmt` node
|
49
|
+
- Method `BackEnd::Engine#before_for_stmt` builds a new environment for the loop variable
|
50
|
+
- Method `BackEnd::Engine#after_for_stmt` implements most of the `for` control flow
|
51
|
+
|
52
|
+
### Changed
|
53
|
+
- File `README.md` updated.
|
54
|
+
|
55
|
+
## [0.0.26] - 2021-01-22
|
56
|
+
- The interpreter implements `while` loops.
|
57
|
+
|
58
|
+
### Added
|
59
|
+
- Class `Ast::LoxWhileStmt` a node that represents a `while` statement
|
60
|
+
- Method `Ast::ASTBuilder#reduce_while_stmt` creates an `Ast::LoxWhileStmt` node
|
61
|
+
- Method `Ast::ASTVisitor#visit_while_stmt` for visiting an `Ast::LoxWhileStmt` node
|
62
|
+
- Method `BackEnd::Engine#after_while_stmt` implements the while looping structure
|
63
|
+
|
64
|
+
### Changed
|
65
|
+
- File `README.md` updated.
|
66
|
+
|
1
67
|
## [0.0.25] - 2021-01-21
|
2
|
-
- The interpreter implements
|
68
|
+
- The interpreter implements blocks of code.
|
3
69
|
|
4
70
|
### Added
|
5
71
|
- Class `Ast::LoxBlockStmt` a node that represents a block of code
|
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,11 @@ 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)
|
147
153
|
- [Block Statement](#block-statement)
|
148
154
|
|
149
155
|
### Comments
|
@@ -182,6 +188,7 @@ Loxxy supports the following statements:
|
|
182
188
|
-[Variable declarations](#var-statement)
|
183
189
|
-[If Statement](#if-statement)
|
184
190
|
-[Print Statement](#print-statement)
|
191
|
+
-[While Statement](#while-statement)
|
185
192
|
-[Block Statement](#block-statement)
|
186
193
|
|
187
194
|
#### Expressions
|
@@ -277,6 +284,15 @@ var iAmNil; // __Lox__ initializes variables to nil by default;
|
|
277
284
|
print iAmNil; // output: nil
|
278
285
|
```
|
279
286
|
|
287
|
+
#### For statement
|
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
|
+
```
|
295
|
+
|
280
296
|
#### If statement
|
281
297
|
|
282
298
|
Based on a given condition, an if statement executes one of two statements:
|
@@ -323,6 +339,16 @@ print "Hello, world!"; // Output: Hello, world!
|
|
323
339
|
|
324
340
|
```
|
325
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
|
+
|
326
352
|
#### Block Statement
|
327
353
|
__Lox__ has code blocks.
|
328
354
|
``` javascript
|
@@ -1,15 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'lox_fun_stmt'
|
3
4
|
require_relative 'lox_variable_expr'
|
4
5
|
require_relative 'lox_literal_expr'
|
5
6
|
require_relative 'lox_noop_expr'
|
7
|
+
require_relative 'lox_call_expr'
|
6
8
|
require_relative 'lox_grouping_expr'
|
7
9
|
require_relative 'lox_unary_expr'
|
8
10
|
require_relative 'lox_binary_expr'
|
9
11
|
require_relative 'lox_logical_expr'
|
10
12
|
require_relative 'lox_assign_expr'
|
11
13
|
require_relative 'lox_block_stmt'
|
14
|
+
require_relative 'lox_while_stmt'
|
12
15
|
require_relative 'lox_print_stmt'
|
13
16
|
require_relative 'lox_if_stmt'
|
17
|
+
require_relative 'lox_for_stmt'
|
14
18
|
require_relative 'lox_var_stmt'
|
15
19
|
require_relative 'lox_seq_decl'
|
@@ -163,6 +163,11 @@ module Loxxy
|
|
163
163
|
[theChildren[0]]
|
164
164
|
end
|
165
165
|
|
166
|
+
# rule('funDecl' => 'FUN function')
|
167
|
+
def reduce_fun_decl(_production, _range, _tokens, theChildren)
|
168
|
+
theChildren[1]
|
169
|
+
end
|
170
|
+
|
166
171
|
# rule('exprStmt' => 'expression SEMICOLON')
|
167
172
|
def reduce_exprStmt(_production, range, tokens, theChildren)
|
168
173
|
return_first_child(range, tokens, theChildren) # Discard the semicolon
|
@@ -180,6 +185,29 @@ module Loxxy
|
|
180
185
|
Ast::LoxVarStmt.new(tokens[1].position, var_name, theChildren[3])
|
181
186
|
end
|
182
187
|
|
188
|
+
# rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
|
189
|
+
def reduce_for_stmt(_production, _range, _tokens, theChildren)
|
190
|
+
for_stmt = theChildren[2]
|
191
|
+
for_stmt.body_stmt = theChildren[4]
|
192
|
+
for_stmt
|
193
|
+
end
|
194
|
+
|
195
|
+
# rule('forControl' => 'forInitialization forTest forUpdate')
|
196
|
+
def reduce_for_control(_production, _range, tokens, theChildren)
|
197
|
+
(init, test, update) = theChildren
|
198
|
+
Ast::LoxForStmt.new(tokens[0].position, init, test, update)
|
199
|
+
end
|
200
|
+
|
201
|
+
# rule('forInitialization' => 'SEMICOLON')
|
202
|
+
def reduce_empty_for_initialization(_production, _range, _tokens, _theChildren)
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
|
206
|
+
# rule('forTest' => 'expression_opt SEMICOLON')
|
207
|
+
def reduce_for_test(_production, range, tokens, theChildren)
|
208
|
+
return_first_child(range, tokens, theChildren)
|
209
|
+
end
|
210
|
+
|
183
211
|
# rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
|
184
212
|
def reduce_if_stmt(_production, _range, tokens, theChildren)
|
185
213
|
condition = theChildren[1]
|
@@ -193,111 +221,57 @@ module Loxxy
|
|
193
221
|
Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
|
194
222
|
end
|
195
223
|
|
224
|
+
# rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as ''
|
225
|
+
def reduce_while_stmt(_production, _range, tokens, theChildren)
|
226
|
+
Ast::LoxWhileStmt.new(tokens[1].position, theChildren[2], theChildren[4])
|
227
|
+
end
|
228
|
+
|
196
229
|
# rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
|
197
230
|
def reduce_block_stmt(_production, _range, tokens, theChildren)
|
198
231
|
decls = LoxSeqDecl.new(tokens[1].position, theChildren[1])
|
199
232
|
Ast::LoxBlockStmt.new(tokens[1].position, decls)
|
200
233
|
end
|
201
234
|
|
235
|
+
# rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
|
236
|
+
def reduce_block_empty(_production, _range, tokens, _children)
|
237
|
+
Ast::LoxBlockStmt.new(tokens[0].position, nil)
|
238
|
+
end
|
239
|
+
|
202
240
|
# rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
|
203
241
|
def reduce_assign_expr(_production, _range, tokens, theChildren)
|
204
242
|
var_name = theChildren[1].token.lexeme.dup
|
205
243
|
Ast::LoxAssignExpr.new(tokens[1].position, var_name, theChildren[3])
|
206
244
|
end
|
207
245
|
|
208
|
-
# rule('logic_or' => 'logic_and disjunct_plus')
|
209
|
-
def reduce_logic_or_plus(production, range, tokens, theChildren)
|
210
|
-
reduce_logical_expr(production, range, tokens, theChildren)
|
211
|
-
end
|
212
|
-
|
213
|
-
# rule('disjunct_plus' => 'disjunct_plus OR logic_and')
|
214
|
-
def reduce_logic_or_plus_more(production, range, tokens, theChildren)
|
215
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
216
|
-
end
|
217
|
-
|
218
|
-
# rule('disjunct_plus' => 'OR logic_and')
|
219
|
-
def reduce_logic_or_plus_end(production, range, tokens, theChildren)
|
220
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
221
|
-
end
|
222
|
-
|
223
|
-
# rule('logic_and' => 'equality conjunct_plus')
|
224
|
-
def reduce_logic_and_plus(production, range, tokens, theChildren)
|
225
|
-
reduce_logical_expr(production, range, tokens, theChildren)
|
226
|
-
end
|
227
|
-
|
228
|
-
# rule('conjunct_plus' => 'conjunct_plus AND equality')
|
229
|
-
def reduce_logic_and_plus_more(production, range, tokens, theChildren)
|
230
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
231
|
-
end
|
232
|
-
|
233
|
-
# rule('conjunct_plus' => 'AND equality')
|
234
|
-
def reduce_logic_and_plus_end(production, range, tokens, theChildren)
|
235
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
236
|
-
end
|
237
|
-
|
238
|
-
# rule('equality' => 'comparison equalityTest_plus')
|
239
|
-
def reduce_equality_plus(production, range, tokens, theChildren)
|
240
|
-
reduce_binary_operator(production, range, tokens, theChildren)
|
241
|
-
end
|
242
|
-
|
243
|
-
# rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison')
|
244
|
-
def reduce_equality_t_plus_more(production, range, tokens, theChildren)
|
245
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
246
|
-
end
|
247
|
-
|
248
|
-
# rule('equalityTest_star' => 'equalityTest comparison')
|
249
|
-
def reduce_equality_t_plus_end(production, range, tokens, theChildren)
|
250
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
251
|
-
end
|
252
|
-
|
253
|
-
# rule('comparison' => 'term comparisonTest_plus')
|
254
|
-
def reduce_comparison_plus(production, range, tokens, theChildren)
|
255
|
-
reduce_binary_operator(production, range, tokens, theChildren)
|
256
|
-
end
|
257
|
-
|
258
246
|
# rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
|
259
247
|
# TODO: is it meaningful to implement this rule?
|
260
248
|
|
261
|
-
# rule('
|
262
|
-
def
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
# rule('term' => 'factor additive_plus')
|
267
|
-
def reduce_term_additive(production, range, tokens, theChildren)
|
268
|
-
reduce_binary_operator(production, range, tokens, theChildren)
|
269
|
-
end
|
270
|
-
|
271
|
-
# rule('additive_star' => 'additive_star additionOp factor').as 'additionOp_expr'
|
272
|
-
def reduce_additive_plus_more(production, range, tokens, theChildren)
|
273
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
249
|
+
# rule('unary' => 'unaryOp unary')
|
250
|
+
def reduce_unary_expr(_production, _range, tokens, theChildren)
|
251
|
+
operator = Name2unary[theChildren[0].symbol.name].to_sym
|
252
|
+
operand = theChildren[1]
|
253
|
+
LoxUnaryExpr.new(tokens[0].position, operator, operand)
|
274
254
|
end
|
275
255
|
|
276
|
-
# rule('
|
277
|
-
def
|
278
|
-
|
256
|
+
# rule('call' => 'primary refinement_plus').as 'call_expr'
|
257
|
+
def reduce_call_expr(_production, _range, _tokens, theChildren)
|
258
|
+
theChildren[1].callee = theChildren[0]
|
259
|
+
theChildren[1]
|
279
260
|
end
|
280
261
|
|
281
|
-
# rule('
|
282
|
-
def
|
283
|
-
|
262
|
+
# rule('refinement_plus' => 'refinement').
|
263
|
+
def reduce_refinement_plus_end(_production, _range, _tokens, theChildren)
|
264
|
+
theChildren[0]
|
284
265
|
end
|
285
266
|
|
286
|
-
# rule('
|
287
|
-
def
|
288
|
-
|
289
|
-
|
267
|
+
# rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN')
|
268
|
+
def reduce_call_arglist(_production, _range, tokens, theChildren)
|
269
|
+
args = theChildren[1] || []
|
270
|
+
if args.size > 255
|
271
|
+
raise StandardError, "Can't have more than 255 arguments."
|
272
|
+
end
|
290
273
|
|
291
|
-
|
292
|
-
def reduce_multiplicative_plus_end(production, range, tokens, theChildren)
|
293
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
294
|
-
end
|
295
|
-
|
296
|
-
# rule('unary' => 'unaryOp unary')
|
297
|
-
def reduce_unary_expr(_production, _range, tokens, theChildren)
|
298
|
-
operator = Name2unary[theChildren[0].symbol.name].to_sym
|
299
|
-
operand = theChildren[1]
|
300
|
-
LoxUnaryExpr.new(tokens[0].position, operator, operand)
|
274
|
+
LoxCallExpr.new(tokens[0].position, args)
|
301
275
|
end
|
302
276
|
|
303
277
|
# rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
|
@@ -319,6 +293,33 @@ module Loxxy
|
|
319
293
|
var_name = theChildren[0].token.lexeme
|
320
294
|
LoxVariableExpr.new(tokens[0].position, var_name)
|
321
295
|
end
|
296
|
+
|
297
|
+
# rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
|
298
|
+
def reduce_function(_production, _range, _tokens, theChildren)
|
299
|
+
first_child = theChildren.first
|
300
|
+
pos = first_child.token.position
|
301
|
+
LoxFunStmt.new(pos, first_child.token.lexeme, theChildren[2], theChildren[4])
|
302
|
+
end
|
303
|
+
|
304
|
+
# rule('parameters' => 'parameters COMMA IDENTIFIER')
|
305
|
+
def reduce_parameters_plus_more(_production, _range, _tokens, theChildren)
|
306
|
+
theChildren[0] << theChildren[2].token.lexeme
|
307
|
+
end
|
308
|
+
|
309
|
+
# rule('parameters' => 'IDENTIFIER')
|
310
|
+
def reduce_parameters_plus_end(_production, _range, _tokens, theChildren)
|
311
|
+
[theChildren[0].token.lexeme]
|
312
|
+
end
|
313
|
+
|
314
|
+
# rule('arguments' => 'arguments COMMA expression')
|
315
|
+
def reduce_arguments_plus_more(_production, _range, _tokens, theChildren)
|
316
|
+
theChildren[0] << theChildren[2]
|
317
|
+
end
|
318
|
+
|
319
|
+
# rule('arguments' => 'expression')
|
320
|
+
def reduce_arguments_plus_end(_production, _range, _tokens, theChildren)
|
321
|
+
theChildren
|
322
|
+
end
|
322
323
|
end # class
|
323
324
|
end # module
|
324
325
|
end # module
|
@@ -12,7 +12,7 @@ module Loxxy
|
|
12
12
|
# attr_reader(:runtime)
|
13
13
|
|
14
14
|
# Build a visitor for the given top.
|
15
|
-
# @param
|
15
|
+
# @param aTop [AST::LoxNode] the parse tree to visit.
|
16
16
|
def initialize(aTop)
|
17
17
|
raise StandardError if aTop.nil?
|
18
18
|
|
@@ -52,7 +52,7 @@ module Loxxy
|
|
52
52
|
end
|
53
53
|
|
54
54
|
# Visit event. The visitor is about to visit a variable declaration statement.
|
55
|
-
# @param
|
55
|
+
# @param aSeqDecls [AST::LOXSeqDecl] the variable declaration node to visit
|
56
56
|
def visit_seq_decl(aSeqDecls)
|
57
57
|
broadcast(:before_seq_decl, aSeqDecls)
|
58
58
|
traverse_subnodes(aSeqDecls)
|
@@ -60,13 +60,21 @@ module Loxxy
|
|
60
60
|
end
|
61
61
|
|
62
62
|
# Visit event. The visitor is about to visit a variable declaration statement.
|
63
|
-
# @param
|
63
|
+
# @param aVarStmt [AST::LOXVarStmt] the variable declaration node to visit
|
64
64
|
def visit_var_stmt(aVarStmt)
|
65
65
|
broadcast(:before_var_stmt, aVarStmt)
|
66
66
|
traverse_subnodes(aVarStmt)
|
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,16 +91,24 @@ 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
|
+
|
86
102
|
# Visit event. The visitor is about to visit a block statement.
|
87
103
|
# @param aBlockStmt [AST::LOXBlockStmt] the print statement node to visit
|
88
104
|
def visit_block_stmt(aBlockStmt)
|
89
105
|
broadcast(:before_block_stmt, aBlockStmt)
|
90
|
-
traverse_subnodes(aBlockStmt)
|
106
|
+
traverse_subnodes(aBlockStmt) unless aBlockStmt.empty?
|
91
107
|
broadcast(:after_block_stmt, aBlockStmt)
|
92
108
|
end
|
93
109
|
|
94
110
|
# Visit event. The visitor is visiting an assignment node
|
95
|
-
# @param
|
111
|
+
# @param anAssignExpr [AST::LoxAssignExpr] the variable assignment node to visit.
|
96
112
|
def visit_assign_expr(anAssignExpr)
|
97
113
|
broadcast(:before_assign_expr, anAssignExpr)
|
98
114
|
traverse_subnodes(anAssignExpr)
|
@@ -102,7 +118,7 @@ module Loxxy
|
|
102
118
|
# Visit event. The visitor is about to visit a logical expression.
|
103
119
|
# Since logical expressions may take shorcuts by not evaluating all their
|
104
120
|
# sub-expressiosns, they are responsible for visiting or not their children.
|
105
|
-
# @param
|
121
|
+
# @param aLogicalExpr [AST::LOXLogicalExpr] the logical expression node to visit
|
106
122
|
def visit_logical_expr(aLogicalExpr)
|
107
123
|
broadcast(:before_logical_expr, aLogicalExpr)
|
108
124
|
|
@@ -129,6 +145,14 @@ module Loxxy
|
|
129
145
|
broadcast(:after_unary_expr, anUnaryExpr)
|
130
146
|
end
|
131
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
|
+
|
132
156
|
# Visit event. The visitor is about to visit a grouping expression.
|
133
157
|
# @param aGroupingExpr [AST::LoxGroupingExpr] grouping expression to visit
|
134
158
|
def visit_grouping_expr(aGroupingExpr)
|
@@ -146,25 +170,35 @@ module Loxxy
|
|
146
170
|
end
|
147
171
|
|
148
172
|
# Visit event. The visitor is visiting a variable usage node
|
149
|
-
# @param
|
173
|
+
# @param aVariableExpr [AST::LoxVariableExpr] the variable reference node to visit.
|
150
174
|
def visit_variable_expr(aVariableExpr)
|
151
175
|
broadcast(:before_variable_expr, aVariableExpr)
|
152
176
|
broadcast(:after_variable_expr, aVariableExpr, self)
|
153
177
|
end
|
154
178
|
|
155
179
|
# Visit event. The visitor is about to visit the given terminal datatype value.
|
156
|
-
# @param
|
180
|
+
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
157
181
|
def visit_builtin(aValue)
|
158
182
|
broadcast(:before_visit_builtin, aValue)
|
159
183
|
broadcast(:after_visit_builtin, aValue)
|
160
184
|
end
|
161
185
|
|
186
|
+
# Visit event. The visitor is about to visit a function statement node.
|
187
|
+
# @param aFunStmt [AST::LoxFunStmt] function declaration to visit
|
188
|
+
def visit_fun_stmt(aFunStmt)
|
189
|
+
broadcast(:before_fun_stmt, aFunStmt)
|
190
|
+
traverse_subnodes(aFunStmt)
|
191
|
+
broadcast(:after_fun_stmt, aFunStmt, self)
|
192
|
+
end
|
193
|
+
|
162
194
|
# Visit event. The visitor is about to visit the given non terminal node.
|
163
195
|
# @param aNonTerminalNode [Rley::PTree::NonTerminalNode] the node to visit.
|
164
|
-
def visit_nonterminal(
|
196
|
+
def visit_nonterminal(aNonTerminalNode)
|
165
197
|
# Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
|
166
198
|
# that it cannot handle.
|
167
|
-
|
199
|
+
symb = aNonTerminalNode.symbol.name
|
200
|
+
msg = "Loxxy cannot execute this code yet for non-terminal symbol '#{symb}'."
|
201
|
+
raise NotImplementedError, msg
|
168
202
|
end
|
169
203
|
|
170
204
|
private
|
@@ -6,11 +6,15 @@ module Loxxy
|
|
6
6
|
module Ast
|
7
7
|
class LoxBlockStmt < LoxCompoundExpr
|
8
8
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
9
|
-
# @param
|
9
|
+
# @param decls [Loxxy::Ast::LoxSeqDecl]
|
10
10
|
def initialize(aPosition, decls)
|
11
11
|
super(aPosition, [decls])
|
12
12
|
end
|
13
13
|
|
14
|
+
def empty?
|
15
|
+
subnodes.size == 1 && subnodes[0].nil?
|
16
|
+
end
|
17
|
+
|
14
18
|
# Part of the 'visitee' role in Visitor design pattern.
|
15
19
|
# @param visitor [Ast::ASTVisitor] the visitor
|
16
20
|
def accept(visitor)
|
@@ -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,41 @@
|
|
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
|
+
def initialize(aPosition, initialization, testExpr, updateExpr)
|
22
|
+
child = initialization ? [initialization] : []
|
23
|
+
super(aPosition, child)
|
24
|
+
@test_expr = testExpr
|
25
|
+
@update_expr = updateExpr
|
26
|
+
end
|
27
|
+
|
28
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
29
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
30
|
+
def accept(visitor)
|
31
|
+
visitor.visit_for_stmt(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Accessor to the condition expression
|
35
|
+
# @return [LoxNode]
|
36
|
+
def condition
|
37
|
+
subnodes[0]
|
38
|
+
end
|
39
|
+
end # class
|
40
|
+
end # module
|
41
|
+
end # module
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
# rubocop: disable Style/AccessorGrouping
|
8
|
+
class LoxFunStmt < LoxCompoundExpr
|
9
|
+
attr_reader :name
|
10
|
+
attr_reader :params
|
11
|
+
attr_reader :body
|
12
|
+
|
13
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
14
|
+
# @param aName [String]
|
15
|
+
# @param arguments [Array<String>]
|
16
|
+
# @param body [Ast::LoxBlockStmt]
|
17
|
+
def initialize(aPosition, aName, paramList, aBody)
|
18
|
+
super(aPosition, [])
|
19
|
+
@name = aName
|
20
|
+
@params = paramList
|
21
|
+
@body = aBody
|
22
|
+
end
|
23
|
+
|
24
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
25
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
26
|
+
def accept(visitor)
|
27
|
+
visitor.visit_fun_stmt(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias operands subnodes
|
31
|
+
end # class
|
32
|
+
# rubocop: enable Style/AccessorGrouping
|
33
|
+
end # module
|
34
|
+
end # module
|
@@ -12,7 +12,9 @@ module Loxxy
|
|
12
12
|
attr_reader :else_stmt
|
13
13
|
|
14
14
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
|
-
# @param
|
15
|
+
# @param condExpr [Loxxy::Ast::LoxNode]
|
16
|
+
# @param thenStmt [Loxxy::Ast::LoxNode]
|
17
|
+
# @param elseStmt [Loxxy::Ast::LoxNode]
|
16
18
|
def initialize(aPosition, condExpr, thenStmt, elseStmt)
|
17
19
|
super(aPosition, [condExpr])
|
18
20
|
@then_stmt = thenStmt
|
@@ -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
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# Load all the classes implementing AST nodes
|
4
4
|
require_relative '../ast/all_lox_nodes'
|
5
|
+
require_relative 'function'
|
5
6
|
require_relative 'symbol_table'
|
6
7
|
|
7
8
|
module Loxxy
|
@@ -25,6 +26,8 @@ module Loxxy
|
|
25
26
|
@ostream = config.include?(:ostream) ? config[:ostream] : $stdout
|
26
27
|
@symbol_table = SymbolTable.new
|
27
28
|
@stack = []
|
29
|
+
|
30
|
+
init_globals
|
28
31
|
end
|
29
32
|
|
30
33
|
# Given an abstract syntax parse tree visitor, launch the visit
|
@@ -51,6 +54,23 @@ module Loxxy
|
|
51
54
|
symbol_table.insert(new_var)
|
52
55
|
end
|
53
56
|
|
57
|
+
def before_for_stmt(aForStmt)
|
58
|
+
before_block_stmt(aForStmt)
|
59
|
+
end
|
60
|
+
|
61
|
+
def after_for_stmt(aForStmt, aVisitor)
|
62
|
+
loop do
|
63
|
+
aForStmt.test_expr.accept(aVisitor)
|
64
|
+
condition = stack.pop
|
65
|
+
break unless condition.truthy?
|
66
|
+
|
67
|
+
aForStmt.body_stmt.accept(aVisitor)
|
68
|
+
aForStmt.update_expr&.accept(aVisitor)
|
69
|
+
stack.pop
|
70
|
+
end
|
71
|
+
after_block_stmt(aForStmt)
|
72
|
+
end
|
73
|
+
|
54
74
|
def after_if_stmt(anIfStmt, aVisitor)
|
55
75
|
# Retrieve the result of the condition evaluation
|
56
76
|
condition = stack.pop
|
@@ -66,6 +86,16 @@ module Loxxy
|
|
66
86
|
@ostream.print tos.to_str
|
67
87
|
end
|
68
88
|
|
89
|
+
def after_while_stmt(aWhileStmt, aVisitor)
|
90
|
+
loop do
|
91
|
+
condition = stack.pop
|
92
|
+
break unless condition.truthy?
|
93
|
+
|
94
|
+
aWhileStmt.body.accept(aVisitor)
|
95
|
+
aWhileStmt.condition.accept(aVisitor)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
69
99
|
def before_block_stmt(_aBlockStmt)
|
70
100
|
new_env = Environment.new
|
71
101
|
symbol_table.enter_environment(new_env)
|
@@ -140,6 +170,27 @@ module Loxxy
|
|
140
170
|
end
|
141
171
|
end
|
142
172
|
|
173
|
+
def after_call_expr(aCallExpr, aVisitor)
|
174
|
+
# Evaluate callee part
|
175
|
+
aCallExpr.callee.accept(aVisitor)
|
176
|
+
callee = stack.pop
|
177
|
+
aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
|
178
|
+
|
179
|
+
if callee.kind_of?(NativeFunction)
|
180
|
+
stack.push callee.call # Pass arguments
|
181
|
+
else
|
182
|
+
new_env = Environment.new(symbol_table.current_env)
|
183
|
+
symbol_table.enter_environment(new_env)
|
184
|
+
callee.parameters&.each do |param_name|
|
185
|
+
local = Variable.new(param_name, stack.pop)
|
186
|
+
symbol_table.insert(local)
|
187
|
+
end
|
188
|
+
stack.push callee.call(aVisitor)
|
189
|
+
|
190
|
+
symbol_table.leave_environment
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
143
194
|
def after_grouping_expr(_groupingExpr)
|
144
195
|
# Do nothing: work was already done by visiting /evaluating the subexpression
|
145
196
|
end
|
@@ -157,10 +208,50 @@ module Loxxy
|
|
157
208
|
stack.push(literalExpr.literal)
|
158
209
|
end
|
159
210
|
|
160
|
-
# @param
|
211
|
+
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
161
212
|
def before_visit_builtin(aValue)
|
162
213
|
stack.push(aValue)
|
163
214
|
end
|
215
|
+
|
216
|
+
def after_fun_stmt(aFunStmt, _visitor)
|
217
|
+
function = Function.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, stack)
|
218
|
+
new_var = Variable.new(aFunStmt.name, function)
|
219
|
+
symbol_table.insert(new_var)
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
NativeFunction = Struct.new(:callable, :interp) do
|
225
|
+
def accept(_visitor)
|
226
|
+
interp.stack.push self
|
227
|
+
end
|
228
|
+
|
229
|
+
def call
|
230
|
+
callable.call
|
231
|
+
end
|
232
|
+
|
233
|
+
def to_str
|
234
|
+
'<native fn>'
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def init_globals
|
239
|
+
add_native_fun('clock', native_clock)
|
240
|
+
end
|
241
|
+
|
242
|
+
def add_native_fun(aName, aProc)
|
243
|
+
native_fun = Variable.new(aName, NativeFunction.new(aProc, self))
|
244
|
+
symbol_table.insert(native_fun)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Ruby-native function that returns (as float) the number of seconds since
|
248
|
+
# a given time reference.
|
249
|
+
def native_clock
|
250
|
+
proc do
|
251
|
+
now = Time.now.to_f
|
252
|
+
Datatype::Number.new(now)
|
253
|
+
end
|
254
|
+
end
|
164
255
|
end # class
|
165
256
|
end # module
|
166
257
|
end # module
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../datatype/all_datatypes'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module BackEnd
|
7
|
+
# rubocop: disable Style/AccessorGrouping
|
8
|
+
# Representation of a Lox function.
|
9
|
+
# It is a named slot that can be associated with a value at the time.
|
10
|
+
class Function
|
11
|
+
# @return [String]
|
12
|
+
attr_reader :name
|
13
|
+
|
14
|
+
# @return [Array<>] the parameters
|
15
|
+
attr_reader :parameters
|
16
|
+
attr_reader :body
|
17
|
+
attr_reader :stack
|
18
|
+
|
19
|
+
# Create a variable with given name and initial value
|
20
|
+
# @param aName [String] The name of the variable
|
21
|
+
# @param aValue [Datatype::BuiltinDatatype] the initial assigned value
|
22
|
+
def initialize(aName, parameterList, aBody, aStack)
|
23
|
+
@name = aName.dup
|
24
|
+
@parameters = parameterList
|
25
|
+
@body = aBody
|
26
|
+
@stack = aStack
|
27
|
+
end
|
28
|
+
|
29
|
+
def accept(_visitor)
|
30
|
+
stack.push self
|
31
|
+
end
|
32
|
+
|
33
|
+
def call(aVisitor)
|
34
|
+
body.empty? ? Datatype::Nil.instance : body.accept(aVisitor)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Text representation of a Lox function
|
38
|
+
def to_str
|
39
|
+
"<fn #{name}>"
|
40
|
+
end
|
41
|
+
end # class
|
42
|
+
# rubocop: enable Style/AccessorGrouping
|
43
|
+
end # module
|
44
|
+
end # module
|
@@ -44,7 +44,7 @@ module Loxxy
|
|
44
44
|
rule('function_star' => 'function_star function')
|
45
45
|
rule('function_star' => [])
|
46
46
|
|
47
|
-
rule('funDecl' => 'FUN function')
|
47
|
+
rule('funDecl' => 'FUN function').as 'fun_decl'
|
48
48
|
|
49
49
|
rule('varDecl' => 'VAR IDENTIFIER SEMICOLON').as 'var_declaration'
|
50
50
|
rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
|
@@ -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,9 +75,9 @@ 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')
|
78
|
+
rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as 'while_stmt'
|
79
79
|
rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE').as 'block_stmt'
|
80
|
-
rule('block' => 'LEFT_BRACE RIGHT_BRACE')
|
80
|
+
rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
|
81
81
|
|
82
82
|
# Expressions: produce values
|
83
83
|
rule('expression_opt' => 'expression')
|
@@ -88,37 +88,37 @@ module Loxxy
|
|
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'
|
@@ -142,15 +142,15 @@ module Loxxy
|
|
142
142
|
rule('primary' => 'SUPER DOT IDENTIFIER')
|
143
143
|
|
144
144
|
# Utility rules
|
145
|
-
rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block')
|
145
|
+
rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
|
146
146
|
rule('params_opt' => 'parameters')
|
147
147
|
rule('params_opt' => [])
|
148
|
-
rule('parameters' => 'parameters COMMA IDENTIFIER')
|
149
|
-
rule('parameters' => 'IDENTIFIER')
|
148
|
+
rule('parameters' => 'parameters COMMA IDENTIFIER').as 'parameters_plus_more'
|
149
|
+
rule('parameters' => 'IDENTIFIER').as 'parameters_plus_end'
|
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)
|
@@ -11,7 +11,7 @@ module Loxxy
|
|
11
11
|
|
12
12
|
# Constructor.
|
13
13
|
# @param aValue [Datatype::BuiltinDatatype] the Lox data value
|
14
|
-
# @param
|
14
|
+
# @param aLexeme [String] the lexeme (= piece of text from input)
|
15
15
|
# @param aTerminal [Rley::Syntax::Terminal, String]
|
16
16
|
# The terminal symbol corresponding to the lexeme.
|
17
17
|
# @param aPosition [Rley::Lexical::Position] The position of lexeme
|
data/lib/loxxy/version.rb
CHANGED
@@ -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
@@ -9,6 +9,7 @@ require_relative '../lib/loxxy/interpreter'
|
|
9
9
|
module Loxxy
|
10
10
|
# This spec contains the bare bones test for the Interpreter class.
|
11
11
|
# The execution of Lox code is tested elsewhere.
|
12
|
+
# rubocop: disable Metrics/BlockLength
|
12
13
|
describe Interpreter do
|
13
14
|
let(:sample_cfg) do
|
14
15
|
{ ostream: StringIO.new }
|
@@ -329,10 +330,105 @@ LOX_END
|
|
329
330
|
expect(sample_cfg[:ostream].string).to eq('innerouter')
|
330
331
|
end
|
331
332
|
|
333
|
+
it 'should implement single statement while loops' do
|
334
|
+
program = <<-LOX_END
|
335
|
+
// Single-expression body.
|
336
|
+
var c = 0;
|
337
|
+
while (c < 3) print c = c + 1;
|
338
|
+
// output: 1
|
339
|
+
// output: 2
|
340
|
+
// output: 3
|
341
|
+
LOX_END
|
342
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
343
|
+
expect(sample_cfg[:ostream].string).to eq('123')
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'should implement block body while loops' do
|
347
|
+
program = <<-LOX_END
|
348
|
+
// Block body.
|
349
|
+
var a = 0;
|
350
|
+
while (a < 3) {
|
351
|
+
print a;
|
352
|
+
a = a + 1;
|
353
|
+
}
|
354
|
+
// output: 0
|
355
|
+
// output: 1
|
356
|
+
// output: 2
|
357
|
+
LOX_END
|
358
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
359
|
+
expect(sample_cfg[:ostream].string).to eq('012')
|
360
|
+
end
|
361
|
+
|
362
|
+
it 'should implement single statement for loops' do
|
363
|
+
program = <<-LOX_END
|
364
|
+
// Single-expression body.
|
365
|
+
for (var c = 0; c < 3;) print c = c + 1;
|
366
|
+
// output: 1
|
367
|
+
// output: 2
|
368
|
+
// output: 3
|
369
|
+
LOX_END
|
370
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
371
|
+
expect(sample_cfg[:ostream].string).to eq('123')
|
372
|
+
end
|
373
|
+
|
374
|
+
it 'should implement for loops with block body' do
|
375
|
+
program = <<-LOX_END
|
376
|
+
// Block body.
|
377
|
+
for (var a = 0; a < 3; a = a + 1) {
|
378
|
+
print a;
|
379
|
+
}
|
380
|
+
// output: 0
|
381
|
+
// output: 1
|
382
|
+
// output: 2
|
383
|
+
LOX_END
|
384
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
385
|
+
expect(sample_cfg[:ostream].string).to eq('012')
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'should implement nullary function calls' do
|
389
|
+
program = <<-LOX_END
|
390
|
+
print clock(); // Lox expects the 'clock' predefined native function
|
391
|
+
LOX_END
|
392
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
393
|
+
tick = sample_cfg[:ostream].string
|
394
|
+
expect(Time.now.to_f - tick.to_f).to be < 0.1
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'should implement function definition' do
|
398
|
+
program = <<-LOX_END
|
399
|
+
fun printSum(a, b) {
|
400
|
+
print a + b;
|
401
|
+
}
|
402
|
+
printSum(1, 2);
|
403
|
+
LOX_END
|
404
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
405
|
+
expect(sample_cfg[:ostream].string).to eq('3')
|
406
|
+
end
|
407
|
+
|
408
|
+
it 'should support functions with empty body' do
|
409
|
+
program = <<-LOX_END
|
410
|
+
fun f() {}
|
411
|
+
print f();
|
412
|
+
LOX_END
|
413
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
414
|
+
expect(sample_cfg[:ostream].string).to eq('nil')
|
415
|
+
end
|
416
|
+
|
417
|
+
it 'should provide print representation of functions' do
|
418
|
+
program = <<-LOX_END
|
419
|
+
fun foo() {}
|
420
|
+
print foo; // output: <fn foo>
|
421
|
+
print clock; // output: <native fn>
|
422
|
+
LOX_END
|
423
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
424
|
+
expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
|
425
|
+
end
|
426
|
+
|
332
427
|
it 'should print the hello world message' do
|
333
428
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
334
429
|
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|
335
430
|
end
|
336
431
|
end # context
|
337
432
|
end # describe
|
433
|
+
# rubocop: enable Metrics/BlockLength
|
338
434
|
end # module
|
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.
|
4
|
+
version: 0.1.01
|
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-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -90,7 +90,10 @@ files:
|
|
90
90
|
- lib/loxxy/ast/lox_assign_expr.rb
|
91
91
|
- lib/loxxy/ast/lox_binary_expr.rb
|
92
92
|
- lib/loxxy/ast/lox_block_stmt.rb
|
93
|
+
- lib/loxxy/ast/lox_call_expr.rb
|
93
94
|
- lib/loxxy/ast/lox_compound_expr.rb
|
95
|
+
- lib/loxxy/ast/lox_for_stmt.rb
|
96
|
+
- lib/loxxy/ast/lox_fun_stmt.rb
|
94
97
|
- lib/loxxy/ast/lox_grouping_expr.rb
|
95
98
|
- lib/loxxy/ast/lox_if_stmt.rb
|
96
99
|
- lib/loxxy/ast/lox_literal_expr.rb
|
@@ -102,9 +105,11 @@ files:
|
|
102
105
|
- lib/loxxy/ast/lox_unary_expr.rb
|
103
106
|
- lib/loxxy/ast/lox_var_stmt.rb
|
104
107
|
- lib/loxxy/ast/lox_variable_expr.rb
|
108
|
+
- lib/loxxy/ast/lox_while_stmt.rb
|
105
109
|
- lib/loxxy/back_end/engine.rb
|
106
110
|
- lib/loxxy/back_end/entry.rb
|
107
111
|
- lib/loxxy/back_end/environment.rb
|
112
|
+
- lib/loxxy/back_end/function.rb
|
108
113
|
- lib/loxxy/back_end/symbol_table.rb
|
109
114
|
- lib/loxxy/back_end/variable.rb
|
110
115
|
- lib/loxxy/datatype/all_datatypes.rb
|