loxxy 0.3.03 → 0.4.03

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="