loxxy 0.0.26 → 0.1.02
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -1
- data/README.md +65 -10
- data/lib/loxxy/ast/all_lox_nodes.rb +3 -0
- data/lib/loxxy/ast/ast_builder.rb +79 -83
- data/lib/loxxy/ast/ast_visitor.rb +36 -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/back_end/engine.rb +83 -2
- data/lib/loxxy/back_end/function.rb +44 -0
- data/lib/loxxy/front_end/grammar.rb +32 -32
- 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 +67 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddfc01e822c7e68c87d515649f6ef6e2c800c926ca289dfe9f65edeff24e7015
|
4
|
+
data.tar.gz: db5a6a8052c0c15f00920c6d76fcd8c6f9cfe380905c20b53f8e74cc7c6d4f84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5a85f8a0a4a762f43a9dd51ab972f924a95ed15ed9e864cef0c60f4815902cbfb905fef624cc100bae9283985f8f6c7f960fbd72bd8d87e9ee3ab211eb7dc24
|
7
|
+
data.tar.gz: 10522ab655b99e31007dccaa479b4ec59a9340160f6768c084c3b96825f4e89f9f1c46c1655117763d66f28004f5d8e2c193315c80a65dd49b733587bd25823a
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,72 @@
|
|
1
|
+
## [0.1.02] - 2021-02-21
|
2
|
+
- Function definition and call documented in `README.md`
|
3
|
+
|
4
|
+
### Changed
|
5
|
+
- File `README.md` updated todescribe function definition and function call.
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
- Method `BackEnd::Engine#after_print_stmt` now handles of empty stack or nil data.
|
9
|
+
- Method `BackEnd::Engine#after_call_expr` was pushing one spurious item onto data stack.
|
10
|
+
|
11
|
+
## [0.1.01] - 2021-02-20
|
12
|
+
### Fixed
|
13
|
+
- Fixed most offences for Rubocop.
|
14
|
+
|
15
|
+
|
16
|
+
## [0.1.00] - 2021-02-20
|
17
|
+
- Version number bumped, `Loxxy` supports function definitions
|
18
|
+
|
19
|
+
### Added
|
20
|
+
- Class `Ast::LoxFunStmt` a node that represents a function declaration
|
21
|
+
- Method `Ast::ASTBuilder#reduce_fun_decl`
|
22
|
+
- Method `Ast::ASTBuilder#reduce_function` creates a `LoxFunStmt` instance
|
23
|
+
- Method `Ast::ASTBuilder#reduce_parameters_plus_more` for dealing with function parameters
|
24
|
+
- Method `Ast::ASTBuilder#reduce_parameters_plus_end`
|
25
|
+
- Method `Ast::ASTVisitor#visit_fun_stmt` for visiting an `Ast::LoxFunStmt` node
|
26
|
+
- Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
|
27
|
+
- Method `BackEnd::Engine#after_fun_stmt`
|
28
|
+
- Method `Backend::NativeFunction#call`
|
29
|
+
- Method `Backend::NativeFunction#to_str`
|
30
|
+
- Method `Backend::Function` implementation of a function object.
|
31
|
+
|
32
|
+
### Changed
|
33
|
+
- Method `BackEnd::Engine#after_call_expr`
|
34
|
+
|
35
|
+
### Fixed
|
36
|
+
- Fixed inconsistencies in documentation comments.
|
37
|
+
|
38
|
+
## [0.0.28] - 2021-02-15
|
39
|
+
- The interpreter implements function calls (to a native function).
|
40
|
+
|
41
|
+
### Added
|
42
|
+
- Class `Ast::LoxCallExpr` a node that represents a function call expression
|
43
|
+
- Method `Ast::ASTBuilder#reduce_call_expr`
|
44
|
+
- Method `Ast::ASTBuilder#reduce_refinement_plus_end`
|
45
|
+
- Method `Ast::ASTBuilder#reduce_call_arglist` creates a `LoxCallExpr` node
|
46
|
+
- Method `Ast::ASTBuilder#reduce_arguments_plus_more` builds the function argument array
|
47
|
+
- Method `Ast::ASTVisitor#visit_call_expr` for visiting an `Ast::LoxCallExpr` node
|
48
|
+
- Method `BackEnd::Engine#after_call_expr`implements the evaluation of a function call.
|
49
|
+
- Method `BackEnd::Engine#after_for_stmt` implements most of the `for` control flow
|
50
|
+
|
51
|
+
## [0.0.27] - 2021-01-24
|
52
|
+
- The interpreter implements `while` loops.
|
53
|
+
|
54
|
+
### Added
|
55
|
+
- Class `Ast::LoxForStmt` a node that represents a `for` statement
|
56
|
+
- Method `Ast::ASTBuilder#reduce_for_stmt`
|
57
|
+
- Method `Ast::ASTBuilder#reduce_for_control` creates an `Ast::LoxForStmt` node
|
58
|
+
- Method `Ast::ASTVisitor#visit_for_stmt` for visiting an `Ast::LoxWhileStmt` node
|
59
|
+
- Method `BackEnd::Engine#before_for_stmt` builds a new environment for the loop variable
|
60
|
+
- Method `BackEnd::Engine#after_for_stmt` implements most of the `for` control flow
|
61
|
+
|
62
|
+
### Changed
|
63
|
+
- File `README.md` updated.
|
64
|
+
|
1
65
|
## [0.0.26] - 2021-01-22
|
2
66
|
- The interpreter implements `while` loops.
|
3
67
|
|
4
68
|
### Added
|
5
|
-
- Class `Ast::LoxWhileStmt` a node that represents a while statement
|
69
|
+
- Class `Ast::LoxWhileStmt` a node that represents a `while` statement
|
6
70
|
- Method `Ast::ASTBuilder#reduce_while_stmt` creates an `Ast::LoxWhileStmt` node
|
7
71
|
- Method `Ast::ASTVisitor#visit_while_stmt` for visiting an `Ast::LoxWhileStmt` node
|
8
72
|
- Method `BackEnd::Engine#after_while_stmt` implements the while looping structure
|
data/README.md
CHANGED
@@ -14,9 +14,12 @@ 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 statements except:
|
18
|
+
- Closures,
|
19
|
+
- Classes and objects.
|
20
|
+
|
21
|
+
These will be implemented soon.
|
18
22
|
|
19
|
-
But the __loxxy__ gem hosts also a parser class `RawPaser` that can parse, in principle, any valid Lox input.
|
20
23
|
|
21
24
|
## What's the fuss about Lox?
|
22
25
|
... Nothing...
|
@@ -32,11 +35,11 @@ Although __Lox__ is fairly simple, it is far from a toy language:
|
|
32
35
|
- Functions and closures
|
33
36
|
- Object-orientation (classes, methods, inheritance).
|
34
37
|
|
35
|
-
In other words, __Lox__ contains interesting features
|
38
|
+
In other words, __Lox__ contains interesting features found in most general-purpose
|
36
39
|
languages.
|
37
40
|
|
38
41
|
### What's missing in Lox?
|
39
|
-
__Lox__ was constrained by design and therefore
|
42
|
+
__Lox__ was constrained by design and was therefore not aimed to be a language used in real-world applications.
|
40
43
|
Here are some missing parts to make it a _practical_ language:
|
41
44
|
- Collections (arrays, maps, ...)
|
42
45
|
- Modules (importing stuff from other packages/files)
|
@@ -66,6 +69,23 @@ lox = Loxxy::Interpreter.new
|
|
66
69
|
lox.evaluate(lox_program) # Output: Hello, world!
|
67
70
|
```
|
68
71
|
|
72
|
+
## A function definition example
|
73
|
+
```ruby
|
74
|
+
require 'loxxy'
|
75
|
+
|
76
|
+
lox_program = <<LOX_END
|
77
|
+
fun add4(n) {
|
78
|
+
n + 4;
|
79
|
+
}
|
80
|
+
|
81
|
+
print add4(6); // Output: 10
|
82
|
+
LOX_END
|
83
|
+
|
84
|
+
lox = Loxxy::Interpreter.new
|
85
|
+
lox.evaluate(lox_program) # Output 10
|
86
|
+
```
|
87
|
+
|
88
|
+
|
69
89
|
## Retrieving the result from a Lox program
|
70
90
|
The __Loxxy__ interpreter returns the value of the last evaluated expression.
|
71
91
|
|
@@ -141,11 +161,13 @@ Here are the language features currently supported by the interpreter:
|
|
141
161
|
- [Datatypes](#datatypes)
|
142
162
|
- [Statements](#statements)
|
143
163
|
-[Expressions](#expressions)
|
144
|
-
- [Variable declarations](#var-statement)
|
164
|
+
- [Variable declarations](#var-statement)
|
165
|
+
- [For statement](#for-statement)
|
145
166
|
- [If Statement](#if-statement)
|
146
167
|
- [Print Statement](#print-statement)
|
147
168
|
- [While Statement](#while-statement)
|
148
169
|
- [Block Statement](#block-statement)
|
170
|
+
- [Function declaration](#func-statement)
|
149
171
|
|
150
172
|
### Comments
|
151
173
|
|
@@ -172,19 +194,21 @@ loxxy supports all the standard __Lox__ datatypes:
|
|
172
194
|
### Statements
|
173
195
|
|
174
196
|
Loxxy supports the following statements:
|
175
|
-
- [Expressions](#expressions)
|
197
|
+
- [Expressions](#expressions)
|
176
198
|
-[Arithmetic expressions](#arithmetic-expressions)
|
177
199
|
-[String concatenation](#string-concatenation)
|
178
200
|
-[Comparison expressions](#comparison-expressions)
|
179
201
|
-[Logical expressions](#logical-expressions)
|
180
202
|
-[Grouping expressions](#grouping-expressions)
|
181
|
-
-[Variable expressions and assignments](#variable-expressions)
|
182
|
-
|
203
|
+
-[Variable expressions and assignments](#variable-expressions)
|
204
|
+
-[Function call](#function-call)
|
205
|
+
|
183
206
|
-[Variable declarations](#var-statement)
|
184
207
|
-[If Statement](#if-statement)
|
185
208
|
-[Print Statement](#print-statement)
|
186
|
-
-[While Statement](#while-statement)
|
187
|
-
-[Block Statement](#block-statement)
|
209
|
+
-[While Statement](#while-statement)
|
210
|
+
-[Block Statement](#block-statement)
|
211
|
+
-[Function Declaration](#function-declaration)
|
188
212
|
|
189
213
|
#### Expressions
|
190
214
|
|
@@ -279,6 +303,24 @@ var iAmNil; // __Lox__ initializes variables to nil by default;
|
|
279
303
|
print iAmNil; // output: nil
|
280
304
|
```
|
281
305
|
|
306
|
+
#### Function call
|
307
|
+
``` javascript
|
308
|
+
// Calling a function without argument
|
309
|
+
print clock();
|
310
|
+
|
311
|
+
// Assumption: there exists a function `add` that takes two arguments
|
312
|
+
print add(2, 3);
|
313
|
+
```
|
314
|
+
|
315
|
+
#### For statement
|
316
|
+
|
317
|
+
Similar to the `for` statement in `C` language
|
318
|
+
``` javascript
|
319
|
+
for (var a = 1; a < 10; a = a + 1) {
|
320
|
+
print a; // Output: 123456789
|
321
|
+
}
|
322
|
+
```
|
323
|
+
|
282
324
|
#### If statement
|
283
325
|
|
284
326
|
Based on a given condition, an if statement executes one of two statements:
|
@@ -348,6 +390,19 @@ var a = "outer";
|
|
348
390
|
print a; // output: outer
|
349
391
|
```
|
350
392
|
|
393
|
+
#### Function Declaration
|
394
|
+
The keyword `fun` is used to begin a function declaration.
|
395
|
+
In __Lox__ a function has a name and a body (which may be empty).
|
396
|
+
|
397
|
+
``` javascript
|
398
|
+
fun add4(n) // `add4` will be the name of the function
|
399
|
+
{
|
400
|
+
n + 4;
|
401
|
+
}
|
402
|
+
|
403
|
+
print add4(6); // output: 10
|
404
|
+
```
|
405
|
+
|
351
406
|
## Installation
|
352
407
|
|
353
408
|
Add this line to your application's Gemfile:
|
@@ -1,8 +1,10 @@
|
|
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'
|
@@ -12,5 +14,6 @@ require_relative 'lox_block_stmt'
|
|
12
14
|
require_relative 'lox_while_stmt'
|
13
15
|
require_relative 'lox_print_stmt'
|
14
16
|
require_relative 'lox_if_stmt'
|
17
|
+
require_relative 'lox_for_stmt'
|
15
18
|
require_relative 'lox_var_stmt'
|
16
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]
|
@@ -204,105 +232,46 @@ module Loxxy
|
|
204
232
|
Ast::LoxBlockStmt.new(tokens[1].position, decls)
|
205
233
|
end
|
206
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
|
+
|
207
240
|
# rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
|
208
241
|
def reduce_assign_expr(_production, _range, tokens, theChildren)
|
209
242
|
var_name = theChildren[1].token.lexeme.dup
|
210
243
|
Ast::LoxAssignExpr.new(tokens[1].position, var_name, theChildren[3])
|
211
244
|
end
|
212
245
|
|
213
|
-
# rule('logic_or' => 'logic_and disjunct_plus')
|
214
|
-
def reduce_logic_or_plus(production, range, tokens, theChildren)
|
215
|
-
reduce_logical_expr(production, range, tokens, theChildren)
|
216
|
-
end
|
217
|
-
|
218
|
-
# rule('disjunct_plus' => 'disjunct_plus OR logic_and')
|
219
|
-
def reduce_logic_or_plus_more(production, range, tokens, theChildren)
|
220
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
221
|
-
end
|
222
|
-
|
223
|
-
# rule('disjunct_plus' => 'OR logic_and')
|
224
|
-
def reduce_logic_or_plus_end(production, range, tokens, theChildren)
|
225
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
226
|
-
end
|
227
|
-
|
228
|
-
# rule('logic_and' => 'equality conjunct_plus')
|
229
|
-
def reduce_logic_and_plus(production, range, tokens, theChildren)
|
230
|
-
reduce_logical_expr(production, range, tokens, theChildren)
|
231
|
-
end
|
232
|
-
|
233
|
-
# rule('conjunct_plus' => 'conjunct_plus AND equality')
|
234
|
-
def reduce_logic_and_plus_more(production, range, tokens, theChildren)
|
235
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
236
|
-
end
|
237
|
-
|
238
|
-
# rule('conjunct_plus' => 'AND equality')
|
239
|
-
def reduce_logic_and_plus_end(production, range, tokens, theChildren)
|
240
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
241
|
-
end
|
242
|
-
|
243
|
-
# rule('equality' => 'comparison equalityTest_plus')
|
244
|
-
def reduce_equality_plus(production, range, tokens, theChildren)
|
245
|
-
reduce_binary_operator(production, range, tokens, theChildren)
|
246
|
-
end
|
247
|
-
|
248
|
-
# rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison')
|
249
|
-
def reduce_equality_t_plus_more(production, range, tokens, theChildren)
|
250
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
251
|
-
end
|
252
|
-
|
253
|
-
# rule('equalityTest_star' => 'equalityTest comparison')
|
254
|
-
def reduce_equality_t_plus_end(production, range, tokens, theChildren)
|
255
|
-
reduce_binary_plus_end(production, range, tokens, theChildren)
|
256
|
-
end
|
257
|
-
|
258
|
-
# rule('comparison' => 'term comparisonTest_plus')
|
259
|
-
def reduce_comparison_plus(production, range, tokens, theChildren)
|
260
|
-
reduce_binary_operator(production, range, tokens, theChildren)
|
261
|
-
end
|
262
|
-
|
263
246
|
# rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
|
264
247
|
# TODO: is it meaningful to implement this rule?
|
265
248
|
|
266
|
-
# rule('
|
267
|
-
def
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
# rule('term' => 'factor additive_plus')
|
272
|
-
def reduce_term_additive(production, range, tokens, theChildren)
|
273
|
-
reduce_binary_operator(production, range, tokens, theChildren)
|
274
|
-
end
|
275
|
-
|
276
|
-
# rule('additive_star' => 'additive_star additionOp factor').as 'additionOp_expr'
|
277
|
-
def reduce_additive_plus_more(production, range, tokens, theChildren)
|
278
|
-
reduce_binary_plus_more(production, range, tokens, theChildren)
|
279
|
-
end
|
280
|
-
|
281
|
-
# rule('additive_plus' => 'additionOp factor')
|
282
|
-
def reduce_additive_plus_end(production, range, tokens, theChildren)
|
283
|
-
reduce_binary_plus_end(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)
|
284
254
|
end
|
285
255
|
|
286
|
-
# rule('
|
287
|
-
def
|
288
|
-
|
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]
|
289
260
|
end
|
290
261
|
|
291
|
-
# rule('
|
292
|
-
def
|
293
|
-
|
262
|
+
# rule('refinement_plus' => 'refinement').
|
263
|
+
def reduce_refinement_plus_end(_production, _range, _tokens, theChildren)
|
264
|
+
theChildren[0]
|
294
265
|
end
|
295
266
|
|
296
|
-
# rule('
|
297
|
-
def
|
298
|
-
|
299
|
-
|
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
|
300
273
|
|
301
|
-
|
302
|
-
def reduce_unary_expr(_production, _range, tokens, theChildren)
|
303
|
-
operator = Name2unary[theChildren[0].symbol.name].to_sym
|
304
|
-
operand = theChildren[1]
|
305
|
-
LoxUnaryExpr.new(tokens[0].position, operator, operand)
|
274
|
+
LoxCallExpr.new(tokens[0].position, args)
|
306
275
|
end
|
307
276
|
|
308
277
|
# rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
|
@@ -324,6 +293,33 @@ module Loxxy
|
|
324
293
|
var_name = theChildren[0].token.lexeme
|
325
294
|
LoxVariableExpr.new(tokens[0].position, var_name)
|
326
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
|
327
323
|
end # class
|
328
324
|
end # module
|
329
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)
|
@@ -95,12 +103,12 @@ module Loxxy
|
|
95
103
|
# @param aBlockStmt [AST::LOXBlockStmt] the print statement node to visit
|
96
104
|
def visit_block_stmt(aBlockStmt)
|
97
105
|
broadcast(:before_block_stmt, aBlockStmt)
|
98
|
-
traverse_subnodes(aBlockStmt)
|
106
|
+
traverse_subnodes(aBlockStmt) unless aBlockStmt.empty?
|
99
107
|
broadcast(:after_block_stmt, aBlockStmt)
|
100
108
|
end
|
101
109
|
|
102
110
|
# Visit event. The visitor is visiting an assignment node
|
103
|
-
# @param
|
111
|
+
# @param anAssignExpr [AST::LoxAssignExpr] the variable assignment node to visit.
|
104
112
|
def visit_assign_expr(anAssignExpr)
|
105
113
|
broadcast(:before_assign_expr, anAssignExpr)
|
106
114
|
traverse_subnodes(anAssignExpr)
|
@@ -110,7 +118,7 @@ module Loxxy
|
|
110
118
|
# Visit event. The visitor is about to visit a logical expression.
|
111
119
|
# Since logical expressions may take shorcuts by not evaluating all their
|
112
120
|
# sub-expressiosns, they are responsible for visiting or not their children.
|
113
|
-
# @param
|
121
|
+
# @param aLogicalExpr [AST::LOXLogicalExpr] the logical expression node to visit
|
114
122
|
def visit_logical_expr(aLogicalExpr)
|
115
123
|
broadcast(:before_logical_expr, aLogicalExpr)
|
116
124
|
|
@@ -137,6 +145,14 @@ module Loxxy
|
|
137
145
|
broadcast(:after_unary_expr, anUnaryExpr)
|
138
146
|
end
|
139
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
|
+
|
140
156
|
# Visit event. The visitor is about to visit a grouping expression.
|
141
157
|
# @param aGroupingExpr [AST::LoxGroupingExpr] grouping expression to visit
|
142
158
|
def visit_grouping_expr(aGroupingExpr)
|
@@ -154,25 +170,35 @@ module Loxxy
|
|
154
170
|
end
|
155
171
|
|
156
172
|
# Visit event. The visitor is visiting a variable usage node
|
157
|
-
# @param
|
173
|
+
# @param aVariableExpr [AST::LoxVariableExpr] the variable reference node to visit.
|
158
174
|
def visit_variable_expr(aVariableExpr)
|
159
175
|
broadcast(:before_variable_expr, aVariableExpr)
|
160
176
|
broadcast(:after_variable_expr, aVariableExpr, self)
|
161
177
|
end
|
162
178
|
|
163
179
|
# Visit event. The visitor is about to visit the given terminal datatype value.
|
164
|
-
# @param
|
180
|
+
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
165
181
|
def visit_builtin(aValue)
|
166
182
|
broadcast(:before_visit_builtin, aValue)
|
167
183
|
broadcast(:after_visit_builtin, aValue)
|
168
184
|
end
|
169
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
|
+
|
170
194
|
# Visit event. The visitor is about to visit the given non terminal node.
|
171
195
|
# @param aNonTerminalNode [Rley::PTree::NonTerminalNode] the node to visit.
|
172
|
-
def visit_nonterminal(
|
196
|
+
def visit_nonterminal(aNonTerminalNode)
|
173
197
|
# Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
|
174
198
|
# that it cannot handle.
|
175
|
-
|
199
|
+
symb = aNonTerminalNode.symbol.name
|
200
|
+
msg = "Loxxy cannot execute this code yet for non-terminal symbol '#{symb}'."
|
201
|
+
raise NotImplementedError, msg
|
176
202
|
end
|
177
203
|
|
178
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
|
@@ -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
|
@@ -63,7 +83,7 @@ module Loxxy
|
|
63
83
|
|
64
84
|
def after_print_stmt(_printStmt)
|
65
85
|
tos = stack.pop
|
66
|
-
@ostream.print tos.to_str
|
86
|
+
@ostream.print tos ? tos.to_str : 'nil'
|
67
87
|
end
|
68
88
|
|
69
89
|
def after_while_stmt(aWhileStmt, aVisitor)
|
@@ -150,6 +170,27 @@ module Loxxy
|
|
150
170
|
end
|
151
171
|
end
|
152
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
|
+
callee.call(aVisitor)
|
189
|
+
|
190
|
+
symbol_table.leave_environment
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
153
194
|
def after_grouping_expr(_groupingExpr)
|
154
195
|
# Do nothing: work was already done by visiting /evaluating the subexpression
|
155
196
|
end
|
@@ -167,10 +208,50 @@ module Loxxy
|
|
167
208
|
stack.push(literalExpr.literal)
|
168
209
|
end
|
169
210
|
|
170
|
-
# @param
|
211
|
+
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
171
212
|
def before_visit_builtin(aValue)
|
172
213
|
stack.push(aValue)
|
173
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
|
174
255
|
end # class
|
175
256
|
end # module
|
176
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'
|
@@ -77,7 +77,7 @@ module Loxxy
|
|
77
77
|
rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
|
78
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 }
|
@@ -358,10 +359,76 @@ LOX_END
|
|
358
359
|
expect(sample_cfg[:ostream].string).to eq('012')
|
359
360
|
end
|
360
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
|
+
|
361
427
|
it 'should print the hello world message' do
|
362
428
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
363
429
|
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|
364
430
|
end
|
365
431
|
end # context
|
366
432
|
end # describe
|
433
|
+
# rubocop: enable Metrics/BlockLength
|
367
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.02
|
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-21 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
|
@@ -106,6 +109,7 @@ files:
|
|
106
109
|
- lib/loxxy/back_end/engine.rb
|
107
110
|
- lib/loxxy/back_end/entry.rb
|
108
111
|
- lib/loxxy/back_end/environment.rb
|
112
|
+
- lib/loxxy/back_end/function.rb
|
109
113
|
- lib/loxxy/back_end/symbol_table.rb
|
110
114
|
- lib/loxxy/back_end/variable.rb
|
111
115
|
- lib/loxxy/datatype/all_datatypes.rb
|