loxxy 0.0.6 → 0.0.11
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 +3 -0
- data/CHANGELOG.md +56 -0
- data/README.md +69 -82
- data/lib/loxxy/ast/ast_builder.rb +150 -7
- data/lib/loxxy/ast/ast_visitor.rb +125 -0
- data/lib/loxxy/ast/lox_binary_expr.rb +22 -0
- data/lib/loxxy/ast/lox_compound_expr.rb +25 -0
- data/lib/loxxy/ast/lox_literal_expr.rb +25 -0
- data/lib/loxxy/ast/lox_node.rb +22 -0
- data/lib/loxxy/front_end/grammar.rb +39 -30
- data/lib/loxxy/version.rb +1 -1
- data/spec/front_end/parser_spec.rb +284 -23
- data/spec/front_end/raw_parser_spec.rb +3 -6
- data/spec/front_end/scanner_spec.rb +12 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 583f5c2f620ea8f45b339607103955eda2f32c02f0358ccb88cf4d20b4398d21
|
4
|
+
data.tar.gz: f349b949c1315473027eaaab589ff1cf2da89deca3b243cd1508c088bf9a9cc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c93648c634783348a22b3b65cf710a59c0ca5e3cefc5a20d7c60b9b429b4334e1e5603cc2bf74f302aed881daa71634cb7253445b3f376838197637f79f8cc7
|
7
|
+
data.tar.gz: 67d60e89985322911a7842a2b5fc2e22b4ccc3136ffa216027089ea05ea56a6932d1de9b8917173ad072695aaa6abb2147d456f54d09f2878d7831937c42af74
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,59 @@
|
|
1
|
+
## [0.0.11] - 2021-01-08
|
2
|
+
- AST node generation for logical expression (and, or).
|
3
|
+
|
4
|
+
## Changed
|
5
|
+
- Class `AST::ASTBuilder` added `reduce_` methods for logical operations.
|
6
|
+
- File `grammar.rb`added name to logical expression rules
|
7
|
+
- File `README.md` added gem version and license badges, expanded roadmap section.
|
8
|
+
|
9
|
+
## Fixed
|
10
|
+
- File `grammar.rb`: a rule had incomplete non-terminal name `conjunct_` in its lhs.
|
11
|
+
|
12
|
+
|
13
|
+
## [0.0.10] - 2021-01-08
|
14
|
+
- AST node generation for equality expression.
|
15
|
+
|
16
|
+
## Changed
|
17
|
+
- Class `AST::ASTBuilder` refactoring and added `reduce_` methods for equality operations.
|
18
|
+
- File `grammar.rb`added name to equality rules
|
19
|
+
- File `README.md` added gem version and license badges, expanded roadmap section.
|
20
|
+
|
21
|
+
## Fixed
|
22
|
+
- File `grammar.rb`: a rule had still the discarded non-terminal `equalityTest_star` in its lhs.
|
23
|
+
|
24
|
+
## [0.0.9] - 2021-01-07
|
25
|
+
- AST node generation for comparison expression.
|
26
|
+
|
27
|
+
## Changed
|
28
|
+
- Class `AST::ASTBuilder` added `reduce_` methods for comparison operations.
|
29
|
+
- File `grammar.rb`added name to comparison rules
|
30
|
+
|
31
|
+
## [0.0.8] - 2021-01-07
|
32
|
+
- AST node generation for arithmetic operations of number literals.
|
33
|
+
|
34
|
+
## Changed
|
35
|
+
- Class `AST::ASTBuilder` added `reduce_` methods for arithmetic operations.
|
36
|
+
- File `grammar.rb`added name to arithmetic rules
|
37
|
+
|
38
|
+
## Fixed
|
39
|
+
- File `grammar.rb`: second rule for `factor` had a missing member in rhs.
|
40
|
+
|
41
|
+
## [0.0.7] - 2021-01-06
|
42
|
+
- Lox grammar reworked, initial AST classes created.
|
43
|
+
|
44
|
+
## Added
|
45
|
+
- Class `Parser` this one generates AST's (Abstract Syntax Tree)
|
46
|
+
- Class `AST::ASTVisitor` draft initial implementation.
|
47
|
+
- Class `AST::BinaryExpr` draft initial implementation.
|
48
|
+
- Class `AST::LoxCompoundExpr` draft initial implementation.
|
49
|
+
- Class `AST::LiteralExpr` draft initial implementation.
|
50
|
+
- Class `AST::LoxNode` draft initial implementation.
|
51
|
+
|
52
|
+
## Changed
|
53
|
+
- File `spec_helper.rb`: removed Bundler dependency
|
54
|
+
- Class `AST::ASTBuilder` added initial `reduce_` methods.
|
55
|
+
- File `README.md` Removed example with AST generation since this is in flux.
|
56
|
+
|
1
57
|
## [0.0.6] - 2021-01-03
|
2
58
|
- First iteration of a parser with AST generation.
|
3
59
|
|
data/README.md
CHANGED
@@ -1,30 +1,70 @@
|
|
1
|
-
#
|
1
|
+
# loxxy
|
2
|
+
[](https://badge.fury.io/rb/loxxy)
|
3
|
+
[](https://github.com/famished-tiger/loxxy/blob/main/LICENSE.txt)
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
+
|
6
|
+
A Ruby implementation of the [Lox programming language](https://craftinginterpreters.com/the-lox-language.html ),
|
7
|
+
a simple language used in Bob Nystrom's online book [Crafting Interpreters](https://craftinginterpreters.com/ ).
|
5
8
|
|
6
9
|
## Purpose of this project:
|
7
10
|
- To deliver an open source example of a programming language fully implemented in Ruby
|
8
11
|
(from the scanner, parser, code generation).
|
9
|
-
- The implementation should be mature enough to run (
|
12
|
+
- The implementation should be mature enough to run [LoxLox](https://github.com/benhoyt/loxlox),
|
10
13
|
a Lox interpreter written in Lox.
|
11
14
|
|
12
|
-
##
|
13
|
-
|
14
|
-
-
|
15
|
-
|
16
|
-
- [STARTED] Custom AST builder class
|
17
|
-
- [TODO] Hierarchy classes for representing Lox expressions in AST
|
18
|
-
- [TODO] Interpreter or transpiler
|
19
|
-
|
20
|
-
## Example
|
21
|
-
The __loxxy__ hosts two distinct parsers classes (`RawParser` and `Parser`).
|
22
|
-
- A `RawParser` instance is able to recognize Lox input and to generate
|
23
|
-
a concrete parse tree from it.
|
15
|
+
## Current status
|
16
|
+
The __loxxy__ gem hosts two distinct parsers classes (`RawParser` and `Parser`).
|
17
|
+
- A `RawParser` instance is able to recognize valid Lox input and to generate
|
18
|
+
a concrete parse tree from it.
|
24
19
|
- A `Parser` instance can also parse Lox source code but will generate an AST
|
25
|
-
(Abstract Syntax Tree) that will be used by the future tree-walking interpreter.
|
20
|
+
(Abstract Syntax Tree) that will be used by the future tree-walking interpreter.
|
21
|
+
Currently it generates AST for arithmetic expressions with literal numbers only.
|
26
22
|
|
27
|
-
|
23
|
+
## Roadmap
|
24
|
+
### Done
|
25
|
+
- Scanner (tokenizer)
|
26
|
+
- Lox grammar (in format required by Rley gem)
|
27
|
+
- Raw parser. It parses Lox programs and generates a raw parse tree.
|
28
|
+
- Tailored parser for generating AST (Abstract Syntax Tree)
|
29
|
+
|
30
|
+
### Started
|
31
|
+
- Custom AST builder class
|
32
|
+
- Classes for representing Lox expressions in AST
|
33
|
+
|
34
|
+
### TODO
|
35
|
+
AST Node generation
|
36
|
+
Goal: parser should generate AST for any input Lox program
|
37
|
+
- [X] Equality operator
|
38
|
+
- [X] Logical operator (and, or)
|
39
|
+
- [] Unary expressions (negate, not)
|
40
|
+
- [] Grouping expressions
|
41
|
+
- [] Print statement
|
42
|
+
- [] Simple assignment expressions
|
43
|
+
- [] Variable declaration
|
44
|
+
- [] Dot notation
|
45
|
+
- [] Block statement
|
46
|
+
- [] If statement
|
47
|
+
- [] For statement
|
48
|
+
- [] While statement
|
49
|
+
- [] Function declaration
|
50
|
+
- [] Call expression
|
51
|
+
- [] Return statement
|
52
|
+
- [] Class declaration
|
53
|
+
- [] AST generation covers complete grammar
|
54
|
+
|
55
|
+
Tree-walking:
|
56
|
+
- [] Tree visitor recognizes all AST node types
|
57
|
+
|
58
|
+
Interpreter:
|
59
|
+
- Keywords: symbol table, scope, activation record, class & object model, Lox test suite
|
60
|
+
- [] Milestone: Interpreter handles expressions but function calls
|
61
|
+
- [] Milestone: Interpreter handles `for`, `if`, `print`, `while` and block statements
|
62
|
+
- [] Milestone: Interpreter handles variable declarations (global, block)
|
63
|
+
- [] Milestone: Interpreter handles function declarations and calls
|
64
|
+
- [] Milestone: Interpreter supports class and object definition
|
65
|
+
- [] Milestone: Lox interpreter complete
|
66
|
+
|
67
|
+
## Example using RawParser class
|
28
68
|
```ruby
|
29
69
|
require 'loxxy'
|
30
70
|
|
@@ -48,8 +88,7 @@ tree_formatter.render(visitor)
|
|
48
88
|
This is the output produced by the above example:
|
49
89
|
```
|
50
90
|
program
|
51
|
-
+--
|
52
|
-
| +-- declaration_star
|
91
|
+
+-- declaration_plus
|
53
92
|
| +-- declaration
|
54
93
|
| +-- statement
|
55
94
|
| +-- printStmt
|
@@ -58,70 +97,18 @@ program
|
|
58
97
|
| | +-- assignment
|
59
98
|
| | +-- logic_or
|
60
99
|
| | +-- logic_and
|
61
|
-
| |
|
62
|
-
| |
|
63
|
-
| |
|
64
|
-
| |
|
65
|
-
| |
|
66
|
-
| |
|
67
|
-
| |
|
68
|
-
| |
|
69
|
-
| | | | | | | | +-- refinement_star
|
70
|
-
| | | | | | | +-- multiplicative_star
|
71
|
-
| | | | | | +-- additive_star
|
72
|
-
| | | | | +-- comparisonTest_star
|
73
|
-
| | | | +-- equalityTest_star
|
74
|
-
| | | +-- conjunct_star
|
75
|
-
| | +-- disjunct_star
|
100
|
+
| | +-- equality
|
101
|
+
| | +-- comparison
|
102
|
+
| | +-- term
|
103
|
+
| | +-- factor
|
104
|
+
| | +-- unary
|
105
|
+
| | +-- call
|
106
|
+
| | +-- primary
|
107
|
+
| | +-- STRING: '"Hello, world!"'
|
76
108
|
| +-- SEMICOLON: ';'
|
77
109
|
+-- EOF: ''
|
78
110
|
```
|
79
111
|
|
80
|
-
### Example using Parser class
|
81
|
-
Except for the `Parser` class name, this example is identical
|
82
|
-
to the previous one.
|
83
|
-
|
84
|
-
```ruby
|
85
|
-
require 'loxxy'
|
86
|
-
|
87
|
-
|
88
|
-
lox_input = <<-LOX_END
|
89
|
-
// Your first Lox program!
|
90
|
-
print "Hello, world!";
|
91
|
-
LOX_END
|
92
|
-
|
93
|
-
# Show that the parser accepts the above program
|
94
|
-
parser = Loxxy::FrontEnd::Parser.new
|
95
|
-
|
96
|
-
# Now parse the input into an abstract parse tree...
|
97
|
-
ptree = parser.parse(lox_input)
|
98
|
-
|
99
|
-
# Display the parse tree thanks to Rley utility classes...
|
100
|
-
visitor = Rley::ParseTreeVisitor.new(ptree)
|
101
|
-
tree_formatter = Rley::Formatter::Asciitree.new($stdout)
|
102
|
-
tree_formatter.render(visitor)
|
103
|
-
```
|
104
|
-
|
105
|
-
However the output differs significantly:
|
106
|
-
```
|
107
|
-
program
|
108
|
-
+-- declaration_star
|
109
|
-
| +-- printStmt
|
110
|
-
| +-- PRINT: 'print'
|
111
|
-
| +-- logic_or
|
112
|
-
| | +-- logic_and
|
113
|
-
| | +-- equality
|
114
|
-
| | +-- comparison
|
115
|
-
| | +-- term
|
116
|
-
| | +-- factor
|
117
|
-
| | +-- call
|
118
|
-
| | +-- STRING: '"Hello, world!"'
|
119
|
-
| +-- SEMICOLON: ';'
|
120
|
-
+-- EOF: ''
|
121
|
-
```
|
122
|
-
|
123
|
-
|
124
|
-
|
125
112
|
## Installation
|
126
113
|
|
127
114
|
Add this line to your application's Gemfile:
|
@@ -143,9 +130,9 @@ Or install it yourself as:
|
|
143
130
|
TODO: Write usage instructions here
|
144
131
|
|
145
132
|
## Other Lox implementations in Ruby
|
146
|
-
An impressive list of Lox implementations can be found [here](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations
|
133
|
+
An impressive list of Lox implementations can be found [here](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations)
|
147
134
|
|
148
|
-
For Ruby,
|
135
|
+
For Ruby, there is the [lox](https://github.com/rdodson41/ruby-lox) gem.
|
149
136
|
There are other Ruby-based projects as well:
|
150
137
|
- [SlowLox](https://github.com/ArminKleinert/SlowLox), described as a "1-to-1 conversion of JLox to Ruby"
|
151
138
|
- [rulox](https://github.com/LevitatingBusinessMan/rulox)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../datatype/all_datatypes'
|
4
|
+
require_relative 'lox_literal_expr'
|
5
|
+
require_relative 'lox_binary_expr'
|
4
6
|
|
5
7
|
module Loxxy
|
6
8
|
module Ast
|
@@ -8,13 +10,31 @@ module Loxxy
|
|
8
10
|
# (Abstract Syntax Tree) from a sequence of input tokens and
|
9
11
|
# visit events produced by walking over a GFGParsing object.
|
10
12
|
class ASTBuilder < Rley::ParseRep::ASTBaseBuilder
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
# Mapping Token name => operator | separator | delimiter characters
|
14
|
+
# @return [Hash{String => String}]
|
15
|
+
Name2special = {
|
16
|
+
'AND' => 'and',
|
17
|
+
'BANG' => '!',
|
18
|
+
'BANG_EQUAL' => '!=',
|
19
|
+
'COMMA' => ',',
|
20
|
+
'DOT' => '.',
|
21
|
+
'EQUAL' => '=',
|
22
|
+
'EQUAL_EQUAL' => '==',
|
23
|
+
'GREATER' => '>',
|
24
|
+
'GREATER_EQUAL' => '>=',
|
25
|
+
'LEFT_BRACE' => '{',
|
26
|
+
'LEFT_PAREN' => '(',
|
27
|
+
'LESS' => '<',
|
28
|
+
'LESS_EQUAL' => '<=',
|
29
|
+
'MINUS' => '-',
|
30
|
+
'OR' => 'or',
|
31
|
+
'PLUS' => '+',
|
32
|
+
'RIGHT_BRACE' => '}',
|
33
|
+
'RIGHT_PAREN' => ')',
|
34
|
+
'SEMICOLON' => ';',
|
35
|
+
'SLASH' => '/',
|
36
|
+
'STAR' => '*'
|
37
|
+
}.freeze
|
18
38
|
|
19
39
|
attr_reader :strict
|
20
40
|
|
@@ -69,6 +89,129 @@ module Loxxy
|
|
69
89
|
|
70
90
|
node
|
71
91
|
end
|
92
|
+
|
93
|
+
# rule('lhs' => 'nonterm_i nonterm_k_plus')
|
94
|
+
def reduce_binary_operator(_production, _range, tokens, theChildren)
|
95
|
+
operand1 = theChildren[0]
|
96
|
+
|
97
|
+
# Second child is array with couples [operator, operand2]
|
98
|
+
theChildren[1].each do |(operator, operand2)|
|
99
|
+
operand1 = LoxBinaryExpr.new(tokens[0].position, operator, operand1, operand2)
|
100
|
+
end
|
101
|
+
|
102
|
+
operand1
|
103
|
+
end
|
104
|
+
|
105
|
+
# rule('something_plus' => 'something_plus operator symbol')
|
106
|
+
def reduce_binary_plus_more(_production, _range, _tokens, theChildren)
|
107
|
+
result = theChildren[0]
|
108
|
+
operator = Name2special[theChildren[1].symbol.name].to_sym
|
109
|
+
operand2 = theChildren[2]
|
110
|
+
result << [operator, operand2]
|
111
|
+
end
|
112
|
+
|
113
|
+
# rule('something_plus' => 'something_plus symbol')
|
114
|
+
def reduce_binary_plus_end(_production, _range, _tokens, theChildren)
|
115
|
+
operator = Name2special[theChildren[0].symbol.name].to_sym
|
116
|
+
operand2 = theChildren[1]
|
117
|
+
[[operator, operand2]]
|
118
|
+
end
|
119
|
+
|
120
|
+
# rule('logic_or' => 'logic_and disjunct_plus')
|
121
|
+
def reduce_logic_or_plus(production, range, tokens, theChildren)
|
122
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
123
|
+
end
|
124
|
+
|
125
|
+
# rule('disjunct_plus' => 'disjunct_plus OR logic_and')
|
126
|
+
def reduce_logic_or_plus_more(production, range, tokens, theChildren)
|
127
|
+
reduce_binary_plus_more(production, range, tokens, theChildren)
|
128
|
+
end
|
129
|
+
|
130
|
+
# rule('disjunct_plus' => 'OR logic_and')
|
131
|
+
def reduce_logic_or_plus_end(production, range, tokens, theChildren)
|
132
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
133
|
+
end
|
134
|
+
|
135
|
+
# rule('logic_and' => 'equality conjunct_plus')
|
136
|
+
def reduce_logic_and_plus(production, range, tokens, theChildren)
|
137
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
138
|
+
end
|
139
|
+
|
140
|
+
# rule('conjunct_plus' => 'conjunct_plus AND equality')
|
141
|
+
def reduce_logic_and_plus_more(production, range, tokens, theChildren)
|
142
|
+
reduce_binary_plus_more(production, range, tokens, theChildren)
|
143
|
+
end
|
144
|
+
|
145
|
+
# rule('conjunct_plus' => 'AND equality')
|
146
|
+
def reduce_logic_and_plus_end(production, range, tokens, theChildren)
|
147
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
148
|
+
end
|
149
|
+
|
150
|
+
# rule('equality' => 'comparison equalityTest_plus')
|
151
|
+
def reduce_equality_plus(production, range, tokens, theChildren)
|
152
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
153
|
+
end
|
154
|
+
|
155
|
+
# rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison')
|
156
|
+
def reduce_equality_t_plus_more(production, range, tokens, theChildren)
|
157
|
+
reduce_binary_plus_more(production, range, tokens, theChildren)
|
158
|
+
end
|
159
|
+
|
160
|
+
# rule('equalityTest_star' => 'equalityTest comparison')
|
161
|
+
def reduce_equality_t_plus_end(production, range, tokens, theChildren)
|
162
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
163
|
+
end
|
164
|
+
|
165
|
+
# rule('comparison' => 'term comparisonTest_plus')
|
166
|
+
def reduce_comparison_plus(production, range, tokens, theChildren)
|
167
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
168
|
+
end
|
169
|
+
|
170
|
+
# rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
|
171
|
+
# TODO: is it meaningful to implement this rule?
|
172
|
+
|
173
|
+
# rule('comparisonTest_plus' => 'comparisonTest term')
|
174
|
+
def reduce_comparison_t_plus_end(production, range, tokens, theChildren)
|
175
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
176
|
+
end
|
177
|
+
|
178
|
+
# rule('term' => 'factor additive_plus')
|
179
|
+
def reduce_term_additive(production, range, tokens, theChildren)
|
180
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
181
|
+
end
|
182
|
+
|
183
|
+
# rule('additive_star' => 'additive_star additionOp factor').as 'additionOp_expr'
|
184
|
+
def reduce_additive_plus_more(production, range, tokens, theChildren)
|
185
|
+
reduce_binary_plus_more(production, range, tokens, theChildren)
|
186
|
+
end
|
187
|
+
|
188
|
+
# rule('additive_plus' => 'additionOp factor')
|
189
|
+
def reduce_additive_plus_end(production, range, tokens, theChildren)
|
190
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
191
|
+
end
|
192
|
+
|
193
|
+
# rule('factor' => 'multiplicative_plus')
|
194
|
+
def reduce_factor_multiplicative(production, range, tokens, theChildren)
|
195
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
196
|
+
end
|
197
|
+
|
198
|
+
# rule('multiplicative_plus' => 'multiplicative_plus multOp unary')
|
199
|
+
def reduce_multiplicative_plus_more(production, range, tokens, theChildren)
|
200
|
+
reduce_binary_plus_more(production, range, tokens, theChildren)
|
201
|
+
end
|
202
|
+
|
203
|
+
# rule('multiplicative_plus' => 'multOp unary')
|
204
|
+
def reduce_multiplicative_plus_end(production, range, tokens, theChildren)
|
205
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
206
|
+
end
|
207
|
+
|
208
|
+
# rule('primary' => 'FALSE' | TRUE').as 'literal_expr'
|
209
|
+
def reduce_literal_expr(_production, _range, _tokens, theChildren)
|
210
|
+
first_child = theChildren.first
|
211
|
+
pos = first_child.token.position
|
212
|
+
literal = first_child.token.value
|
213
|
+
LoxLiteralExpr.new(pos, literal)
|
214
|
+
end
|
72
215
|
end # class
|
73
216
|
end # module
|
74
217
|
end # module
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Loxxy
|
4
|
+
module Ast
|
5
|
+
class ASTVisitor
|
6
|
+
# Link to the top node to visit
|
7
|
+
attr_reader(:top)
|
8
|
+
|
9
|
+
# List of objects that subscribed to the visit event notification.
|
10
|
+
attr_reader(:subscribers)
|
11
|
+
|
12
|
+
# attr_reader(:runtime)
|
13
|
+
|
14
|
+
# Build a visitor for the given top.
|
15
|
+
# @param aRoot [AST::LoxNode] the parse tree to visit.
|
16
|
+
def initialize(aTop)
|
17
|
+
raise StandardError if aTop.nil?
|
18
|
+
|
19
|
+
@top = aTop
|
20
|
+
@subscribers = []
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add a subscriber for the visit event notifications.
|
24
|
+
# @param aSubscriber [Object]
|
25
|
+
def subscribe(aSubscriber)
|
26
|
+
subscribers << aSubscriber
|
27
|
+
end
|
28
|
+
|
29
|
+
# Remove the given object from the subscription list.
|
30
|
+
# The object won't be notified of visit events.
|
31
|
+
# @param aSubscriber [Object]
|
32
|
+
def unsubscribe(aSubscriber)
|
33
|
+
subscribers.delete_if { |entry| entry == aSubscriber }
|
34
|
+
end
|
35
|
+
|
36
|
+
# The signal to begin the visit of the top.
|
37
|
+
def start # (aRuntime)
|
38
|
+
# @runtime = aRuntime
|
39
|
+
top.accept(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Visit event. The visitor is visiting the
|
43
|
+
# given terminal node containing a datatype object.
|
44
|
+
# @param aLiteralExpr [AST::LoxLiteralExpr] the leaf node to visit.
|
45
|
+
def visit_literal_expr(aLiteralExpr)
|
46
|
+
broadcast(:before_literal_expr, aLiteralExpr)
|
47
|
+
broadcast(:after_literal_expr, aLiteralExpr)
|
48
|
+
end
|
49
|
+
|
50
|
+
=begin
|
51
|
+
|
52
|
+
|
53
|
+
def visit_compound_datum(aCompoundDatum)
|
54
|
+
broadcast(:before_compound_datum, aCompoundDatum)
|
55
|
+
traverse_children(aCompoundDatum)
|
56
|
+
broadcast(:after_compound_datum, aCompoundDatum)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Visit event. The visitor is visiting the
|
60
|
+
# given empty list object.
|
61
|
+
# @param anEmptyList [SkmEmptyList] the empty list object to visit.
|
62
|
+
def visit_empty_list(anEmptyList)
|
63
|
+
broadcast(:before_empty_list, anEmptyList)
|
64
|
+
broadcast(:after_empty_list, anEmptyList)
|
65
|
+
end
|
66
|
+
|
67
|
+
def visit_pair(aPair)
|
68
|
+
broadcast(:before_pair, aPair)
|
69
|
+
traverse_car_cdr(aPair)
|
70
|
+
broadcast(:after_pair, aPair)
|
71
|
+
end
|
72
|
+
=end
|
73
|
+
=begin
|
74
|
+
# Visit event. The visitor is about to visit the given non terminal node.
|
75
|
+
# @param aNonTerminalNode [NonTerminalNode] the node to visit.
|
76
|
+
def visit_nonterminal(aNonTerminalNode)
|
77
|
+
if @traversal == :post_order
|
78
|
+
broadcast(:before_non_terminal, aNonTerminalNode)
|
79
|
+
traverse_subnodes(aNonTerminalNode)
|
80
|
+
else
|
81
|
+
traverse_subnodes(aNonTerminalNode)
|
82
|
+
broadcast(:before_non_terminal, aNonTerminalNode)
|
83
|
+
end
|
84
|
+
broadcast(:after_non_terminal, aNonTerminalNode)
|
85
|
+
end
|
86
|
+
=end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def traverse_children(aParent)
|
91
|
+
children = aParent.children
|
92
|
+
broadcast(:before_children, aParent, children)
|
93
|
+
|
94
|
+
# Let's proceed with the visit of children
|
95
|
+
children.each { |a_child| a_child.accept(self) }
|
96
|
+
|
97
|
+
broadcast(:after_children, aParent, children)
|
98
|
+
end
|
99
|
+
|
100
|
+
def traverse_car_cdr(aPair)
|
101
|
+
if aPair.car
|
102
|
+
broadcast(:before_car, aPair, aPair.car)
|
103
|
+
aPair.car.accept(self)
|
104
|
+
broadcast(:after_car, aPair, aPair.car)
|
105
|
+
end
|
106
|
+
if aPair.cdr
|
107
|
+
broadcast(:before_cdr, aPair, aPair.cdr)
|
108
|
+
aPair.cdr.accept(self)
|
109
|
+
broadcast(:after_cdr, aPair, aPair.cdr)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Send a notification to all subscribers.
|
114
|
+
# @param msg [Symbol] event to notify
|
115
|
+
# @param args [Array] arguments of the notification.
|
116
|
+
def broadcast(msg, *args)
|
117
|
+
subscribers.each do |subscr|
|
118
|
+
next unless subscr.respond_to?(msg) || subscr.respond_to?(:accept_all)
|
119
|
+
|
120
|
+
subscr.send(msg, runtime, *args)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end # class
|
124
|
+
end # module
|
125
|
+
end # module
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxBinaryExpr < LoxCompoundExpr
|
8
|
+
# @return [Symbol]
|
9
|
+
attr_reader :operator
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param operand1 [Loxxy::Ast::LoxNode]
|
13
|
+
# @param operand2 [Loxxy::Ast::LoxNode]
|
14
|
+
def initialize(aPosition, anOperator, operand1, operand2)
|
15
|
+
super(aPosition, [operand1, operand2])
|
16
|
+
@operator = anOperator
|
17
|
+
end
|
18
|
+
|
19
|
+
alias operands subnodes
|
20
|
+
end # class
|
21
|
+
end # module
|
22
|
+
end # module
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_node'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxCompoundExpr < LoxNode
|
8
|
+
# @return [Array<Ast::LoxNode>]
|
9
|
+
attr_reader :subnodes
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param theSubnodes [Array<Ast::LoxNode>]
|
13
|
+
def initialize(aPosition, theSubnodes)
|
14
|
+
super(aPosition)
|
15
|
+
@subnodes = theSubnodes
|
16
|
+
end
|
17
|
+
|
18
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
19
|
+
# @param visitor [ASTVisitor] the visitor
|
20
|
+
def accept(visitor)
|
21
|
+
visitor.visit_compound_expr(self)
|
22
|
+
end
|
23
|
+
end # class
|
24
|
+
end # module
|
25
|
+
end # module
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_node'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxLiteralExpr < LoxNode
|
8
|
+
# @return [Loxxy::Datatype::BuiltinDatatype]
|
9
|
+
attr_reader :literal
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param aLiteral [Loxxy::Datatype::BuiltinDatatype]
|
13
|
+
def initialize(aPosition, aLiteral)
|
14
|
+
super(aPosition)
|
15
|
+
@literal = aLiteral
|
16
|
+
end
|
17
|
+
|
18
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
19
|
+
# @param visitor [ASTVisitor] the visitor
|
20
|
+
def accept(visitor)
|
21
|
+
visitor.visit_literal_expr(self)
|
22
|
+
end
|
23
|
+
end # class
|
24
|
+
end # module
|
25
|
+
end # module
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Loxxy
|
4
|
+
module Ast
|
5
|
+
class LoxNode
|
6
|
+
# return [Rley::Lexical::Position] Position of the entry in the input stream.
|
7
|
+
attr_reader :position
|
8
|
+
|
9
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
10
|
+
def initialize(aPosition)
|
11
|
+
@position = aPosition
|
12
|
+
end
|
13
|
+
|
14
|
+
# Abstract method.
|
15
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
16
|
+
# @param _visitor [LoxxyTreeVisitor] the visitor
|
17
|
+
def accept(_visitor)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
end # class
|
21
|
+
end # module
|
22
|
+
end # module
|
@@ -26,11 +26,12 @@ module Loxxy
|
|
26
26
|
add_terminals('EOF')
|
27
27
|
|
28
28
|
# Top-level rule that matches an entire Lox program
|
29
|
-
rule('program' => '
|
29
|
+
rule('program' => 'EOF')
|
30
|
+
rule('program' => 'declaration_plus EOF')
|
30
31
|
|
31
32
|
# Declarations: bind an identifier to something
|
32
|
-
rule('
|
33
|
-
rule('
|
33
|
+
rule('declaration_plus' => 'declaration_plus declaration')
|
34
|
+
rule('declaration_plus' => 'declaration')
|
34
35
|
rule('declaration' => 'classDecl')
|
35
36
|
rule('declaration' => 'funDecl')
|
36
37
|
rule('declaration' => 'varDecl')
|
@@ -75,7 +76,8 @@ module Loxxy
|
|
75
76
|
rule('printStmt' => 'PRINT expression SEMICOLON')
|
76
77
|
rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
|
77
78
|
rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement')
|
78
|
-
rule('block' => 'LEFT_BRACE
|
79
|
+
rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
|
80
|
+
rule('block' => 'LEFT_BRACE RIGHT_BRACE')
|
79
81
|
|
80
82
|
# Expressions: produce values
|
81
83
|
rule('expression_opt' => 'expression')
|
@@ -85,49 +87,56 @@ module Loxxy
|
|
85
87
|
rule('assignment' => 'logic_or')
|
86
88
|
rule('owner_opt' => 'call DOT')
|
87
89
|
rule('owner_opt' => [])
|
88
|
-
rule('logic_or' => 'logic_and
|
89
|
-
rule('
|
90
|
-
rule('
|
91
|
-
rule('
|
92
|
-
rule('
|
93
|
-
rule('
|
94
|
-
rule('
|
95
|
-
rule('
|
96
|
-
rule('
|
90
|
+
rule('logic_or' => 'logic_and')
|
91
|
+
rule('logic_or' => 'logic_and disjunct_plus').as 'logic_or_plus'
|
92
|
+
rule('disjunct_plus' => 'disjunct_plus OR logic_and').as 'logic_or_plus_more'
|
93
|
+
rule('disjunct_plus' => 'OR logic_and').as 'logic_or_plus_end'
|
94
|
+
rule('logic_and' => 'equality')
|
95
|
+
rule('logic_and' => 'equality conjunct_plus').as 'logic_and_plus'
|
96
|
+
rule('conjunct_plus' => 'conjunct_plus AND equality').as 'logic_and_plus_more'
|
97
|
+
rule('conjunct_plus' => 'AND equality').as 'logic_and_plus_end'
|
98
|
+
rule('equality' => 'comparison')
|
99
|
+
rule('equality' => 'comparison equalityTest_plus').as 'equality_plus'
|
100
|
+
rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison').as 'equality_t_plus_more'
|
101
|
+
rule('equalityTest_plus' => 'equalityTest comparison').as 'equality_t_plus_end'
|
97
102
|
rule('equalityTest' => 'BANG_EQUAL')
|
98
103
|
rule('equalityTest' => 'EQUAL_EQUAL')
|
99
|
-
rule('comparison' => 'term
|
100
|
-
rule('
|
101
|
-
rule('
|
104
|
+
rule('comparison' => 'term')
|
105
|
+
rule('comparison' => 'term comparisonTest_plus').as 'comparison_plus'
|
106
|
+
rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
|
107
|
+
rule('comparisonTest_plus' => 'comparisonTest term').as 'comparison_t_plus_end'
|
102
108
|
rule('comparisonTest' => 'GREATER')
|
103
109
|
rule('comparisonTest' => 'GREATER_EQUAL')
|
104
110
|
rule('comparisonTest' => 'LESS')
|
105
111
|
rule('comparisonTest' => 'LESS_EQUAL')
|
106
|
-
rule('term' => 'factor
|
107
|
-
rule('
|
108
|
-
rule('
|
112
|
+
rule('term' => 'factor')
|
113
|
+
rule('term' => 'factor additive_plus').as 'term_additive'
|
114
|
+
rule('additive_plus' => 'additive_plus additionOp factor').as 'additive_plus_more'
|
115
|
+
rule('additive_plus' => 'additionOp factor').as 'additive_plus_end'
|
109
116
|
rule('additionOp' => 'MINUS')
|
110
117
|
rule('additionOp' => 'PLUS')
|
111
|
-
rule('factor' => 'unary
|
112
|
-
rule('
|
113
|
-
rule('
|
118
|
+
rule('factor' => 'unary')
|
119
|
+
rule('factor' => 'unary multiplicative_plus').as 'factor_multiplicative'
|
120
|
+
rule('multiplicative_plus' => 'multiplicative_plus multOp unary').as 'multiplicative_plus_more'
|
121
|
+
rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
|
114
122
|
rule('multOp' => 'SLASH')
|
115
123
|
rule('multOp' => 'STAR')
|
116
124
|
rule('unary' => 'unaryOp unary')
|
117
125
|
rule('unary' => 'call')
|
118
126
|
rule('unaryOp' => 'BANG')
|
119
127
|
rule('unaryOp' => 'MINUS')
|
120
|
-
rule('call' => 'primary
|
121
|
-
rule('
|
122
|
-
rule('
|
128
|
+
rule('call' => 'primary')
|
129
|
+
rule('call' => 'primary refinement_plus')
|
130
|
+
rule('refinement_plus' => 'refinement_plus refinement')
|
131
|
+
rule('refinement_plus' => 'refinement')
|
123
132
|
rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN')
|
124
133
|
rule('refinement' => 'DOT IDENTIFIER')
|
125
|
-
rule('primary' => 'TRUE')
|
126
|
-
rule('primary' => 'FALSE')
|
127
|
-
rule('primary' => 'NIL')
|
134
|
+
rule('primary' => 'TRUE').as 'literal_expr'
|
135
|
+
rule('primary' => 'FALSE').as 'literal_expr'
|
136
|
+
rule('primary' => 'NIL').as 'literal_expr'
|
128
137
|
rule('primary' => 'THIS')
|
129
|
-
rule('primary' => 'NUMBER')
|
130
|
-
rule('primary' => 'STRING')
|
138
|
+
rule('primary' => 'NUMBER').as 'literal_expr'
|
139
|
+
rule('primary' => 'STRING').as 'literal_expr'
|
131
140
|
rule('primary' => 'IDENTIFIER')
|
132
141
|
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
|
133
142
|
rule('primary' => 'SUPER DOT IDENTIFIER')
|
data/lib/loxxy/version.rb
CHANGED
@@ -10,6 +10,18 @@ module Loxxy
|
|
10
10
|
describe Parser do
|
11
11
|
subject { Parser.new }
|
12
12
|
|
13
|
+
# Utility method to walk towards deeply nested node
|
14
|
+
# @param aNTNode [Rley::PTree::NonTerminalNode]
|
15
|
+
# @param subnodePath[Array<Integer>] An Array of subnode indices
|
16
|
+
def walk_subnodes(aNTNode, subnodePath)
|
17
|
+
curr_node = aNTNode
|
18
|
+
subnodePath.each do |index|
|
19
|
+
curr_node = curr_node.subnodes[index]
|
20
|
+
end
|
21
|
+
|
22
|
+
curr_node
|
23
|
+
end
|
24
|
+
|
13
25
|
context 'Initialization:' do
|
14
26
|
it 'should be initialized without argument' do
|
15
27
|
expect { Parser.new }.not_to raise_error
|
@@ -25,11 +37,7 @@ module Loxxy
|
|
25
37
|
# Parse results MUST to comply to grammar rule:
|
26
38
|
# program => declaration_star EOF
|
27
39
|
# where the declaration_star MUST be empty
|
28
|
-
expect(aParseTree.root.symbol.name).to eq('
|
29
|
-
expect(aParseTree.root.subnodes.size).to eq(1)
|
30
|
-
eof = aParseTree.root.subnodes[0]
|
31
|
-
expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
|
32
|
-
expect(eof.symbol.name).to eq('EOF')
|
40
|
+
expect(aParseTree.root.symbol.name).to eq('EOF')
|
33
41
|
end
|
34
42
|
|
35
43
|
it 'should cope with an empty input' do
|
@@ -52,18 +60,59 @@ module Loxxy
|
|
52
60
|
end
|
53
61
|
end # context
|
54
62
|
|
55
|
-
context 'Parsing
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
+
context 'Parsing literals:' do
|
64
|
+
it 'should parse a false literal' do
|
65
|
+
input = 'false;'
|
66
|
+
ptree = subject.parse(input)
|
67
|
+
leaf = walk_subnodes(ptree.root, [0, 0])
|
68
|
+
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
69
|
+
expect(leaf.literal).to be_equal(Datatype::False.instance)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should parse a true literal' do
|
73
|
+
input = 'true;'
|
74
|
+
ptree = subject.parse(input)
|
75
|
+
leaf = walk_subnodes(ptree.root, [0, 0])
|
76
|
+
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
77
|
+
expect(leaf.literal).to be_equal(Datatype::True.instance)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should parse number literals' do
|
81
|
+
inputs = %w[1234; 12.34;]
|
82
|
+
inputs.each do |source|
|
83
|
+
ptree = subject.parse(source)
|
84
|
+
leaf = walk_subnodes(ptree.root, [0, 0])
|
85
|
+
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
86
|
+
expect(leaf.literal).to be_kind_of(Datatype::Number)
|
87
|
+
expect(leaf.literal.value).to eq(source.to_f)
|
63
88
|
end
|
89
|
+
end
|
64
90
|
|
65
|
-
|
91
|
+
it 'should parse string literals' do
|
92
|
+
inputs = [
|
93
|
+
'"I am a string";',
|
94
|
+
'"";',
|
95
|
+
'"123";'
|
96
|
+
]
|
97
|
+
inputs.each do |source|
|
98
|
+
ptree = subject.parse(source)
|
99
|
+
leaf = walk_subnodes(ptree.root, [0, 0])
|
100
|
+
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
101
|
+
expect(leaf.literal).to be_kind_of(Datatype::LXString)
|
102
|
+
expect(leaf.literal.value).to eq(source.gsub(/(^")|(";$)/, ''))
|
103
|
+
end
|
66
104
|
end
|
105
|
+
|
106
|
+
it 'should parse a nil literal' do
|
107
|
+
input = 'nil;'
|
108
|
+
ptree = subject.parse(input)
|
109
|
+
leaf = walk_subnodes(ptree.root, [0, 0])
|
110
|
+
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
111
|
+
expect(leaf.literal).to be_equal(Datatype::Nil.instance)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'Parsing expressions:' do
|
67
116
|
it 'should parse a hello world program' do
|
68
117
|
program = <<-LOX_END
|
69
118
|
// Your first Lox program!
|
@@ -72,25 +121,237 @@ LOX_END
|
|
72
121
|
ptree = subject.parse(program)
|
73
122
|
root = ptree.root
|
74
123
|
expect(root.symbol.name).to eq('program')
|
75
|
-
(
|
76
|
-
expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
|
77
|
-
expect(decls.symbol.name).to eq('declaration_star')
|
78
|
-
prnt_stmt = decls.subnodes[0]
|
124
|
+
(prnt_stmt, eof) = root.subnodes
|
79
125
|
expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
|
126
|
+
expect(prnt_stmt.symbol.name).to eq('printStmt')
|
80
127
|
expect(prnt_stmt.subnodes.size).to eq(3)
|
81
128
|
expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
|
82
129
|
expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
|
83
|
-
expect(prnt_stmt.subnodes[1]).to be_kind_of(
|
84
|
-
|
85
|
-
|
86
|
-
# expect(leaf_node.symbol.name).to eq('STRING')
|
87
|
-
# expect(leaf_node.token.value).to eq('Hello, world!')
|
130
|
+
expect(prnt_stmt.subnodes[1]).to be_kind_of(Loxxy::Ast::LoxLiteralExpr)
|
131
|
+
expect(prnt_stmt.subnodes[1].literal).to be_kind_of(Loxxy::Datatype::LXString)
|
132
|
+
expect(prnt_stmt.subnodes[1].literal.value).to eq('Hello, world!')
|
88
133
|
expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
|
89
134
|
expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
|
90
135
|
expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
|
91
136
|
expect(eof.symbol.name).to eq('EOF')
|
92
137
|
end
|
138
|
+
end # context
|
139
|
+
|
140
|
+
context 'Parsing arithmetic operations' do
|
141
|
+
it 'should parse the addition of two number literals' do
|
142
|
+
input = '123 + 456;'
|
143
|
+
ptree = subject.parse(input)
|
144
|
+
parent = ptree.root.subnodes[0]
|
145
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
146
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
147
|
+
expr = parent.subnodes[0]
|
148
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
149
|
+
expect(expr.operator).to eq(:+)
|
150
|
+
expect(expr.operands[0].literal.value).to eq(123)
|
151
|
+
expect(expr.operands[1].literal.value).to eq(456)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should parse the subtraction of two number literals' do
|
155
|
+
input = '4 - 3;'
|
156
|
+
ptree = subject.parse(input)
|
157
|
+
parent = ptree.root.subnodes[0]
|
158
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
159
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
160
|
+
expr = parent.subnodes[0]
|
161
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
162
|
+
expect(expr.operator).to eq(:-)
|
163
|
+
expect(expr.operands[0].literal.value).to eq(4)
|
164
|
+
expect(expr.operands[1].literal.value).to eq(3)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should parse multiple additive operations' do
|
168
|
+
input = '5 + 2 - 3;'
|
169
|
+
ptree = subject.parse(input)
|
170
|
+
parent = ptree.root.subnodes[0]
|
171
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
172
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
173
|
+
expr = parent.subnodes[0]
|
174
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
175
|
+
expect(expr.operator).to eq(:-)
|
176
|
+
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
177
|
+
expect(expr.operands[0].operator).to eq(:+)
|
178
|
+
expect(expr.operands[0].operands[0].literal.value).to eq(5)
|
179
|
+
expect(expr.operands[0].operands[1].literal.value).to eq(2)
|
180
|
+
expect(expr.operands[1].literal.value).to eq(3)
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should parse the division of two number literals' do
|
184
|
+
input = '8 / 2;'
|
185
|
+
ptree = subject.parse(input)
|
186
|
+
parent = ptree.root.subnodes[0]
|
187
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
188
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
189
|
+
expr = parent.subnodes[0]
|
190
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
191
|
+
expect(expr.operator).to eq(:/)
|
192
|
+
expect(expr.operands[0].literal.value).to eq(8)
|
193
|
+
expect(expr.operands[1].literal.value).to eq(2)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'should parse the product of two number literals' do
|
197
|
+
input = '12.34 * 0.3;'
|
198
|
+
ptree = subject.parse(input)
|
199
|
+
parent = ptree.root.subnodes[0]
|
200
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
201
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
202
|
+
expr = parent.subnodes[0]
|
203
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
204
|
+
expect(expr.operator).to eq(:*)
|
205
|
+
expect(expr.operands[0].literal.value).to eq(12.34)
|
206
|
+
expect(expr.operands[1].literal.value).to eq(0.3)
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'should parse multiple multiplicative operations' do
|
210
|
+
input = '5 * 2 / 3;'
|
211
|
+
ptree = subject.parse(input)
|
212
|
+
parent = ptree.root.subnodes[0]
|
213
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
214
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
215
|
+
expr = parent.subnodes[0]
|
216
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
217
|
+
expect(expr.operator).to eq(:/)
|
218
|
+
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
219
|
+
expect(expr.operands[0].operator).to eq(:*)
|
220
|
+
expect(expr.operands[0].operands[0].literal.value).to eq(5)
|
221
|
+
expect(expr.operands[0].operands[1].literal.value).to eq(2)
|
222
|
+
expect(expr.operands[1].literal.value).to eq(3)
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'should parse combination of terms and factors' do
|
226
|
+
input = '5 + 2 / 3;'
|
227
|
+
ptree = subject.parse(input)
|
228
|
+
parent = ptree.root.subnodes[0]
|
229
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
230
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
231
|
+
expr = parent.subnodes[0]
|
232
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
233
|
+
expect(expr.operator).to eq(:+)
|
234
|
+
expect(expr.operands[0].literal.value).to eq(5)
|
235
|
+
expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
|
236
|
+
expect(expr.operands[1].operator).to eq(:/)
|
237
|
+
expect(expr.operands[1].operands[0].literal.value).to eq(2)
|
238
|
+
expect(expr.operands[1].operands[1].literal.value).to eq(3)
|
239
|
+
end
|
240
|
+
end # context
|
241
|
+
|
242
|
+
context 'Parsing string concatenation' do
|
243
|
+
it 'should parse the concatenation of two string literals' do
|
244
|
+
input = '"Lo" + "ve";'
|
245
|
+
ptree = subject.parse(input)
|
246
|
+
parent = ptree.root.subnodes[0]
|
247
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
248
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
249
|
+
expr = parent.subnodes[0]
|
250
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
251
|
+
expect(expr.operator).to eq(:+)
|
252
|
+
expect(expr.operands[0].literal.value).to eq('Lo')
|
253
|
+
expect(expr.operands[1].literal.value).to eq('ve')
|
254
|
+
end
|
255
|
+
end # context
|
256
|
+
|
257
|
+
context 'Parsing comparison expressions' do
|
258
|
+
it 'should parse the comparison of two number literals' do
|
259
|
+
%w[> >= < <=].each do |predicate|
|
260
|
+
input = "3 #{predicate} 2;"
|
261
|
+
ptree = subject.parse(input)
|
262
|
+
parent = ptree.root.subnodes[0]
|
263
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
264
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
265
|
+
expr = parent.subnodes[0]
|
266
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
267
|
+
expect(expr.operator).to eq(predicate.to_sym)
|
268
|
+
expect(expr.operands[0].literal.value).to eq(3)
|
269
|
+
expect(expr.operands[1].literal.value).to eq(2)
|
270
|
+
end
|
93
271
|
end
|
272
|
+
end # context
|
273
|
+
|
274
|
+
context 'Parsing equality expressions' do
|
275
|
+
it 'should parse the equality of two number literals' do
|
276
|
+
%w[!= ==].each do |predicate|
|
277
|
+
input = "3 #{predicate} 2;"
|
278
|
+
ptree = subject.parse(input)
|
279
|
+
parent = ptree.root.subnodes[0]
|
280
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
281
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
282
|
+
expr = parent.subnodes[0]
|
283
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
284
|
+
expect(expr.operator).to eq(predicate.to_sym)
|
285
|
+
expect(expr.operands[0].literal.value).to eq(3)
|
286
|
+
expect(expr.operands[1].literal.value).to eq(2)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'should parse combination of equality expressions' do
|
291
|
+
input = '5 != 2 == false; // A bit contrived example'
|
292
|
+
ptree = subject.parse(input)
|
293
|
+
parent = ptree.root.subnodes[0]
|
294
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
295
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
296
|
+
expr = parent.subnodes[0]
|
297
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
298
|
+
expect(expr.operator).to eq(:==)
|
299
|
+
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
300
|
+
expect(expr.operands[0].operator).to eq(:!=)
|
301
|
+
expect(expr.operands[0].operands[0].literal.value).to eq(5)
|
302
|
+
expect(expr.operands[0].operands[1].literal.value).to eq(2)
|
303
|
+
expect(expr.operands[1].literal.value).to be_falsey
|
304
|
+
end
|
305
|
+
end # context
|
306
|
+
|
307
|
+
context 'Parsing logical expressions' do
|
308
|
+
it 'should parse the logical operations betweentwo sub-expression' do
|
309
|
+
%w[or and].each do |connector|
|
310
|
+
input = "5 > 2 #{connector} 3 <= 4;"
|
311
|
+
ptree = subject.parse(input)
|
312
|
+
parent = ptree.root.subnodes[0]
|
313
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
314
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
315
|
+
expr = parent.subnodes[0]
|
316
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
317
|
+
expect(expr.operator).to eq(connector.to_sym)
|
318
|
+
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
319
|
+
expect(expr.operands[0].operator).to eq(:>)
|
320
|
+
expect(expr.operands[0].operands[0].literal.value).to eq(5)
|
321
|
+
expect(expr.operands[0].operands[1].literal.value).to eq(2)
|
322
|
+
expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
|
323
|
+
expect(expr.operands[1].operator).to eq(:<=)
|
324
|
+
expect(expr.operands[1].operands[0].literal.value).to eq(3)
|
325
|
+
expect(expr.operands[1].operands[1].literal.value).to eq(4)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'should parse a combinations of logical expressions' do
|
330
|
+
input = '4 > 3 and 1 < 2 or 4 >= 5;'
|
331
|
+
ptree = subject.parse(input)
|
332
|
+
parent = ptree.root.subnodes[0]
|
333
|
+
expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
|
334
|
+
expect(parent.symbol.name).to eq('exprStmt')
|
335
|
+
expr = parent.subnodes[0]
|
336
|
+
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
337
|
+
expect(expr.operator).to eq(:or) # or has lower precedence than and
|
338
|
+
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
339
|
+
expect(expr.operands[0].operator).to eq(:and)
|
340
|
+
conjuncts = expr.operands[0].operands
|
341
|
+
expect(conjuncts[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
342
|
+
expect(conjuncts[0].operator).to eq(:>)
|
343
|
+
expect(conjuncts[0].operands[0].literal.value).to eq(4)
|
344
|
+
expect(conjuncts[0].operands[1].literal.value).to eq(3)
|
345
|
+
expect(conjuncts[1]).to be_kind_of(Ast::LoxBinaryExpr)
|
346
|
+
expect(conjuncts[1].operator).to eq(:<)
|
347
|
+
expect(conjuncts[1].operands[0].literal.value).to eq(1)
|
348
|
+
expect(conjuncts[1].operands[1].literal.value).to eq(2)
|
349
|
+
expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
|
350
|
+
expect(expr.operands[1].operator).to eq(:>=)
|
351
|
+
expect(expr.operands[1].operands[0].literal.value).to eq(4)
|
352
|
+
expect(expr.operands[1].operands[1].literal.value).to eq(5)
|
353
|
+
end
|
354
|
+
end # context
|
94
355
|
end # describe
|
95
356
|
end # module
|
96
357
|
end # module
|
@@ -26,10 +26,7 @@ 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
|
-
|
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
|
29
|
+
eof = aParseTree.root.subnodes.first
|
33
30
|
expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
|
34
31
|
expect(eof.symbol.name).to eq('EOF')
|
35
32
|
end
|
@@ -76,8 +73,8 @@ LOX_END
|
|
76
73
|
expect(root.symbol.name).to eq('program')
|
77
74
|
(decls, eof) = root.subnodes
|
78
75
|
expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
|
79
|
-
expect(decls.symbol.name).to eq('
|
80
|
-
stmt = decls.subnodes[
|
76
|
+
expect(decls.symbol.name).to eq('declaration_plus')
|
77
|
+
stmt = decls.subnodes[0].subnodes[0]
|
81
78
|
expect(stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
|
82
79
|
expect(stmt.symbol.name).to eq('statement')
|
83
80
|
prnt_stmt = stmt.subnodes[0]
|
@@ -223,6 +223,18 @@ LOX_END
|
|
223
223
|
]
|
224
224
|
match_expectations(subject, expectations)
|
225
225
|
end
|
226
|
+
|
227
|
+
it 'should cope with single slash (divide) expression' do
|
228
|
+
subject.start_with('8 / 2')
|
229
|
+
|
230
|
+
expectations = [
|
231
|
+
# [token lexeme]
|
232
|
+
%w[NUMBER 8],
|
233
|
+
%w[SLASH /],
|
234
|
+
%w[NUMBER 2]
|
235
|
+
]
|
236
|
+
match_expectations(subject, expectations)
|
237
|
+
end
|
226
238
|
end # context
|
227
239
|
end # describe
|
228
240
|
end # module
|
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.0.
|
4
|
+
version: 0.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-01-
|
11
|
+
date: 2021-01-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -85,6 +85,11 @@ files:
|
|
85
85
|
- Rakefile
|
86
86
|
- lib/loxxy.rb
|
87
87
|
- lib/loxxy/ast/ast_builder.rb
|
88
|
+
- lib/loxxy/ast/ast_visitor.rb
|
89
|
+
- lib/loxxy/ast/lox_binary_expr.rb
|
90
|
+
- lib/loxxy/ast/lox_compound_expr.rb
|
91
|
+
- lib/loxxy/ast/lox_literal_expr.rb
|
92
|
+
- lib/loxxy/ast/lox_node.rb
|
88
93
|
- lib/loxxy/datatype/all_datatypes.rb
|
89
94
|
- lib/loxxy/datatype/boolean.rb
|
90
95
|
- lib/loxxy/datatype/builtin_datatype.rb
|