loxxy 0.3.02 → 0.4.02

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: a0e398714d64763c288f06c315611c86bf3dae2e3403055ae3ca240a10bd292f
4
- data.tar.gz: 8db95e837824181e1794277dc3e67e4005432006877124bbd62ffd07def57591
3
+ metadata.gz: e5a4313b7377778f23e130f599cb4d28d43fd4ff3d56ec2c93c9b9636ac04743
4
+ data.tar.gz: 17f543465f55d0e83e2145973522da4230d7162d72f81d54ea957ac1522be412
5
5
  SHA512:
6
- metadata.gz: caf2aab9a8c03997fbde67467b7cb66a786c2fb8ef46fc24869c42ac7da76831f5e771a282c938ec83585666de801602ffc9818fc489454ec93dec9bd76bf5a7
7
- data.tar.gz: 9d98db7d9bddec915929dc1cbaed057248ccb5e0cc63efc3d0d7e99536ea9b330925d91d6b1ffd6805194dc3805b0025228f66d11f11b5455cb8f52d7753e6eb
6
+ metadata.gz: 18ab688d47ee5d2b98ae1be44bba06740f9299d58b22cbe37b477d4a27624a0b7e3b0a761cde422993a8270d15df3c6113ee50876fb16b5d6a1b2d6e13ed4719
7
+ data.tar.gz: 5353f0cb4d853a15a3f5f156e19176d8c3e3a9e64bd186d53a9caec78edb4393e96b2be339634907bf2871a5a974f409d224b9de11a9940e24615fd2b55fb825
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,3 +1,45 @@
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
30
+
31
+ ### Changed
32
+ - Method `BackEnd::Engine#after_variable_expr` the error message `Undefined variable` nows gives the location of the offending variable.
33
+ - Class `Ast::LoxClassStmt`is now a subclass of `LoxNode`
34
+
35
+ - File `README.md` added an explanation on how to run `LoxLox`interpreter.
36
+
37
+ ### Fixed
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.
42
+
1
43
  ## [0.3.02] - 2021-05-22
2
44
  - New built-in expressions `getc`, `chr`, `exit` and `print_eeror` , fixes with deeply nested returns, set expressions
3
45
 
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
@@ -395,9 +361,10 @@ module Loxxy
395
361
  end
396
362
 
397
363
  # rule('primary' => 'IDENTIFIER')
398
- def reduce_variable_expr(_production, _range, tokens, theChildren)
364
+ def reduce_variable_expr(_production, _range, _tokens, theChildren)
399
365
  var_name = theChildren[0].token.lexeme
400
- LoxVariableExpr.new(tokens[0].position, var_name)
366
+ pos = theChildren[0].token.position
367
+ LoxVariableExpr.new(pos, var_name)
401
368
  end
402
369
 
403
370
  # rule('primary' => 'THIS')
@@ -421,24 +388,21 @@ module Loxxy
421
388
  LoxFunStmt.new(pos, first_child.token.lexeme, theChildren[2], theChildren[4])
422
389
  end
423
390
 
424
- # rule('parameters' => 'parameters COMMA IDENTIFIER')
425
- def reduce_parameters_plus_more(_production, _range, _tokens, theChildren)
426
- theChildren[0] << theChildren[2].token.lexeme
427
- 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]
428
395
 
429
- # rule('parameters' => 'IDENTIFIER')
430
- def reduce_parameters_plus_end(_production, _range, _tokens, theChildren)
431
- [theChildren[0].token.lexeme]
396
+ successors = theChildren[1].map { |seq_node| seq_node.last.token.lexeme }
397
+ successors.unshift(first_lexeme)
432
398
  end
433
399
 
434
- # rule('arguments' => 'arguments COMMA expression')
435
- def reduce_arguments_plus_more(_production, _range, _tokens, theChildren)
436
- theChildren[0] << theChildren[2]
437
- end
400
+ # rule('arguments' => 'expression (COMMA expression)*')
401
+ def reduce_arguments(_production, _range, _tokens, theChildren)
402
+ return [theChildren[0]] unless theChildren[1]
438
403
 
439
- # rule('arguments' => 'expression')
440
- def reduce_arguments_plus_end(_production, _range, _tokens, theChildren)
441
- theChildren
404
+ successors = theChildren[1].map { |seq_node| seq_node.last }
405
+ successors.unshift(theChildren[0])
442
406
  end
443
407
  end # class
444
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.
@@ -326,7 +326,10 @@ module Loxxy
326
326
  def after_variable_expr(aVarExpr, aVisitor)
327
327
  var_name = aVarExpr.name
328
328
  var = variable_lookup(aVarExpr)
329
- raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless var
329
+ unless var
330
+ pos = "line #{aVarExpr.position.line}:#{aVarExpr.position.column}"
331
+ raise Loxxy::RuntimeError, "[#{pos}] Undefined variable '#{var_name}'."
332
+ end
330
333
 
331
334
  var.value.accept(aVisitor) # Evaluate variable value then push on stack
332
335
  end
@@ -472,10 +475,12 @@ module Loxxy
472
475
  end
473
476
 
474
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
475
479
  def native_getc
476
480
  proc do
477
481
  ch = @istream.getc
478
- Datatype::Number.new(ch.codepoints[0])
482
+ val = ch ? ch.codepoints[0] : -1
483
+ Datatype::Number.new(val)
479
484
  end
480
485
  end
481
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
@@ -47,11 +47,10 @@ module Loxxy
47
47
 
48
48
  def validated_value(aValue)
49
49
  unless aValue.is_a?(String)
50
- raise StandardError, "Invalid number value #{aValue}"
50
+ raise StandardError, "Invalid string value #{aValue}"
51
51
  end
52
52
 
53
- # Remove double quotes delimiter
54
- aValue.gsub(/(^")|("$)/, '')
53
+ aValue
55
54
  end
56
55
  end # class
57
56
  end # module
@@ -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,30 +26,21 @@ 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')
@@ -65,12 +56,11 @@ 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')
63
+ rule('forTest' => 'expression? SEMICOLON').as 'for_test'
74
64
 
75
65
  rule('ifStmt' => 'IF ifCondition statement ELSE statement').as 'if_else_stmt'
76
66
  rule('unbalancedStmt' => 'IF ifCondition stmt').as 'if_stmt'
@@ -78,19 +68,14 @@ module Loxxy
78
68
  rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN').as 'keep_symbol2'
79
69
 
80
70
  rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
81
- rule('returnStmt' => 'RETURN expression_opt SEMICOLON').as 'return_stmt'
71
+ rule('returnStmt' => 'RETURN expression? SEMICOLON').as 'return_stmt'
82
72
  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'
73
+ rule('block' => 'LEFT_BRACE declaration* RIGHT_BRACE').as 'block_stmt'
85
74
 
86
75
  # Expressions: produce values
87
- rule('expression_opt' => 'expression')
88
- rule('expression_opt' => [])
89
76
  rule('expression' => 'assignment')
90
- rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
77
+ rule('assignment' => '(call DOT)? IDENTIFIER EQUAL assignment').as 'assign_expr'
91
78
  rule('assignment' => 'logic_or')
92
- rule('owner_opt' => 'call DOT').as 'set_expr'
93
- rule('owner_opt' => [])
94
79
  rule('logic_or' => 'logic_and')
95
80
  rule('logic_or' => 'logic_and disjunct_plus').as 'logical_expr'
96
81
  rule('disjunct_plus' => 'disjunct_plus OR logic_and').as 'binary_plus_more'
@@ -130,10 +115,8 @@ module Loxxy
130
115
  rule('unaryOp' => 'BANG')
131
116
  rule('unaryOp' => 'MINUS')
132
117
  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'
118
+ rule('call' => 'primary refinement+').as 'call_expr'
119
+ rule('refinement' => 'LEFT_PAREN arguments? RIGHT_PAREN').as 'call_arglist'
137
120
  rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
138
121
  rule('primary' => 'TRUE').as 'literal_expr'
139
122
  rule('primary' => 'FALSE').as 'literal_expr'
@@ -146,15 +129,9 @@ module Loxxy
146
129
  rule('primary' => 'SUPER DOT IDENTIFIER').as 'super_expr'
147
130
 
148
131
  # 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'
132
+ rule('function' => 'IDENTIFIER LEFT_PAREN parameters? RIGHT_PAREN block').as 'function'
133
+ rule('parameters' => 'IDENTIFIER (COMMA IDENTIFIER)*').as 'parameters'
134
+ rule('arguments' => 'expression (COMMA expression)*').as 'arguments'
158
135
  end
159
136
 
160
137
  unless defined?(Grammar)
@@ -61,6 +61,20 @@ module Loxxy
61
61
  print return super this true var while
62
62
  ].map { |x| [x, x] }.to_h
63
63
 
64
+ # Single character that have a special meaning when escaped
65
+ # @return [{Char => String}]
66
+ @@escape_chars = {
67
+ ?a => "\a",
68
+ ?b => "\b",
69
+ ?e => "\e",
70
+ ?f => "\f",
71
+ ?n => "\n",
72
+ ?r => "\r",
73
+ ?s => "\s",
74
+ ?t => "\t",
75
+ ?v => "\v"
76
+ }.freeze
77
+
64
78
  # Constructor. Initialize a tokenizer for Lox input.
65
79
  # @param source [String] Lox text to tokenize.
66
80
  def initialize(source = nil)
@@ -104,18 +118,14 @@ module Loxxy
104
118
  elsif (lexeme = scanner.scan(/[!=><]=?/))
105
119
  # One or two special character tokens
106
120
  token = build_token(@@lexeme2name[lexeme], lexeme)
121
+ elsif scanner.scan(/"/) # Start of string detected...
122
+ token = build_string_token
107
123
  elsif (lexeme = scanner.scan(/\d+(?:\.\d+)?/))
108
124
  token = build_token('NUMBER', lexeme)
109
- elsif (lexeme = scanner.scan(/"(?:\\"|[^"])*"/))
110
- token = build_token('STRING', lexeme)
111
125
  elsif (lexeme = scanner.scan(/[a-zA-Z_][a-zA-Z_0-9]*/))
112
126
  keyw = @@keywords[lexeme]
113
127
  tok_type = keyw ? keyw.upcase : 'IDENTIFIER'
114
128
  token = build_token(tok_type, lexeme)
115
- elsif scanner.scan(/"(?:\\"|[^"])*\z/)
116
- # Error: unterminated string...
117
- col = scanner.pos - @line_start + 1
118
- raise ScanError, "Error: [line #{lineno}:#{col}]: Unterminated string."
119
129
  else # Unknown token
120
130
  col = scanner.pos - @line_start + 1
121
131
  _erroneous = curr_ch.nil? ? '' : scanner.scan(/./)
@@ -153,8 +163,6 @@ module Loxxy
153
163
  value = Datatype::Nil.instance
154
164
  when 'NUMBER'
155
165
  value = Datatype::Number.new(aLexeme)
156
- when 'STRING'
157
- value = Datatype::LXString.new(unescape_string(aLexeme))
158
166
  when 'TRUE'
159
167
  value = Datatype::True.instance
160
168
  else
@@ -164,27 +172,47 @@ module Loxxy
164
172
  return [value, symb]
165
173
  end
166
174
 
167
- # Replace any sequence sequence by their "real" value.
168
- def unescape_string(aText)
169
- result = +''
170
- previous = nil
171
-
172
- aText.each_char do |ch|
173
- if previous
174
- if ch == ?n
175
- result << "\n"
176
- else
177
- result << ch
178
- end
179
- previous = nil
180
- elsif ch == '\\'
181
- previous = ?\
175
+ # precondition: current position at leading quote
176
+ def build_string_token
177
+ scan_pos = scanner.pos
178
+ line = @lineno
179
+ column_start = scan_pos - @line_start
180
+ literal = +''
181
+ loop do
182
+ substr = scanner.scan(/[^"\\\r\n]*/)
183
+ if scanner.eos?
184
+ pos_start = "line #{line}:#{column_start}"
185
+ raise ScanError, "Error: [#{pos_start}]: Unterminated string."
182
186
  else
183
- result << ch
187
+ literal << substr
188
+ special = scanner.scan(/["\\\r\n]/)
189
+ case special
190
+ when '"' # Terminating quote found
191
+ break
192
+ when "\r"
193
+ next_line
194
+ special << scanner.scan(/./) if scanner.match?(/\n/)
195
+ literal << special
196
+ when "\n"
197
+ next_line
198
+ literal << special
199
+ when '\\'
200
+ ch = scanner.scan(/./)
201
+ next unless ch
202
+
203
+ escaped = @@escape_chars[ch]
204
+ if escaped
205
+ literal << escaped
206
+ else
207
+ literal << ch
208
+ end
209
+ end
184
210
  end
185
211
  end
186
-
187
- result
212
+ pos = Rley::Lexical::Position.new(line, column_start)
213
+ lox_string = Datatype::LXString.new(literal)
214
+ lexeme = scanner.string[scan_pos - 1..scanner.pos - 1]
215
+ Literal.new(lox_string, lexeme, 'STRING', pos)
188
216
  end
189
217
 
190
218
  # Skip non-significant whitespaces and comments.
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.02'
4
+ VERSION = '0.4.02'
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]
@@ -211,22 +211,25 @@ LOX_END
211
211
  end
212
212
 
213
213
  it 'should recognize escaped quotes' do
214
- embedded_quotes = %q{she said: \"Hello\"}
215
- result = subject.send(:unescape_string, embedded_quotes)
216
- expect(result).to eq('she said: "Hello"')
214
+ embedded_quotes = %q{"she said: \"Hello\""}
215
+ subject.start_with(embedded_quotes)
216
+ result = subject.tokens[0]
217
+ expect(result.value).to eq('she said: "Hello"')
217
218
  end
218
219
 
219
220
  it 'should recognize escaped backslash' do
220
- embedded_backslash = 'backslash>\\\\'
221
- result = subject.send(:unescape_string, embedded_backslash)
222
- expect(result).to eq('backslash>\\')
221
+ embedded_backslash = '"backslash>\\\\"'
222
+ subject.start_with(embedded_backslash)
223
+ result = subject.tokens[0]
224
+ expect(result.value).to eq('backslash>\\')
223
225
  end
224
226
 
225
227
  # rubocop: disable Style/StringConcatenation
226
228
  it 'should recognize newline escape sequence' do
227
- embedded_newline = 'line1\\nline2'
228
- result = subject.send(:unescape_string, embedded_newline)
229
- expect(result).to eq('line1' + "\n" + 'line2')
229
+ embedded_newline = '"line1\\nline2"'
230
+ subject.start_with(embedded_newline)
231
+ result = subject.tokens[0]
232
+ expect(result.value).to eq('line1' + "\n" + 'line2')
230
233
  end
231
234
  # rubocop: enable Style/StringConcatenation
232
235
 
@@ -289,7 +292,7 @@ LOX_END
289
292
  it 'should complain if it finds an unterminated string' do
290
293
  subject.start_with('var a = "Unfinished;')
291
294
  err = Loxxy::ScanError
292
- err_msg = 'Error: [line 1:21]: Unterminated string.'
295
+ err_msg = 'Error: [line 1:9]: Unterminated string.'
293
296
  expect { subject.tokens }.to raise_error(err, err_msg)
294
297
  end
295
298
 
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.02
4
+ version: 0.4.02
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-22 00:00:00.000000000 Z
11
+ date: 2021-09-10 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
  - - ">="