loxxy 0.3.02 → 0.4.02

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: 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
  - - ">="