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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de9d3c24db15863c6ea99c2a9c8a2e413b2a243ddf79de28da5583d1944fc4e4
4
- data.tar.gz: a5f6dacfd67b6833e3c051273765e1d94c95d1d1700a974a09a3caf0519a1570
3
+ metadata.gz: 583f5c2f620ea8f45b339607103955eda2f32c02f0358ccb88cf4d20b4398d21
4
+ data.tar.gz: f349b949c1315473027eaaab589ff1cf2da89deca3b243cd1508c088bf9a9cc5
5
5
  SHA512:
6
- metadata.gz: 3c4bd62b1a9cd960ee97d807a5f44a26cc360df4344975210ca967117c62585a2138fef382ffb5ea7d79f002b9a9a951ba77622cb8d799973c0ad8a24ffbae52
7
- data.tar.gz: 5b14b5c4a6ae98f5e1d9a819160393393654cc3d817e36f1da68cd12fcfedfbce47097463dc6babcf36b5582e970ea41b4cef5ebceb768b962c8bd7b02bcb553
6
+ metadata.gz: 9c93648c634783348a22b3b65cf710a59c0ca5e3cefc5a20d7c60b9b429b4334e1e5603cc2bf74f302aed881daa71634cb7253445b3f376838197637f79f8cc7
7
+ data.tar.gz: 67d60e89985322911a7842a2b5fc2e22b4ccc3136ffa216027089ea05ea56a6932d1de9b8917173ad072695aaa6abb2147d456f54d09f2878d7831937c42af74
@@ -170,6 +170,9 @@ Naming/BlockParameterName:
170
170
 
171
171
  Naming/MethodParameterName:
172
172
  Enabled: false
173
+
174
+ Naming/MethodName:
175
+ Enabled: false
173
176
 
174
177
  Naming/VariableName:
175
178
  Enabled: false
@@ -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
- # Loxxy
1
+ # loxxy
2
+ [![Gem Version](https://badge.fury.io/rb/loxxy.svg)](https://badge.fury.io/rb/loxxy)
3
+ [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](https://github.com/famished-tiger/loxxy/blob/main/LICENSE.txt)
2
4
 
3
- A Ruby implementation of the [Lox programming language](https://craftinginterpreters.com/the-lox-language.html),
4
- a simple language used in Bob Nystrom's online book [Crafting Interpreters](https://craftinginterpreters.com/).
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 (LoxLox)[https://github.com/benhoyt/loxlox],
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
- ## Roadmap
13
- - [DONE] Scanner (tokenizer)
14
- - [DONE] Raw parser. It parses Lox programs and generates a parse tree.
15
- - [DONE] Tailored parser for generating AST (Abstract Syntax Tree)
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
- ### Example using RawParser class
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
- +-- declaration_star
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
- | | | +-- equality
62
- | | | | +-- comparison
63
- | | | | | +-- term
64
- | | | | | | +-- factor
65
- | | | | | | | +-- unary
66
- | | | | | | | | +-- call
67
- | | | | | | | | +-- primary
68
- | | | | | | | | | +-- STRING: '"Hello, world!"'
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, ther is the [lox](https://github.com/rdodson41/ruby-lox) gem.
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
- # Terminal2NodeClass = {
12
- # 'FALSE' => Datatype::False,
13
- # 'NIL' => Datatype::Nil,
14
- # 'NUMBER' => Datatype::Number,
15
- # 'STRING' => Datatype::LXString,
16
- # 'TRUE' => Datatype::True
17
- # }.freeze
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' => 'declaration_star EOF')
29
+ rule('program' => 'EOF')
30
+ rule('program' => 'declaration_plus EOF')
30
31
 
31
32
  # Declarations: bind an identifier to something
32
- rule('declaration_star' => 'declaration_star declaration')
33
- rule('declaration_star' => [])
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 declaration_star RIGHT_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 disjunct_star')
89
- rule('disjunct_star' => 'disjunct_star OR logic_and')
90
- rule('disjunct_star' => [])
91
- rule('logic_and' => 'equality conjunct_star')
92
- rule('conjunct_star' => 'conjunct_star AND equality')
93
- rule('conjunct_star' => [])
94
- rule('equality' => 'comparison equalityTest_star')
95
- rule('equalityTest_star' => 'equalityTest_star equalityTest comparison')
96
- rule('equalityTest_star' => [])
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 comparisonTest_star')
100
- rule('comparisonTest_star' => 'comparisonTest_star comparisonTest term')
101
- rule('comparisonTest_star' => [])
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 additive_star')
107
- rule('additive_star' => 'additive_star additionOp factor')
108
- rule('additive_star' => [])
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 multiplicative_star')
112
- rule('multiplicative_star' => 'multiplicative_star multOp unary')
113
- rule('multiplicative_star' => [])
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 refinement_star')
121
- rule('refinement_star' => 'refinement_star refinement')
122
- rule('refinement_star' => [])
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')
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.6'
4
+ VERSION = '0.0.11'
5
5
  end
@@ -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('program')
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 expressions:' do
56
- # Utility method to walk towards deeply nested node
57
- # @param aNTNode [Rley::PTree::NonTerminalNode]
58
- # @param subnodePath[Array<Integer>] An Array of subnode indices
59
- def walk_subnodes(aNTNode, subnodePath)
60
- curr_node = aNTNode
61
- subnodePath.each do |index|
62
- curr_node = curr_node.subnodes[index]
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
- curr_node
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
- (decls, eof) = root.subnodes
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(Rley::PTree::NonTerminalNode)
84
- # leaf_node = walk_subnodes(prnt_stmt, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
85
- # expect(leaf_node).to be_kind_of(Rley::PTree::TerminalNode)
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
- (decls, eof) = aParseTree.root.subnodes
30
- expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
31
- expect(decls.symbol.name).to eq('declaration_star')
32
- expect(decls.subnodes).to be_empty
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('declaration_star')
80
- stmt = decls.subnodes[1].subnodes[0]
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.6
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-03 00:00:00.000000000 Z
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