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 +4 -4
- data/.rubocop.yml +24 -0
- data/CHANGELOG.md +42 -0
- data/README.md +63 -5
- data/lib/loxxy/ast/ast_builder.rb +55 -91
- data/lib/loxxy/ast/ast_visitor.rb +0 -1
- data/lib/loxxy/ast/lox_class_stmt.rb +8 -5
- data/lib/loxxy/ast/lox_fun_stmt.rb +12 -5
- data/lib/loxxy/ast/lox_node.rb +1 -1
- data/lib/loxxy/back_end/engine.rb +7 -2
- data/lib/loxxy/back_end/lox_instance.rb +14 -2
- data/lib/loxxy/back_end/resolver.rb +5 -0
- data/lib/loxxy/datatype/lx_string.rb +2 -3
- data/lib/loxxy/front_end/grammar.rb +16 -39
- data/lib/loxxy/front_end/scanner.rb +54 -26
- data/lib/loxxy/version.rb +1 -1
- data/loxxy.gemspec +2 -2
- data/spec/front_end/raw_parser_spec.rb +10 -3
- data/spec/front_end/scanner_spec.rb +13 -10
- data/spec/spec_helper.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5a4313b7377778f23e130f599cb4d28d43fd4ff3d56ec2c93c9b9636ac04743
|
4
|
+
data.tar.gz: 17f543465f55d0e83e2145973522da4230d7162d72f81d54ea957ac1522be412
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 (
|
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 '
|
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
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
169
|
-
|
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
|
180
|
-
theChildren[
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
173
|
+
# rule('class_body' => 'LEFT_BRACE function* RIGHT_BRACE')
|
191
174
|
def reduce_class_body(_production, _range, _tokens, theChildren)
|
192
|
-
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
|
-
|
219
|
-
|
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
|
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' => '
|
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
|
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
|
273
|
+
# rule('block' => 'LEFT_BRACE declaration* RIGHT_BRACE')
|
306
274
|
def reduce_block_stmt(_production, _range, tokens, theChildren)
|
307
|
-
decls =
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
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' => '
|
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]
|
320
|
-
theChildren[0].
|
321
|
-
|
322
|
-
theChildren[
|
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
|
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,
|
364
|
+
def reduce_variable_expr(_production, _range, _tokens, theChildren)
|
399
365
|
var_name = theChildren[0].token.lexeme
|
400
|
-
|
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' => '
|
425
|
-
def
|
426
|
-
|
427
|
-
|
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
|
-
|
430
|
-
|
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' => '
|
435
|
-
def
|
436
|
-
theChildren[0]
|
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
|
-
|
440
|
-
|
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 '
|
3
|
+
require_relative 'lox_node'
|
4
4
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
|
-
|
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
|
19
|
-
# @param
|
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
|
-
#
|
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
|
17
|
-
# @param
|
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
|
data/lib/loxxy/ast/lox_node.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
26
|
-
|
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
|
50
|
+
raise StandardError, "Invalid string value #{aValue}"
|
51
51
|
end
|
52
52
|
|
53
|
-
|
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
|
-
#
|
10
|
+
# Authoritative grammar at:
|
11
11
|
# https://craftinginterpreters.com/appendix-i.html
|
12
|
-
builder = Rley::
|
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 '
|
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 '
|
42
|
-
rule('
|
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
|
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' => '
|
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
|
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
|
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' => '
|
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
|
134
|
-
rule('
|
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
|
150
|
-
rule('
|
151
|
-
rule('
|
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
|
-
#
|
168
|
-
def
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
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
|
-
|
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
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.
|
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.
|
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
|
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('
|
77
|
-
|
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
|
-
|
216
|
-
|
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
|
-
|
222
|
-
|
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
|
-
|
229
|
-
|
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:
|
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
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.
|
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-
|
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.
|
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.
|
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.
|
176
|
+
version: '2.5'
|
177
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
178
178
|
requirements:
|
179
179
|
- - ">="
|