loxxy 0.0.23 → 0.0.28

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