loxxy 0.0.5 → 0.0.10
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 +58 -0
- data/README.md +70 -39
- data/lib/loxxy.rb +1 -0
- data/lib/loxxy/ast/ast_builder.rb +187 -0
- 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 +44 -33
- data/lib/loxxy/front_end/parser.rb +56 -0
- data/lib/loxxy/version.rb +1 -1
- data/spec/front_end/parser_spec.rb +308 -0
- data/spec/front_end/raw_parser_spec.rb +3 -6
- data/spec/front_end/scanner_spec.rb +12 -0
- data/spec/spec_helper.rb +1 -1
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4f33203580d2b83ee7139e449787b439c0fb56c5a8c3dcc8035d114ffd957d3
|
4
|
+
data.tar.gz: 46c9fb2f55849c6aa9b9e4d58588746ddb3ac3353f7ed09922d9394a9fc512c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30f554af6338b50297ce6befaa62c86c8406df681e25098cc95dc28da64bb6feabbb9e085846eb8032f51d8d3655332105eb1d7165903bbd0d6be24cb13c4d0b
|
7
|
+
data.tar.gz: d9d9a69a010d16807f6c4bf24b15e8135367bb62b6d2e052943e0d6030751f282c777f652dd3fced08093aee8abae228e63d365cae156fa6e87ac7f52fc7e2e6
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,61 @@
|
|
1
|
+
## [0.0.10] - 2021-01-08
|
2
|
+
- AST node generation for equality expression.
|
3
|
+
|
4
|
+
## Changed
|
5
|
+
- Class `AST::ASTBuilder` refactoring and added `reduce_` methods for equality operations.
|
6
|
+
- File `grammar.rb`added name to equality rules
|
7
|
+
- File `README.md` added gem version and license badges, expanded roadmap section.
|
8
|
+
|
9
|
+
## Fixed
|
10
|
+
- File `grammar.rb`: a rule had still the discarded non-terminal `equalityTest_star` in its lhs.
|
11
|
+
|
12
|
+
## [0.0.9] - 2021-01-07
|
13
|
+
- AST node generation for comparison expression.
|
14
|
+
|
15
|
+
## Changed
|
16
|
+
- Class `AST::ASTBuilder` added `reduce_` methods for comparison operations.
|
17
|
+
- File `grammar.rb`added name to comparison rules
|
18
|
+
|
19
|
+
## [0.0.8] - 2021-01-07
|
20
|
+
- AST node generation for arithmetic operations of number literals.
|
21
|
+
|
22
|
+
## Changed
|
23
|
+
- Class `AST::ASTBuilder` added `reduce_` methods for arithmetic operations.
|
24
|
+
- File `grammar.rb`added name to arithmetic rules
|
25
|
+
|
26
|
+
## Fixed
|
27
|
+
- File `grammar.rb`: second rule for `factor` had a missing member in rhs.
|
28
|
+
|
29
|
+
## [0.0.7] - 2021-01-06
|
30
|
+
- Lox grammar reworked, initial AST classes created.
|
31
|
+
|
32
|
+
## Added
|
33
|
+
- Class `Parser` this one generates AST's (Abstract Syntax Tree)
|
34
|
+
- Class `AST::ASTVisitor` draft initial implementation.
|
35
|
+
- Class `AST::BinaryExpr` draft initial implementation.
|
36
|
+
- Class `AST::LoxCompoundExpr` draft initial implementation.
|
37
|
+
- Class `AST::LiteralExpr` draft initial implementation.
|
38
|
+
- Class `AST::LoxNode` draft initial implementation.
|
39
|
+
|
40
|
+
## Changed
|
41
|
+
- File `spec_helper.rb`: removed Bundler dependency
|
42
|
+
- Class `AST::ASTBuilder` added initial `reduce_` methods.
|
43
|
+
- File `README.md` Removed example with AST generation since this is in flux.
|
44
|
+
|
45
|
+
## [0.0.6] - 2021-01-03
|
46
|
+
- First iteration of a parser with AST generation.
|
47
|
+
|
48
|
+
## Added
|
49
|
+
- Class `Parser` this one generates AST's (Abstract Syntax Tree)
|
50
|
+
- Class `AST::ASTBuilder` default code to generate an AST.
|
51
|
+
|
52
|
+
## Changed
|
53
|
+
- File `spec_helper.rb`: removed Bundler dependency
|
54
|
+
- File `README.md` Added example with AST visualization.
|
55
|
+
|
56
|
+
## Fixed
|
57
|
+
- File `grammar.rb` ensure that the constant `Grammar` is created once only.
|
58
|
+
|
1
59
|
## [0.0.5] - 2021-01-02
|
2
60
|
- Improved example in `README.md`, code re-styling to please `Rubocop` 1.7
|
3
61
|
|
data/README.md
CHANGED
@@ -1,27 +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
|
-
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
## Example
|
22
|
-
At this, stage, the raw parser 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.
|
19
|
+
- A `Parser` instance can also parse Lox source code but will generate an AST
|
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.
|
24
22
|
|
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
|
+
- [] 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
|
25
68
|
```ruby
|
26
69
|
require 'loxxy'
|
27
70
|
|
@@ -45,8 +88,7 @@ tree_formatter.render(visitor)
|
|
45
88
|
This is the output produced by the above example:
|
46
89
|
```
|
47
90
|
program
|
48
|
-
+--
|
49
|
-
| +-- declaration_star
|
91
|
+
+-- declaration_plus
|
50
92
|
| +-- declaration
|
51
93
|
| +-- statement
|
52
94
|
| +-- printStmt
|
@@ -55,29 +97,18 @@ program
|
|
55
97
|
| | +-- assignment
|
56
98
|
| | +-- logic_or
|
57
99
|
| | +-- logic_and
|
58
|
-
| |
|
59
|
-
| |
|
60
|
-
| |
|
61
|
-
| |
|
62
|
-
| |
|
63
|
-
| |
|
64
|
-
| |
|
65
|
-
| |
|
66
|
-
| | | | | | | | +-- refinement_star
|
67
|
-
| | | | | | | +-- multiplicative_star
|
68
|
-
| | | | | | +-- additive_star
|
69
|
-
| | | | | +-- comparisonTest_star
|
70
|
-
| | | | +-- equalityTest_star
|
71
|
-
| | | +-- conjunct_star
|
72
|
-
| | +-- disjunct_star
|
100
|
+
| | +-- equality
|
101
|
+
| | +-- comparison
|
102
|
+
| | +-- term
|
103
|
+
| | +-- factor
|
104
|
+
| | +-- unary
|
105
|
+
| | +-- call
|
106
|
+
| | +-- primary
|
107
|
+
| | +-- STRING: '"Hello, world!"'
|
73
108
|
| +-- SEMICOLON: ';'
|
74
109
|
+-- EOF: ''
|
75
110
|
```
|
76
111
|
|
77
|
-
Soon, the `loxxy` will host another, tailored parser, that will generate
|
78
|
-
abstract syntax tree which much more convenient for further processing
|
79
|
-
(like implementing an interpreter).
|
80
|
-
|
81
112
|
## Installation
|
82
113
|
|
83
114
|
Add this line to your application's Gemfile:
|
@@ -99,9 +130,9 @@ Or install it yourself as:
|
|
99
130
|
TODO: Write usage instructions here
|
100
131
|
|
101
132
|
## Other Lox implementations in Ruby
|
102
|
-
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)
|
103
134
|
|
104
|
-
For Ruby,
|
135
|
+
For Ruby, there is the [lox](https://github.com/rdodson41/ruby-lox) gem.
|
105
136
|
There are other Ruby-based projects as well:
|
106
137
|
- [SlowLox](https://github.com/ArminKleinert/SlowLox), described as a "1-to-1 conversion of JLox to Ruby"
|
107
138
|
- [rulox](https://github.com/LevitatingBusinessMan/rulox)
|
data/lib/loxxy.rb
CHANGED
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../datatype/all_datatypes'
|
4
|
+
require_relative 'lox_literal_expr'
|
5
|
+
require_relative 'lox_binary_expr'
|
6
|
+
|
7
|
+
module Loxxy
|
8
|
+
module Ast
|
9
|
+
# The purpose of ASTBuilder is to build piece by piece an AST
|
10
|
+
# (Abstract Syntax Tree) from a sequence of input tokens and
|
11
|
+
# visit events produced by walking over a GFGParsing object.
|
12
|
+
class ASTBuilder < Rley::ParseRep::ASTBaseBuilder
|
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
|
38
|
+
|
39
|
+
attr_reader :strict
|
40
|
+
|
41
|
+
# Create a new AST builder instance.
|
42
|
+
# @param theTokens [Array<Rley::Lexical::Token>] The sequence of input tokens.
|
43
|
+
def initialize(theTokens)
|
44
|
+
super(theTokens)
|
45
|
+
@strict = false
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def terminal2node
|
51
|
+
Terminal2NodeClass
|
52
|
+
end
|
53
|
+
|
54
|
+
# Method override
|
55
|
+
def new_leaf_node(_production, _terminal, aTokenPosition, aToken)
|
56
|
+
Rley::PTree::TerminalNode.new(aToken, aTokenPosition)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Factory method for creating a parent node object.
|
60
|
+
# @param aProduction [Production] Production rule
|
61
|
+
# @param aRange [Range] Range of tokens matched by the rule
|
62
|
+
# @param theTokens [Array] The input tokens
|
63
|
+
# @param theChildren [Array] Children nodes (one per rhs symbol)
|
64
|
+
def new_parent_node(aProduction, aRange, theTokens, theChildren)
|
65
|
+
mth_name = method_name(aProduction.name)
|
66
|
+
if respond_to?(mth_name, true)
|
67
|
+
node = send(mth_name, aProduction, aRange, theTokens, theChildren)
|
68
|
+
else
|
69
|
+
# Default action...
|
70
|
+
node = case aProduction.rhs.size
|
71
|
+
when 0
|
72
|
+
return_epsilon(aRange, theTokens, theChildren)
|
73
|
+
when 1
|
74
|
+
return_first_child(aRange, theTokens, theChildren)
|
75
|
+
else
|
76
|
+
if strict
|
77
|
+
msg = "Don't know production '#{aProduction.name}'"
|
78
|
+
raise StandardError, msg
|
79
|
+
else
|
80
|
+
node = Rley::PTree::NonTerminalNode.new(aProduction.lhs, aRange)
|
81
|
+
theChildren&.reverse_each do |child|
|
82
|
+
node.add_subnode(child) if child
|
83
|
+
end
|
84
|
+
|
85
|
+
node
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
node
|
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('equality' => 'comparison equalityTest_plus')
|
121
|
+
def reduce_equality_plus(production, range, tokens, theChildren)
|
122
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
123
|
+
end
|
124
|
+
|
125
|
+
# rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison')
|
126
|
+
def reduce_equality_t_plus_more(production, range, tokens, theChildren)
|
127
|
+
reduce_binary_plus_more(production, range, tokens, theChildren)
|
128
|
+
end
|
129
|
+
|
130
|
+
# rule('equalityTest_star' => 'equalityTest comparison')
|
131
|
+
def reduce_equality_t_plus_end(production, range, tokens, theChildren)
|
132
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
133
|
+
end
|
134
|
+
|
135
|
+
# rule('comparison' => 'term comparisonTest_plus')
|
136
|
+
def reduce_comparison_plus(production, range, tokens, theChildren)
|
137
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
138
|
+
end
|
139
|
+
|
140
|
+
# rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
|
141
|
+
# TODO: is it meaningful to implement this rule?
|
142
|
+
|
143
|
+
# rule('comparisonTest_plus' => 'comparisonTest term')
|
144
|
+
def reduce_comparison_t_plus_end(production, range, tokens, theChildren)
|
145
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
146
|
+
end
|
147
|
+
|
148
|
+
# rule('term' => 'factor additive_plus')
|
149
|
+
def reduce_term_additive(production, range, tokens, theChildren)
|
150
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
151
|
+
end
|
152
|
+
|
153
|
+
# rule('additive_star' => 'additive_star additionOp factor').as 'additionOp_expr'
|
154
|
+
def reduce_additive_plus_more(production, range, tokens, theChildren)
|
155
|
+
reduce_binary_plus_more(production, range, tokens, theChildren)
|
156
|
+
end
|
157
|
+
|
158
|
+
# rule('additive_plus' => 'additionOp factor')
|
159
|
+
def reduce_additive_plus_end(production, range, tokens, theChildren)
|
160
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
161
|
+
end
|
162
|
+
|
163
|
+
# rule('factor' => 'multiplicative_plus')
|
164
|
+
def reduce_factor_multiplicative(production, range, tokens, theChildren)
|
165
|
+
reduce_binary_operator(production, range, tokens, theChildren)
|
166
|
+
end
|
167
|
+
|
168
|
+
# rule('multiplicative_plus' => 'multiplicative_plus multOp unary')
|
169
|
+
def reduce_multiplicative_plus_more(production, range, tokens, theChildren)
|
170
|
+
reduce_binary_plus_more(production, range, tokens, theChildren)
|
171
|
+
end
|
172
|
+
|
173
|
+
# rule('multiplicative_plus' => 'multOp unary')
|
174
|
+
def reduce_multiplicative_plus_end(production, range, tokens, theChildren)
|
175
|
+
reduce_binary_plus_end(production, range, tokens, theChildren)
|
176
|
+
end
|
177
|
+
|
178
|
+
# rule('primary' => 'FALSE' | TRUE').as 'literal_expr'
|
179
|
+
def reduce_literal_expr(_production, _range, _tokens, theChildren)
|
180
|
+
first_child = theChildren.first
|
181
|
+
pos = first_child.token.position
|
182
|
+
literal = first_child.token.value
|
183
|
+
LoxLiteralExpr.new(pos, literal)
|
184
|
+
end
|
185
|
+
end # class
|
186
|
+
end # module
|
187
|
+
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')
|
92
|
+
rule('disjunct_plus' => 'disjunct_plus OR logic_and')
|
93
|
+
rule('disjunct_plus' => 'OR logic_and')
|
94
|
+
rule('logic_and' => 'equality')
|
95
|
+
rule('logic_and' => 'equality conjunct_plus')
|
96
|
+
rule('conjunct_plus' => 'conjunct_plus AND equality')
|
97
|
+
rule('conjunct_' => 'AND equality')
|
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')
|
@@ -144,8 +153,10 @@ module Loxxy
|
|
144
153
|
rule('arguments' => 'expression')
|
145
154
|
end
|
146
155
|
|
147
|
-
|
148
|
-
|
149
|
-
|
156
|
+
unless defined?(Grammar)
|
157
|
+
# And now build the grammar and make it accessible via a constant
|
158
|
+
# @return [Rley::Syntax::Grammar]
|
159
|
+
Grammar = builder.grammar
|
160
|
+
end
|
150
161
|
end # module
|
151
162
|
end # module
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'scanner'
|
4
|
+
require_relative 'grammar'
|
5
|
+
require_relative '../ast/ast_builder'
|
6
|
+
|
7
|
+
module Loxxy
|
8
|
+
module FrontEnd
|
9
|
+
# A Lox parser that produce concrete parse trees.
|
10
|
+
# Concrete parse trees are the default kind of parse tree
|
11
|
+
# generated by the Rley library.
|
12
|
+
# They consist of two node types only:
|
13
|
+
# - NonTerminalNode
|
14
|
+
# - TerminalNode
|
15
|
+
# A NonTerminalNode has zero or more child nodes (called subnodes)
|
16
|
+
# A TerminalNode is leaf node, that is, it has no child node.
|
17
|
+
# While concrete parse tree nodes can be generated out of the box,
|
18
|
+
# they have the following drawbacks:
|
19
|
+
# - Generic node classes that aren't always suited for the needs of
|
20
|
+
# the language being processing.
|
21
|
+
# - Concrete parse tree tend to be deeply nested, which may complicate
|
22
|
+
# further processing.
|
23
|
+
class Parser
|
24
|
+
# @return [Rley::Engine] A facade object for the Rley parsing library
|
25
|
+
attr_reader(:engine)
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
# Create a Rley facade object
|
29
|
+
@engine = Rley::Engine.new do |cfg|
|
30
|
+
cfg.diagnose = true
|
31
|
+
cfg.repr_builder = Ast::ASTBuilder
|
32
|
+
end
|
33
|
+
|
34
|
+
# Step 1. Load Lox grammar
|
35
|
+
@engine.use_grammar(Loxxy::FrontEnd::Grammar)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Parse the given Lox program into a parse tree.
|
39
|
+
# @param source [String] Lox program to parse
|
40
|
+
# @return [Rley::ParseTree] A parse tree equivalent to the Lox input.
|
41
|
+
def parse(source)
|
42
|
+
lexer = Scanner.new(source)
|
43
|
+
result = engine.parse(lexer.tokens)
|
44
|
+
|
45
|
+
unless result.success?
|
46
|
+
# Stop if the parse failed...
|
47
|
+
line1 = "Parsing failed\n"
|
48
|
+
line2 = "Reason: #{result.failure_reason.message}"
|
49
|
+
raise StandardError, line1 + line2
|
50
|
+
end
|
51
|
+
|
52
|
+
return engine.convert(result) # engine.to_ptree(result)
|
53
|
+
end
|
54
|
+
end # class
|
55
|
+
end # module
|
56
|
+
end # module
|
data/lib/loxxy/version.rb
CHANGED
@@ -0,0 +1,308 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../spec_helper' # Use the RSpec framework
|
4
|
+
|
5
|
+
# Load the class under test
|
6
|
+
require_relative '../../lib/loxxy/front_end/parser'
|
7
|
+
|
8
|
+
module Loxxy
|
9
|
+
module FrontEnd
|
10
|
+
describe Parser do
|
11
|
+
subject { Parser.new }
|
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
|
+
|
25
|
+
context 'Initialization:' do
|
26
|
+
it 'should be initialized without argument' do
|
27
|
+
expect { Parser.new }.not_to raise_error
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should have its parse engine initialized' do
|
31
|
+
expect(subject.engine).to be_kind_of(Rley::Engine)
|
32
|
+
end
|
33
|
+
end # context
|
34
|
+
|
35
|
+
context 'Parsing blank files:' do
|
36
|
+
def check_empty_input_result(aParseTree)
|
37
|
+
# Parse results MUST to comply to grammar rule:
|
38
|
+
# program => declaration_star EOF
|
39
|
+
# where the declaration_star MUST be empty
|
40
|
+
expect(aParseTree.root.symbol.name).to eq('EOF')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should cope with an empty input' do
|
44
|
+
ptree = subject.parse('')
|
45
|
+
check_empty_input_result(ptree)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should cope with whitespaces only input' do
|
49
|
+
ptree = subject.parse(' ' * 80 + "\n" * 20)
|
50
|
+
check_empty_input_result(ptree)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should cope with comments only input' do
|
54
|
+
input = +''
|
55
|
+
%w[First Second Third].each do |ordinal|
|
56
|
+
input << "// #{ordinal} comment line\r\n"
|
57
|
+
end
|
58
|
+
ptree = subject.parse(input)
|
59
|
+
check_empty_input_result(ptree)
|
60
|
+
end
|
61
|
+
end # context
|
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)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
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
|
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
|
116
|
+
it 'should parse a hello world program' do
|
117
|
+
program = <<-LOX_END
|
118
|
+
// Your first Lox program!
|
119
|
+
print "Hello, world!";
|
120
|
+
LOX_END
|
121
|
+
ptree = subject.parse(program)
|
122
|
+
root = ptree.root
|
123
|
+
expect(root.symbol.name).to eq('program')
|
124
|
+
(prnt_stmt, eof) = root.subnodes
|
125
|
+
expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
|
126
|
+
expect(prnt_stmt.symbol.name).to eq('printStmt')
|
127
|
+
expect(prnt_stmt.subnodes.size).to eq(3)
|
128
|
+
expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
|
129
|
+
expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
|
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!')
|
133
|
+
expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
|
134
|
+
expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
|
135
|
+
expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
|
136
|
+
expect(eof.symbol.name).to eq('EOF')
|
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
|
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
|
+
end # describe
|
307
|
+
end # module
|
308
|
+
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
|
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.0.
|
4
|
+
version: 0.0.10
|
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
|
@@ -84,6 +84,12 @@ files:
|
|
84
84
|
- README.md
|
85
85
|
- Rakefile
|
86
86
|
- lib/loxxy.rb
|
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
|
87
93
|
- lib/loxxy/datatype/all_datatypes.rb
|
88
94
|
- lib/loxxy/datatype/boolean.rb
|
89
95
|
- lib/loxxy/datatype/builtin_datatype.rb
|
@@ -94,11 +100,13 @@ files:
|
|
94
100
|
- lib/loxxy/datatype/true.rb
|
95
101
|
- lib/loxxy/front_end/grammar.rb
|
96
102
|
- lib/loxxy/front_end/literal.rb
|
103
|
+
- lib/loxxy/front_end/parser.rb
|
97
104
|
- lib/loxxy/front_end/raw_parser.rb
|
98
105
|
- lib/loxxy/front_end/scanner.rb
|
99
106
|
- lib/loxxy/version.rb
|
100
107
|
- loxxy.gemspec
|
101
108
|
- spec/datatype/lx_string_spec.rb
|
109
|
+
- spec/front_end/parser_spec.rb
|
102
110
|
- spec/front_end/raw_parser_spec.rb
|
103
111
|
- spec/front_end/scanner_spec.rb
|
104
112
|
- spec/loxxy_spec.rb
|
@@ -129,6 +137,7 @@ specification_version: 4
|
|
129
137
|
summary: An implementation of the Lox programming language. WIP
|
130
138
|
test_files:
|
131
139
|
- spec/datatype/lx_string_spec.rb
|
140
|
+
- spec/front_end/parser_spec.rb
|
132
141
|
- spec/front_end/raw_parser_spec.rb
|
133
142
|
- spec/front_end/scanner_spec.rb
|
134
143
|
- spec/loxxy_spec.rb
|