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