loxxy 0.0.25 → 0.1.01

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