loxxy 0.3.03 → 0.4.03

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: 734fc040d2487c17abd8d89d4f3422e4d55db226ef79b354e52402fc24cd61fc
4
- data.tar.gz: e799c5d1044159e9bfdc1b337c370e95878c85752dcaaf60578e71c4763a7da1
3
+ metadata.gz: 5a500598bd4afc6d330a6e661bc9686b86de41c921fc7fea04ea0293dd97c057
4
+ data.tar.gz: 7e3f0335e8f9d7914d36db4e0e306ce053f9df18d5efce5cca1f43d22c866402
5
5
  SHA512:
6
- metadata.gz: d6d393729d977e6493979f4e89c664a1bba685b08c67a5e9e9b0d12354ac552deadae4e15a213560b752bdb9b44c7a05a5652fc8b4ed6500409fa04fe3cf29c1
7
- data.tar.gz: f3b121e4a5ca07b4297aca8c43a3325b00dd30fb131f412a17a5e82d7887e8fc8749a2e1a422c75fe0c1045652970a121a78883b06c6bbc6baa6d896b0fea03f
6
+ metadata.gz: 51affe131c074f29e887b83de9263cedf31394d9c9254877ddcf971d8e5d41f7035abb50b95af8f98c18a2d9cee06bfb3df7e6c9c97db24e31dbad7fb124c2f5
7
+ data.tar.gz: 6e16c93754aa0bbe32815b78b2362ade39e7823dafc677cb1c2694f3bdec8c07e59820487464634bde7ab08221da4f83381b38149bd68bfd18987d19565a71ab
data/.rubocop.yml CHANGED
@@ -50,6 +50,9 @@ Layout/IndentationConsistency:
50
50
  Layout/HeredocIndentation:
51
51
  Enabled: false
52
52
 
53
+ Layout/LineEndStringConcatenationIndentation:
54
+ Enabled: true
55
+
53
56
  Layout/MultilineHashBraceLayout:
54
57
  Enabled: true
55
58
 
@@ -81,6 +84,9 @@ Layout/TrailingWhitespace:
81
84
  Lint/AmbiguousAssignment:
82
85
  Enabled: true
83
86
 
87
+ Lint/AmbiguousRange:
88
+ Enabled: true
89
+
84
90
  Lint/DeprecatedConstants:
85
91
  Enabled: true
86
92
 
@@ -96,6 +102,9 @@ Lint/EmptyBlock:
96
102
  Lint/EmptyClass:
97
103
  Enabled: false
98
104
 
105
+ Lint/EmptyInPattern:
106
+ Enabled: true
107
+
99
108
  Lint/LambdaWithoutLiteralBlock:
100
109
  Enabled: true
101
110
 
@@ -192,6 +201,9 @@ Naming/ClassAndModuleCamelCase:
192
201
  Naming/BlockParameterName:
193
202
  Enabled: true
194
203
 
204
+ Naming/InclusiveLanguage:
205
+ Enabled: true
206
+
195
207
  Naming/MethodParameterName:
196
208
  Enabled: false
197
209
 
@@ -273,11 +285,17 @@ Style/HashTransformValues:
273
285
  Style/IfUnlessModifier:
274
286
  Enabled: false
275
287
 
288
+ Style/InPatternThen:
289
+ Enabled: true
290
+
276
291
  Style/InverseMethods:
277
292
  Enabled: false
278
293
 
279
294
  Style/MissingRespondToMissing:
280
295
  Enabled: false
296
+
297
+ Style/MultilineInPatternThen:
298
+ Enabled: true
281
299
 
282
300
  Style/NegatedIfElseCondition:
283
301
  Enabled: true
@@ -290,6 +308,9 @@ Style/NilLambda:
290
308
 
291
309
  Style/NumericLiterals:
292
310
  Enabled: false
311
+
312
+ Style/QuotedSymbols:
313
+ Enabled: true
293
314
 
294
315
  Style/RaiseArgs:
295
316
  Enabled: true
@@ -302,6 +323,9 @@ Style/RedundantReturn:
302
323
 
303
324
  Style/RedundantSelf:
304
325
  Enabled: true
326
+
327
+ Style/RedundantSelfAssignmentBranch:
328
+ Enabled: true
305
329
 
306
330
  Style/RegexpLiteral:
307
331
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,12 +1,44 @@
1
- ## [0.3.03] - 2021-05-23
2
- - Fixes in the location of an undefined variable. Rewrite of the scanning of lox string.
1
+ ## [0.4.02] - 2021-09-10
2
+ - Fixes in`AST::AstBuilder` class to cope with changes inRley 0.8.03
3
+
4
+ ### Changed
5
+ - File `loxxy.gemspec` forced dependency to Rley 0.8.03
6
+
7
+ ### Fixed
8
+ - Method `Ast::AstBuilder#reduce_class_naming` fixed access to `IDENTIFER` in (LESS IDENTIFER)?
9
+ - Method `Ast::AstBuilder#reduce_var_declaration` fixed access to `expression` in (EQUAL expression)?
10
+ - Method `Ast::AstBuilder#reduce_assign_expr`fixed access to `call` in (call DOT)?
11
+ - Method `Ast::AstBuilder#reduce_parameters`fixed access to `IDENTIFIER` in (COMMA IDENTIFIER)*
12
+ - Method `Ast::AstBuilder#reduce_arguments`fixed access to `expression` in (COMMA expression)*
13
+
14
+
15
+
16
+ ## [0.4.01] - 2021-08-22
17
+ - Grammar and AST::AstBuilder adapted to take profit of extended grammar notiation in Rley 0.8.01
18
+
19
+ ### Changed
20
+ - Class `Ast::ASTBuilder` removal of methods made redundant with new Rley version
21
+ - File `.rubocop.yml` added config for new cops
22
+ - File `grammar.rb` changed rules to use new extended rule syntax in Rley
23
+
24
+ ## [0.4.00] - 2021-05-24
25
+ - Version bump. `Loxxy` is capable to run the LoxLox interpreter, an interpreter written in `Lox`.
26
+
27
+ ### New
28
+ - Method `BackEnd::LoxInstance#falsey?` added
29
+ - Method `BackEnd::LoxInstance#truthy?` added
3
30
 
4
31
  ### Changed
5
32
  - Method `BackEnd::Engine#after_variable_expr` the error message `Undefined variable` nows gives the location of the offending variable.
6
- - Class `FrontEnd#Scanner` complete refactoring of String recognition.
33
+ - Class `Ast::LoxClassStmt`is now a subclass of `LoxNode`
34
+
35
+ - File `README.md` added an explanation on how to run `LoxLox`interpreter.
7
36
 
8
37
  ### Fixed
9
- - Method `Ast::AstBuilder#reduce_variable_expr` now associates the correct location of the variable.
38
+ - Method `Ast::LoxClassStmt#initialize` fixed inconsistencies in its Yard/RDoc documentation.
39
+ - Method `Ast::LoxFunStmt#initialize` fixed inconsistencies in its Yard/RDoc documentation.
40
+ - Method `BackEnd::Engine#native_getc` now returns -1 when its reaches EOF.
41
+ - Method `BackEnd::Resolver#after_logical_expr` was missing and this caused the lack of resultation in the second operand.
10
42
 
11
43
  ## [0.3.02] - 2021-05-22
12
44
  - New built-in expressions `getc`, `chr`, `exit` and `print_eeror` , fixes with deeply nested returns, set expressions
data/README.md CHANGED
@@ -19,7 +19,8 @@ Although __Lox__ is fairly simple, it is far from being a toy language:
19
19
  ### Loxxy gem features
20
20
  - Complete tree-walking interpreter including lexer, parser and resolver
21
21
  - 100% pure Ruby with clean design (not a port from some other language)
22
- - Passes the `jox` (THE reference `Lox` implementation) test suite
22
+ - Passes the `jox` (THE reference `Lox` implementation) test suite
23
+ - Can run a Lox imterpreter implemented in ... `Lox` [LoxLox](https://github.com/benhoyt/loxlox),
23
24
  - Minimal runtime dependency (Rley gem). Won't drag a bunch of gems...
24
25
  - Ruby API for integrating a Lox interpreter with your code.
25
26
  - A command-line interpreter `loxxy`
@@ -53,6 +54,7 @@ And then execute:
53
54
  ### 2. Your first `Lox` program
54
55
  Create a text file and enter the following lines:
55
56
  ```javascript
57
+ // hello.lox
56
58
  // Your firs Lox program
57
59
  print "Hello, world.";
58
60
  ```
@@ -75,6 +77,7 @@ Let's admit it, the hello world example was unimpressive.
75
77
  To a get a taste of `Lox` object-oriented capabilities, let's try another `Hello world` variant:
76
78
 
77
79
  ```javascript
80
+ // oo_hello.lox
78
81
  // Object-oriented hello world
79
82
  class Greeter {
80
83
  // in Lox, initializers/constructors are named `init`
@@ -98,6 +101,7 @@ Our next assignment: compute the first 20 elements of the Fibbonacci sequence.
98
101
  Here's an answer using the `while` loop construct:
99
102
 
100
103
  ```javascript
104
+ // fibbonacci.lox
101
105
  // Compute the first 20 elements from the Fibbonacci sequence
102
106
 
103
107
  var a = 0; // Use the var keyword to declare a new variable
@@ -127,6 +131,7 @@ Fans of `for` loops will be pleased to find their favorite looping construct.
127
131
  Here again, the Fibbonacci sequence refactored with a `for` loop:
128
132
 
129
133
  ```javascript
134
+ // fibbonacci_v2.lox
130
135
  // Fibbonacci sequence - version 2
131
136
  var a = 0;
132
137
  var b = 1;
@@ -169,13 +174,69 @@ for (var i = 0; i < count; i = i + 1) {
169
174
  }
170
175
  ```
171
176
 
177
+ ### Loxxy goes meta...
178
+ The `Loxxy` is able to run the `LoxLox` interpreter.
179
+ [LoxLox](https://github.com/benhoyt/loxlox) is a Lox interpreter written in Lox by Ben Hoyt.
180
+ This interpreter with over 1900 lines long is (one of) the longest Lox pragram.
181
+ As such, it is a good testbed for any Lox interpreter.
182
+
183
+ Executing a lox program with the LoxLox interpreter that is itself running on top of Loxxy.
184
+ #### Step 1 Download `lox.lox´ file
185
+ Download the [LoxLox](https://github.com/benhoyt/loxlox) source file in Github.
186
+
187
+ #### Step 2 (alternative a): running from the command line
188
+
189
+ ```
190
+ $ loxxy lox.lox
191
+ ```
192
+
193
+ Once loxxy CLI starts its interpreter that, in turn, executes the LoxLox interpreter.
194
+ This may take a couple of seconds.
195
+ Don't be surprised, if the program seems unresponsive: it is waiting for you input.
196
+ Enter a line like this:
197
+ ```
198
+ print "Hello, world!";
199
+ ```
200
+ Then terminate with an end of file (crtl-D on Linuxes, crtl-z on Windows) followed by an enter key.
201
+ You should see the famous greeting.
202
+
203
+ #### Step 2 (alternative b): launching the interpreter from Ruby snippet
204
+ The following snippet executes the LoxLox interpreter and feeds to it the
205
+ input text. That input text is made available through a StringIO that replaces
206
+ the `$stdio` device.
207
+
208
+ ```ruby
209
+ require 'stringio'
210
+ require 'loxxy'
211
+
212
+ # Place your Lox pragram within the heredoc
213
+ program = <<-LOX_END
214
+ print "Hello, world!";
215
+ LOX_END
216
+
217
+ lox_filename = 'lox.lox'
218
+ File.open(lox_filename, 'r') do |f|
219
+ source = f.read
220
+ cfg = { istream: StringIO.new(program, 'r')}
221
+ lox = Loxxy::Interpreter.new(cfg)
222
+ lox.evaluate(source)
223
+ end
224
+ ```
225
+
226
+ Save this snippet as a Ruby file, launch Ruby with this file in command line.
227
+ After a couple of seconds, you'll see the Ruby interpreter that executes the
228
+ Loxxy interpreter that itself executes the LoxLox interpreter written in Lox.
229
+ That last interpreter is the one that run the hello world line.
230
+
231
+ That's definitively meta...
232
+
172
233
  This completes our quick tour of `Lox`, to learn more about the language,
173
234
  check the online book [Crafting Interpreters](https://craftinginterpreters.com/ )
174
235
 
175
236
  ## What's the fuss about Lox?
176
237
  ... Nothing...
177
238
  Bob Nystrom designed a language __simple__ enough so that he could present
178
- two implementations (an interpreter, then a compiler) in one single book.
239
+ two interpreter implementations (a tree-walking one, then a bytecode one) in one single book.
179
240
 
180
241
  In other words, __Lox__ contains interesting features found in most general-purpose
181
242
  languages. In addition to that, there are [numerous implementations](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations) in different languages
@@ -202,14 +263,11 @@ There are already a number of programming languages derived from `Lox`...
202
263
  ### Purpose of this project:
203
264
  - To deliver an open source example of a programming language fully implemented in Ruby
204
265
  (from the scanner and parser to an interpreter).
205
- - The implementation should be mature enough to run [LoxLox](https://github.com/benhoyt/loxlox),
206
- a Lox interpreter written in Lox.
207
266
 
208
267
  ### Roadmap
209
268
  - Extend the test suite
210
269
  - Improve the error handling
211
270
  - Improve the documentation
212
- - Ability run the LoxLox interpreter
213
271
 
214
272
  ## Hello world example
215
273
  The next examples show how to use the interpreter directly from Ruby code.
@@ -143,63 +143,36 @@ module Loxxy
143
143
  # SEMANTIC ACTIONS
144
144
  #####################################
145
145
 
146
- # rule('program' => 'EOF').as 'null_program'
147
- def reduce_null_program(_production, _range, _tokens, _theChildren)
148
- Ast::LoxNoopExpr.new(tokens[0].position)
149
- end
150
-
151
- # rule('program' => 'declaration_plus EOF').as ''
146
+ # rule('program' => 'declaration+ EOF').as ''
152
147
  def reduce_lox_program(_production, _range, tokens, theChildren)
153
- LoxSeqDecl.new(tokens[0].position, theChildren[0])
154
- end
155
-
156
- # rule('declaration_plus' => 'declaration_plus declaration').as ''
157
- def reduce_declaration_plus_more(_production, _range, _tokens, theChildren)
158
- theChildren[0] << theChildren[1]
159
- end
160
-
161
- # rule('declaration_plus' => 'declaration')
162
- def reduce_declaration_plus_end(_production, _range, _tokens, theChildren)
163
- [theChildren[0]]
148
+ if theChildren[0].empty?
149
+ Ast::LoxNoopExpr.new(tokens[0].position)
150
+ else
151
+ LoxSeqDecl.new(tokens[0].position, theChildren[0])
152
+ end
164
153
  end
165
154
 
166
155
  # rule('classDecl' => 'CLASS classNaming class_body')
167
156
  def reduce_class_decl(_production, _range, _tokens, theChildren)
168
- if theChildren[1].kind_of?(Array)
169
- name = theChildren[1].first
170
- parent = theChildren[1].last
171
- else
172
- name = theChildren[1]
173
- parent = nil
174
- end
157
+ name = theChildren[1].first
158
+ parent = theChildren[1].last
175
159
  Ast::LoxClassStmt.new(tokens[1].position, name, parent, theChildren[2])
176
160
  end
177
161
 
178
- # rule('classNaming' => 'IDENTIFIER')
179
- def reduce_class_name(_production, _range, _tokens, theChildren)
180
- theChildren[0].token.lexeme
181
- end
182
-
183
- # rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
184
- def reduce_class_subclassing(_production, _range, _tokens, theChildren)
185
- super_token = theChildren[2].token
186
- super_var = LoxVariableExpr.new(super_token.position, super_token.lexeme)
162
+ # rule('classNaming' => 'IDENTIFIER (LESS IDENTIFIER)?')
163
+ def reduce_class_naming(_production, _range, _tokens, theChildren)
164
+ if theChildren[1].nil?
165
+ super_var = nil
166
+ else
167
+ super_token = theChildren[1].last.token
168
+ super_var = LoxVariableExpr.new(super_token.position, super_token.lexeme)
169
+ end
187
170
  [theChildren[0].token.lexeme, super_var]
188
171
  end
189
172
 
190
- # rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE')
173
+ # rule('class_body' => 'LEFT_BRACE function* RIGHT_BRACE')
191
174
  def reduce_class_body(_production, _range, _tokens, theChildren)
192
- theChildren[1].nil? ? [] : theChildren[1]
193
- end
194
-
195
- # rule('method_plus' => 'method_plus function')
196
- def reduce_method_plus_more(_production, _range, _tokens, theChildren)
197
- theChildren[0] << theChildren[1]
198
- end
199
-
200
- # rule('method_plus' => 'function')
201
- def reduce_method_plus_end(_production, _range, _tokens, theChildren)
202
- theChildren
175
+ theChildren[1]
203
176
  end
204
177
 
205
178
  # rule('funDecl' => 'FUN function')
@@ -212,16 +185,11 @@ module Loxxy
212
185
  return_first_child(range, tokens, theChildren) # Discard the semicolon
213
186
  end
214
187
 
215
- # rule('varDecl' => 'VAR IDENTIFIER SEMICOLON')
188
+ # rule('varDecl' => 'VAR IDENTIFIER (EQUAL expression)? SEMICOLON')
216
189
  def reduce_var_declaration(_production, _range, tokens, theChildren)
217
190
  var_name = theChildren[1].token.lexeme.dup
218
- Ast::LoxVarStmt.new(tokens[1].position, var_name, nil)
219
- end
220
-
221
- # rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON')
222
- def reduce_var_initialization(_production, _range, tokens, theChildren)
223
- var_name = theChildren[1].token.lexeme.dup
224
- Ast::LoxVarStmt.new(tokens[1].position, var_name, theChildren[3])
191
+ init_val = theChildren[2] ? theChildren[2].last : nil
192
+ Ast::LoxVarStmt.new(tokens[1].position, var_name, init_val)
225
193
  end
226
194
 
227
195
  # rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
@@ -248,7 +216,7 @@ module Loxxy
248
216
  for_stmt
249
217
  end
250
218
 
251
- # rule('forControl' => 'forInitialization forTest forUpdate')
219
+ # rule('forControl' => 'forInitialization forTest expression?')
252
220
  def reduce_for_control(_production, _range, tokens, theChildren)
253
221
  (init, test, update) = theChildren
254
222
  if test.nil? && update
@@ -265,7 +233,7 @@ module Loxxy
265
233
  nil
266
234
  end
267
235
 
268
- # rule('forTest' => 'expression_opt SEMICOLON')
236
+ # rule('forTest' => 'expression? SEMICOLON')
269
237
  def reduce_for_test(_production, range, tokens, theChildren)
270
238
  return_first_child(range, tokens, theChildren)
271
239
  end
@@ -292,7 +260,7 @@ module Loxxy
292
260
  Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
293
261
  end
294
262
 
295
- # rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
263
+ # rule('returnStmt' => 'RETURN expression? SEMICOLON')
296
264
  def reduce_return_stmt(_production, _range, tokens, theChildren)
297
265
  Ast::LoxReturnStmt.new(tokens[1].position, theChildren[1])
298
266
  end
@@ -302,34 +270,31 @@ module Loxxy
302
270
  Ast::LoxWhileStmt.new(tokens[1].position, theChildren[2], theChildren[4])
303
271
  end
304
272
 
305
- # rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
273
+ # rule('block' => 'LEFT_BRACE declaration* RIGHT_BRACE')
306
274
  def reduce_block_stmt(_production, _range, tokens, theChildren)
307
- decls = LoxSeqDecl.new(tokens[1].position, theChildren[1])
308
- Ast::LoxBlockStmt.new(tokens[1].position, decls)
309
- end
310
-
311
- # rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
312
- def reduce_block_empty(_production, _range, tokens, _children)
313
- Ast::LoxBlockStmt.new(tokens[0].position, nil)
275
+ decls = nil
276
+ if theChildren[1]
277
+ pos = tokens[1].position
278
+ decls = LoxSeqDecl.new(tokens[1].position, theChildren[1])
279
+ else
280
+ pos = tokens[0].position
281
+ end
282
+ Ast::LoxBlockStmt.new(pos, decls)
314
283
  end
315
284
 
316
- # rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
285
+ # rule('assignment' => '(call DOT)? IDENTIFIER EQUAL assignment')
317
286
  def reduce_assign_expr(_production, _range, tokens, theChildren)
318
287
  name_assignee = theChildren[1].token.lexeme.dup
319
- if theChildren[0].kind_of?(Ast::LoxSetExpr)
320
- theChildren[0].property = name_assignee
321
- theChildren[0].value = theChildren[3]
322
- theChildren[0]
288
+ if theChildren[0]
289
+ set_expr = Ast::LoxSetExpr.new(tokens[1].position, theChildren[0].first)
290
+ set_expr.property = name_assignee
291
+ set_expr.value = theChildren[3]
292
+ set_expr
323
293
  else
324
294
  Ast::LoxAssignExpr.new(tokens[1].position, name_assignee, theChildren[3])
325
295
  end
326
296
  end
327
297
 
328
- # rule('owner_opt' => 'call DOT')
329
- def reduce_set_expr(_production, _range, tokens, theChildren)
330
- Ast::LoxSetExpr.new(tokens[1].position, theChildren[0])
331
- end
332
-
333
298
 
334
299
  # rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
335
300
  # TODO: is it meaningful to implement this rule?
@@ -343,6 +308,7 @@ module Loxxy
343
308
 
344
309
  # rule('call' => 'primary refinement_plus').as 'call_expr'
345
310
  def reduce_call_expr(_production, _range, _tokens, theChildren)
311
+ # return theChildren[0] unless theChildren[1]
346
312
  members = theChildren.flatten
347
313
  call_expr = nil
348
314
  loop do
@@ -365,7 +331,7 @@ module Loxxy
365
331
  theChildren
366
332
  end
367
333
 
368
- # rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN')
334
+ # rule('refinement' => 'LEFT_PAREN arguments? RIGHT_PAREN')
369
335
  def reduce_call_arglist(_production, _range, tokens, theChildren)
370
336
  args = theChildren[1] || []
371
337
  if args.size > 255
@@ -422,24 +388,21 @@ module Loxxy
422
388
  LoxFunStmt.new(pos, first_child.token.lexeme, theChildren[2], theChildren[4])
423
389
  end
424
390
 
425
- # rule('parameters' => 'parameters COMMA IDENTIFIER')
426
- def reduce_parameters_plus_more(_production, _range, _tokens, theChildren)
427
- theChildren[0] << theChildren[2].token.lexeme
428
- end
391
+ # rule('parameters' => 'IDENTIFIER (COMMA IDENTIFIER)*').as 'parameters'
392
+ def reduce_parameters(_production, _range, _tokens, theChildren)
393
+ first_lexeme = theChildren[0].token.lexeme
394
+ return [first_lexeme] unless theChildren[1]
429
395
 
430
- # rule('parameters' => 'IDENTIFIER')
431
- def reduce_parameters_plus_end(_production, _range, _tokens, theChildren)
432
- [theChildren[0].token.lexeme]
396
+ successors = theChildren[1].map { |seq_node| seq_node.last.token.lexeme }
397
+ successors.unshift(first_lexeme)
433
398
  end
434
399
 
435
- # rule('arguments' => 'arguments COMMA expression')
436
- def reduce_arguments_plus_more(_production, _range, _tokens, theChildren)
437
- theChildren[0] << theChildren[2]
438
- end
400
+ # rule('arguments' => 'expression (COMMA expression)*')
401
+ def reduce_arguments(_production, _range, _tokens, theChildren)
402
+ return [theChildren[0]] unless theChildren[1]
439
403
 
440
- # rule('arguments' => 'expression')
441
- def reduce_arguments_plus_end(_production, _range, _tokens, theChildren)
442
- theChildren
404
+ successors = theChildren[1].map { |seq_node| seq_node.last }
405
+ successors.unshift(theChildren[0])
443
406
  end
444
407
  end # class
445
408
  end # module
@@ -71,7 +71,6 @@ module Loxxy
71
71
  # @param aClassStmt [AST::LOXClassStmt] the for statement node to visit
72
72
  def visit_class_stmt(aClassStmt)
73
73
  broadcast(:before_class_stmt, aClassStmt)
74
- traverse_subnodes(aClassStmt) # The methods are visited here...
75
74
  broadcast(:after_class_stmt, aClassStmt, self)
76
75
  end
77
76
 
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lox_compound_expr'
3
+ require_relative 'lox_node'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
- class LoxClassStmt < LoxCompoundExpr
7
+ # A parse tree node that represents a Lox class declaration.
8
+ class LoxClassStmt < LoxNode
8
9
  # @return [String] the class name
9
10
  attr_reader :name
10
11
 
@@ -14,11 +15,13 @@ module Loxxy
14
15
  # @return [Array<Ast::LoxFunStmt>] the methods
15
16
  attr_reader :body
16
17
 
18
+ # Constructor for a parse node that represents a Lox function declaration
17
19
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
18
- # @param condExpr [Loxxy::Ast::LoxNode] iteration condition
19
- # @param theBody [Array<Loxxy::Ast::LoxNode>]
20
+ # @param aName [String] the class name
21
+ # @param aSuperclassName [String] the super class name
22
+ # @param theMethods [Array<Loxxy::Ast::LoxFunStmt>] the methods
20
23
  def initialize(aPosition, aName, aSuperclassName, theMethods)
21
- super(aPosition, [])
24
+ super(aPosition)
22
25
  @name = aName.dup
23
26
  @superclass = aSuperclassName
24
27
  @body = theMethods
@@ -4,17 +4,25 @@ require_relative 'lox_node'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
- # rubocop: disable Style/AccessorGrouping
7
+ # A parse tree node that represents a Lox function declaration.
8
8
  class LoxFunStmt < LoxNode
9
+ # @return [String] the function name
9
10
  attr_reader :name
11
+
12
+ # @return [Array<String>] the parameter names
10
13
  attr_reader :params
14
+
15
+ # @return [Ast::LoxBlockStmt] the parse tree representing the function's body
11
16
  attr_reader :body
17
+
18
+ # @return [Boolean] true if the function is a method
12
19
  attr_accessor :is_method
13
20
 
21
+ # Constructor for a parse node that represents a Lox function declaration
14
22
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
- # @param aName [String]
16
- # @param arguments [Array<String>]
17
- # @param body [Ast::LoxBlockStmt]
23
+ # @param aName [String] the function name
24
+ # @param paramList [Array<String>] the parameter names
25
+ # @param aBody [Ast::LoxBlockStmt] the parse tree representing the function's body
18
26
  def initialize(aPosition, aName, paramList, aBody)
19
27
  super(aPosition)
20
28
  @name = aName.dup
@@ -25,6 +33,5 @@ module Loxxy
25
33
 
26
34
  define_accept # Add `accept` method as found in Visitor design pattern
27
35
  end # class
28
- # rubocop: enable Style/AccessorGrouping
29
36
  end # module
30
37
  end # module
@@ -11,7 +11,7 @@ module Loxxy
11
11
  # Let nodes take `visitee` role as defined in the Visitor design pattern
12
12
  extend ASTVisitee
13
13
 
14
- # return [Rley::Lexical::Position] Position of the entry in the input stream.
14
+ # @return [Rley::Lexical::Position] Position of the entry in the input stream.
15
15
  attr_reader :position
16
16
 
17
17
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
@@ -475,10 +475,12 @@ module Loxxy
475
475
  end
476
476
 
477
477
  # Read a single character and return the character code as an integer.
478
+ # LoxLox requires the end of input to be a negative number
478
479
  def native_getc
479
480
  proc do
480
481
  ch = @istream.getc
481
- Datatype::Number.new(ch.codepoints[0])
482
+ val = ch ? ch.codepoints[0] : -1
483
+ Datatype::Number.new(val)
482
484
  end
483
485
  end
484
486
 
@@ -22,8 +22,16 @@ module Loxxy
22
22
  @fields = {}
23
23
  end
24
24
 
25
- def accept(_visitor)
26
- engine.expr_stack.push self
25
+ # In Lox, only false and Nil have false value...
26
+ # @return [FalseClass]
27
+ def falsey?
28
+ false # Default implementation
29
+ end
30
+
31
+ # Any instance is truthy
32
+ # @return [TrueClass]
33
+ def truthy?
34
+ true # Default implementation
27
35
  end
28
36
 
29
37
  # Text representation of a Lox instance
@@ -31,6 +39,10 @@ module Loxxy
31
39
  "#{klass.to_str} instance"
32
40
  end
33
41
 
42
+ def accept(_visitor)
43
+ engine.expr_stack.push self
44
+ end
45
+
34
46
  # Look up the value of property with given name
35
47
  # aName [String] name of object property
36
48
  def get(aName)
@@ -138,6 +138,11 @@ module Loxxy
138
138
  aSetExpr.object.accept(aVisitor)
139
139
  end
140
140
 
141
+ def after_logical_expr(aLogicalExpr, aVisitor)
142
+ # Force the visit of second operand (resolver should ignore shortcuts)
143
+ aLogicalExpr.operands.last.accept(aVisitor)
144
+ end
145
+
141
146
  # Variable expressions require their variables resolved
142
147
  def before_variable_expr(aVarExpr)
143
148
  var_name = aVarExpr.name
@@ -7,9 +7,9 @@ module Loxxy
7
7
  module FrontEnd
8
8
  ########################################
9
9
  # Grammar for Lox language
10
- # Authoritave grammar at:
10
+ # Authoritative grammar at:
11
11
  # https://craftinginterpreters.com/appendix-i.html
12
- builder = Rley::Syntax::GrammarBuilder.new do
12
+ builder = Rley::grammar_builder do
13
13
  # Punctuators, separators...
14
14
  add_terminals('LEFT_PAREN', 'RIGHT_PAREN', 'LEFT_BRACE', 'RIGHT_BRACE')
15
15
  add_terminals('COMMA', 'DOT', 'MINUS', 'PLUS')
@@ -26,34 +26,25 @@ module Loxxy
26
26
  add_terminals('EOF')
27
27
 
28
28
  # Top-level rule that matches an entire Lox program
29
- rule('program' => 'EOF').as 'null_program'
30
- rule('program' => 'declaration_plus EOF').as 'lox_program'
29
+ rule('program' => 'declaration* EOF').as 'lox_program'
31
30
 
32
31
  # Declarations: bind an identifier to something
33
- rule('declaration_plus' => 'declaration_plus declaration').as 'declaration_plus_more'
34
- rule('declaration_plus' => 'declaration').as 'declaration_plus_end'
35
32
  rule('declaration' => 'classDecl')
36
33
  rule('declaration' => 'funDecl')
37
34
  rule('declaration' => 'varDecl')
38
35
  rule('declaration' => 'stmt')
39
36
 
40
37
  rule('classDecl' => 'CLASS classNaming class_body').as 'class_decl'
41
- rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER').as 'class_subclassing'
42
- rule('classNaming' => 'IDENTIFIER').as 'class_name'
43
- rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE').as 'class_body'
44
- rule('methods_opt' => 'method_plus')
45
- rule('methods_opt' => [])
46
- rule('method_plus' => 'method_plus function').as 'method_plus_more'
47
- rule('method_plus' => 'function').as 'method_plus_end'
38
+ rule('classNaming' => 'IDENTIFIER (LESS IDENTIFIER)?').as 'class_naming'
39
+ rule('class_body' => 'LEFT_BRACE function* RIGHT_BRACE').as 'class_body'
48
40
 
49
41
  rule('funDecl' => 'FUN function').as 'fun_decl'
50
42
 
51
- rule('varDecl' => 'VAR IDENTIFIER SEMICOLON').as 'var_declaration'
52
- rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
43
+ rule('varDecl' => 'VAR IDENTIFIER (EQUAL expression)? SEMICOLON').as 'var_declaration'
53
44
 
54
45
  # Statements: produce side effects, but don't introduce bindings
55
46
  rule('stmt' => 'statement')
56
- rule('stmt' => 'unbalancedStmt') # Tweak to cope with "dangling else" problem
47
+ # rule('stmt' => 'unbalancedStmt') # Tweak to cope with "dangling else" problem
57
48
  rule('statement' => 'exprStmt')
58
49
  rule('statement' => 'forStmt')
59
50
  rule('statement' => 'ifStmt')
@@ -65,32 +56,29 @@ module Loxxy
65
56
  rule('exprStmt' => 'expression SEMICOLON').as 'exprStmt'
66
57
 
67
58
  rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement').as 'for_stmt'
68
- rule('forControl' => 'forInitialization forTest forUpdate').as 'for_control'
59
+ rule('forControl' => 'forInitialization forTest expression?').as 'for_control'
69
60
  rule('forInitialization' => 'varDecl')
70
61
  rule('forInitialization' => 'exprStmt')
71
62
  rule('forInitialization' => 'SEMICOLON').as 'empty_for_initialization'
72
- rule('forTest' => 'expression_opt SEMICOLON').as 'for_test'
73
- rule('forUpdate' => 'expression_opt')
74
-
75
- rule('ifStmt' => 'IF ifCondition statement ELSE statement').as 'if_else_stmt'
76
- rule('unbalancedStmt' => 'IF ifCondition stmt').as 'if_stmt'
77
- rule('unbalancedStmt' => 'IF ifCondition statement ELSE unbalancedStmt').as 'if_else_stmt'
63
+ rule('forTest' => 'expression? SEMICOLON').as 'for_test'
64
+
65
+ rule('ifStmt' => 'IF ifCondition statement ELSE {match_closest: "IF"} statement').as 'if_else_stmt'
66
+ rule('ifStmt' => 'IF ifCondition stmt').as 'if_stmt'
67
+ # rule('unbalancedStmt' => 'IF ifCondition statement ELSE unbalancedStmt').as 'if_else_stmt'
68
+ # rule('ifStmt' => 'IF ifCondition statement ELSE statement').as 'if_else_stmt'
69
+ # rule('unbalancedStmt' => 'IF ifCondition stmt').as 'if_stmt'
70
+ # rule('unbalancedStmt' => 'IF ifCondition statement ELSE unbalancedStmt').as 'if_else_stmt'
78
71
  rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN').as 'keep_symbol2'
79
72
 
80
73
  rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
81
- rule('returnStmt' => 'RETURN expression_opt SEMICOLON').as 'return_stmt'
74
+ rule('returnStmt' => 'RETURN expression? SEMICOLON').as 'return_stmt'
82
75
  rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as 'while_stmt'
83
- rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE').as 'block_stmt'
84
- rule('block' => 'LEFT_BRACE RIGHT_BRACE').as 'block_empty'
76
+ rule('block' => 'LEFT_BRACE declaration* RIGHT_BRACE').as 'block_stmt'
85
77
 
86
78
  # Expressions: produce values
87
- rule('expression_opt' => 'expression')
88
- rule('expression_opt' => [])
89
79
  rule('expression' => 'assignment')
90
- rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
80
+ rule('assignment' => '(call DOT)? IDENTIFIER EQUAL assignment').as 'assign_expr'
91
81
  rule('assignment' => 'logic_or')
92
- rule('owner_opt' => 'call DOT').as 'set_expr'
93
- rule('owner_opt' => [])
94
82
  rule('logic_or' => 'logic_and')
95
83
  rule('logic_or' => 'logic_and disjunct_plus').as 'logical_expr'
96
84
  rule('disjunct_plus' => 'disjunct_plus OR logic_and').as 'binary_plus_more'
@@ -130,10 +118,8 @@ module Loxxy
130
118
  rule('unaryOp' => 'BANG')
131
119
  rule('unaryOp' => 'MINUS')
132
120
  rule('call' => 'primary')
133
- rule('call' => 'primary refinement_plus').as 'call_expr'
134
- rule('refinement_plus' => 'refinement_plus refinement').as 'refinement_plus_more'
135
- rule('refinement_plus' => 'refinement').as 'refinement_plus_end'
136
- rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN').as 'call_arglist'
121
+ rule('call' => 'primary refinement+').as 'call_expr'
122
+ rule('refinement' => 'LEFT_PAREN arguments? RIGHT_PAREN').as 'call_arglist'
137
123
  rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
138
124
  rule('primary' => 'TRUE').as 'literal_expr'
139
125
  rule('primary' => 'FALSE').as 'literal_expr'
@@ -146,15 +132,9 @@ module Loxxy
146
132
  rule('primary' => 'SUPER DOT IDENTIFIER').as 'super_expr'
147
133
 
148
134
  # Utility rules
149
- rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
150
- rule('params_opt' => 'parameters')
151
- rule('params_opt' => [])
152
- rule('parameters' => 'parameters COMMA IDENTIFIER').as 'parameters_plus_more'
153
- rule('parameters' => 'IDENTIFIER').as 'parameters_plus_end'
154
- rule('arguments_opt' => 'arguments')
155
- rule('arguments_opt' => [])
156
- rule('arguments' => 'arguments COMMA expression').as 'arguments_plus_more'
157
- rule('arguments' => 'expression').as 'arguments_plus_end'
135
+ rule('function' => 'IDENTIFIER LEFT_PAREN parameters? RIGHT_PAREN block').as 'function'
136
+ rule('parameters' => 'IDENTIFIER (COMMA IDENTIFIER)*').as 'parameters'
137
+ rule('arguments' => 'expression (COMMA expression)*').as 'arguments'
158
138
  end
159
139
 
160
140
  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.3.03'
4
+ VERSION = '0.4.03'
5
5
  end
data/loxxy.gemspec CHANGED
@@ -48,7 +48,7 @@ Gem::Specification.new do |spec|
48
48
  DESCR_END
49
49
  spec.homepage = 'https://github.com/famished-tiger/loxxy'
50
50
  spec.license = 'MIT'
51
- spec.required_ruby_version = '~> 2.4'
51
+ spec.required_ruby_version = '~> 2.5'
52
52
 
53
53
  spec.bindir = 'bin'
54
54
  spec.executables = ['loxxy']
@@ -58,7 +58,7 @@ Gem::Specification.new do |spec|
58
58
  PkgExtending.pkg_documentation(spec)
59
59
 
60
60
  # Runtime dependencies
61
- spec.add_dependency 'rley', '~> 0.7.06'
61
+ spec.add_dependency 'rley', '~> 0.8.03'
62
62
 
63
63
  # Development dependencies
64
64
  spec.add_development_dependency 'bundler', '~> 2.0'
@@ -26,7 +26,10 @@ module Loxxy
26
26
  # program => declaration_star EOF
27
27
  # where the declaration_star MUST be empty
28
28
  expect(aParseTree.root.symbol.name).to eq('program')
29
- eof = aParseTree.root.subnodes.first
29
+ (decls, eof) = aParseTree.root.subnodes
30
+ expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
31
+ expect(decls.symbol.name).to eq('declaration_star')
32
+ expect(decls.subnodes).to be_empty
30
33
  expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
31
34
  expect(eof.symbol.name).to eq('EOF')
32
35
  end
@@ -73,8 +76,12 @@ LOX_END
73
76
  expect(root.symbol.name).to eq('program')
74
77
  (decls, eof) = root.subnodes
75
78
  expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
76
- expect(decls.symbol.name).to eq('declaration_plus')
77
- stmt = decls.subnodes[0].subnodes[0]
79
+ expect(decls.symbol.name).to eq('declaration_star')
80
+ expect(decls.subnodes[0]).to be_kind_of(Rley::PTree::NonTerminalNode)
81
+ expect(decls.subnodes[0].symbol.name).to eq('declaration_star')
82
+ expect(decls.subnodes[1]).to be_kind_of(Rley::PTree::NonTerminalNode)
83
+ expect(decls.subnodes[1].symbol.name).to eq('declaration')
84
+ stmt = decls.subnodes[1].subnodes[0]
78
85
  expect(stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
79
86
  expect(stmt.symbol.name).to eq('stmt')
80
87
  statement = stmt.subnodes[0]
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rspec' # Use the RSpec framework
4
- require 'loxxy'
4
+ require_relative '../lib/loxxy'
5
5
 
6
6
  RSpec.configure do |config|
7
7
  # Enable flags like --only-failures and --next-failure
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.3.03
4
+ version: 0.4.03
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-23 00:00:00.000000000 Z
11
+ date: 2021-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.7.06
19
+ version: 0.8.03
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.7.06
26
+ version: 0.8.03
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -173,7 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
173
173
  requirements:
174
174
  - - "~>"
175
175
  - !ruby/object:Gem::Version
176
- version: '2.4'
176
+ version: '2.5'
177
177
  required_rubygems_version: !ruby/object:Gem::Requirement
178
178
  requirements:
179
179
  - - ">="