loxxy 0.0.15 → 0.0.20
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 +1 -1
- data/CHANGELOG.md +69 -1
- data/README.md +140 -48
- data/lib/loxxy/ast/all_lox_nodes.rb +6 -1
- data/lib/loxxy/ast/ast_builder.rb +44 -3
- data/lib/loxxy/ast/ast_visitor.rb +54 -2
- data/lib/loxxy/ast/lox_binary_expr.rb +1 -1
- data/lib/loxxy/ast/lox_grouping_expr.rb +23 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +35 -0
- data/lib/loxxy/ast/lox_logical_expr.rb +28 -0
- data/lib/loxxy/ast/lox_unary_expr.rb +27 -0
- data/lib/loxxy/back_end/engine.rb +60 -3
- data/lib/loxxy/datatype/builtin_datatype.rb +55 -0
- data/lib/loxxy/datatype/false.rb +22 -0
- data/lib/loxxy/datatype/lx_string.rb +3 -4
- data/lib/loxxy/datatype/nil.rb +22 -0
- data/lib/loxxy/datatype/number.rb +98 -15
- data/lib/loxxy/datatype/true.rb +8 -0
- data/lib/loxxy/front_end/grammar.rb +5 -5
- data/lib/loxxy/version.rb +1 -1
- data/spec/front_end/parser_spec.rb +4 -4
- data/spec/interpreter_spec.rb +208 -9
- metadata +6 -2
data/lib/loxxy/datatype/true.rb
CHANGED
@@ -19,6 +19,14 @@ module Loxxy
|
|
19
19
|
def true?
|
20
20
|
true
|
21
21
|
end
|
22
|
+
|
23
|
+
# Check for equality of a Lox True with another Lox object / Ruby true
|
24
|
+
# @param other [Datatype::True, TrueClass, Object]
|
25
|
+
# @return [Datatype::Boolean]
|
26
|
+
def ==(other)
|
27
|
+
thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
|
28
|
+
thruthy ? True.instance : False.instance
|
29
|
+
end
|
22
30
|
end # class
|
23
31
|
|
24
32
|
True.instance.freeze # Make the sole instance immutable
|
@@ -68,9 +68,9 @@ module Loxxy
|
|
68
68
|
rule('forTest' => 'expression_opt SEMICOLON')
|
69
69
|
rule('forUpdate' => 'expression_opt')
|
70
70
|
|
71
|
-
rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
|
72
|
-
rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN')
|
73
|
-
rule('elsePart_opt' => 'ELSE statement')
|
71
|
+
rule('ifStmt' => 'IF ifCondition statement elsePart_opt').as 'if_stmt'
|
72
|
+
rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN').as 'keep_symbol2'
|
73
|
+
rule('elsePart_opt' => 'ELSE statement').as 'keep_symbol2'
|
74
74
|
rule('elsePart_opt' => [])
|
75
75
|
|
76
76
|
rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
|
@@ -121,7 +121,7 @@ module Loxxy
|
|
121
121
|
rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
|
122
122
|
rule('multOp' => 'SLASH')
|
123
123
|
rule('multOp' => 'STAR')
|
124
|
-
rule('unary' => 'unaryOp unary')
|
124
|
+
rule('unary' => 'unaryOp unary').as 'unary_expr'
|
125
125
|
rule('unary' => 'call')
|
126
126
|
rule('unaryOp' => 'BANG')
|
127
127
|
rule('unaryOp' => 'MINUS')
|
@@ -138,7 +138,7 @@ module Loxxy
|
|
138
138
|
rule('primary' => 'NUMBER').as 'literal_expr'
|
139
139
|
rule('primary' => 'STRING').as 'literal_expr'
|
140
140
|
rule('primary' => 'IDENTIFIER')
|
141
|
-
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
|
141
|
+
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
|
142
142
|
rule('primary' => 'SUPER DOT IDENTIFIER')
|
143
143
|
|
144
144
|
# Utility rules
|
data/lib/loxxy/version.rb
CHANGED
@@ -262,12 +262,12 @@ LOX_END
|
|
262
262
|
end # context
|
263
263
|
|
264
264
|
context 'Parsing logical expressions' do
|
265
|
-
it 'should parse the logical operations
|
265
|
+
it 'should parse the logical operations between two sub-expression' do
|
266
266
|
%w[or and].each do |connector|
|
267
267
|
input = "5 > 2 #{connector} 3 <= 4;"
|
268
268
|
ptree = subject.parse(input)
|
269
269
|
expr = ptree.root
|
270
|
-
expect(expr).to be_kind_of(Ast::
|
270
|
+
expect(expr).to be_kind_of(Ast::LoxLogicalExpr)
|
271
271
|
expect(expr.operator).to eq(connector.to_sym)
|
272
272
|
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
273
273
|
expect(expr.operands[0].operator).to eq(:>)
|
@@ -284,9 +284,9 @@ LOX_END
|
|
284
284
|
input = '4 > 3 and 1 < 2 or 4 >= 5;'
|
285
285
|
ptree = subject.parse(input)
|
286
286
|
expr = ptree.root
|
287
|
-
expect(expr).to be_kind_of(Ast::
|
287
|
+
expect(expr).to be_kind_of(Ast::LoxLogicalExpr)
|
288
288
|
expect(expr.operator).to eq(:or) # or has lower precedence than and
|
289
|
-
expect(expr.operands[0]).to be_kind_of(Ast::
|
289
|
+
expect(expr.operands[0]).to be_kind_of(Ast::LoxLogicalExpr)
|
290
290
|
expect(expr.operands[0].operator).to eq(:and)
|
291
291
|
conjuncts = expr.operands[0].operands
|
292
292
|
expect(conjuncts[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
data/spec/interpreter_spec.rb
CHANGED
@@ -25,32 +25,231 @@ module Loxxy
|
|
25
25
|
end
|
26
26
|
end # context
|
27
27
|
|
28
|
-
context 'Evaluating
|
29
|
-
let(:hello_world) { 'print "Hello, world!";' }
|
30
|
-
|
31
|
-
it 'should evaluate core data types' do
|
32
|
-
result = subject.evaluate('true; // Not false')
|
33
|
-
expect(result).to be_kind_of(Loxxy::Datatype::True)
|
34
|
-
end
|
35
|
-
|
28
|
+
context 'Evaluating arithmetic operations code:' do
|
36
29
|
it 'should evaluate an addition of two numbers' do
|
37
30
|
result = subject.evaluate('123 + 456; // => 579')
|
38
31
|
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
39
32
|
expect(result == 579).to be_true
|
40
33
|
end
|
41
34
|
|
42
|
-
it 'should evaluate a
|
35
|
+
it 'should evaluate a subtraction of two numbers' do
|
43
36
|
result = subject.evaluate('4 - 3; // => 1')
|
44
37
|
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
45
38
|
expect(result == 1).to be_true
|
46
39
|
end
|
47
40
|
|
41
|
+
it 'should evaluate a multiplication of two numbers' do
|
42
|
+
result = subject.evaluate('5 * 3; // => 15')
|
43
|
+
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
44
|
+
expect(result == 15).to be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should evaluate a division of two numbers' do
|
48
|
+
result = subject.evaluate('8 / 2; // => 4')
|
49
|
+
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
50
|
+
expect(result == 4).to be_true
|
51
|
+
end
|
52
|
+
end # context
|
53
|
+
|
54
|
+
context 'Evaluating Lox code:' do
|
55
|
+
let(:hello_world) { 'print "Hello, world!";' }
|
56
|
+
|
57
|
+
it 'should evaluate core data types' do
|
58
|
+
result = subject.evaluate('true; // Not false')
|
59
|
+
expect(result).to be_kind_of(Loxxy::Datatype::True)
|
60
|
+
end
|
61
|
+
|
48
62
|
it 'should evaluate string concatenation' do
|
49
63
|
result = subject.evaluate('"str" + "ing"; // => "string"')
|
50
64
|
expect(result).to be_kind_of(Loxxy::Datatype::LXString)
|
51
65
|
expect(result == 'string').to be_true
|
52
66
|
end
|
53
67
|
|
68
|
+
it 'should perform the equality tests of two values' do
|
69
|
+
[
|
70
|
+
['nil == nil;', true],
|
71
|
+
['true == true;', true],
|
72
|
+
['true == false;', false],
|
73
|
+
['1 == 1;', true],
|
74
|
+
['1 == 2;', false],
|
75
|
+
['0 == 0;', true],
|
76
|
+
['"str" == "str";', true],
|
77
|
+
['"str" == "ing";', false],
|
78
|
+
['"" == "";', true],
|
79
|
+
['nil == false;', false],
|
80
|
+
['false == 0;', false],
|
81
|
+
['0 == "0";', false]
|
82
|
+
].each do |(source, predicted)|
|
83
|
+
lox = Loxxy::Interpreter.new
|
84
|
+
result = lox.evaluate(source)
|
85
|
+
expect(result.value == predicted).to be_truthy
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should perform the inequality test of two values' do
|
90
|
+
[
|
91
|
+
['nil != nil;', false],
|
92
|
+
['true != true;', false],
|
93
|
+
['true != false;', true],
|
94
|
+
['1 != 1;', false],
|
95
|
+
['1 != 2;', true],
|
96
|
+
['0 != 0;', false],
|
97
|
+
['"str" != "str";', false],
|
98
|
+
['"str" != "ing";', true],
|
99
|
+
['"" != "";', false],
|
100
|
+
['nil != false;', true],
|
101
|
+
['false != 0;', true],
|
102
|
+
['0 != "0";', true]
|
103
|
+
].each do |(source, predicted)|
|
104
|
+
lox = Loxxy::Interpreter.new
|
105
|
+
result = lox.evaluate(source)
|
106
|
+
expect(result.value == predicted).to be_truthy
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should evaluate a comparison of two numbers' do
|
111
|
+
[
|
112
|
+
['1 < 2;', true],
|
113
|
+
['2 < 2;', false],
|
114
|
+
['2 < 1;', false],
|
115
|
+
['1 <= 2;', true],
|
116
|
+
['2 <= 2;', true],
|
117
|
+
['2 <= 1;', false],
|
118
|
+
['1 > 2;', false],
|
119
|
+
['2 > 2;', false],
|
120
|
+
['2 > 1;', true],
|
121
|
+
['1 >= 2;', false],
|
122
|
+
['2 >= 2;', true],
|
123
|
+
['2 >= 1;', true],
|
124
|
+
['0 < -0;', false],
|
125
|
+
['-0 < 0;', false],
|
126
|
+
['0 > -0;', false],
|
127
|
+
['-0 > 0;', false],
|
128
|
+
['0 <= -0;', true],
|
129
|
+
['-0 <= 0;', true],
|
130
|
+
['0 >= -0;', true],
|
131
|
+
['-0 >= 0;', true]
|
132
|
+
].each do |(source, predicted)|
|
133
|
+
lox = Loxxy::Interpreter.new
|
134
|
+
result = lox.evaluate(source)
|
135
|
+
expect(result.value == predicted).to be_truthy
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should evaluate the change sign of a number' do
|
140
|
+
[
|
141
|
+
['- 3;', -3],
|
142
|
+
['- - 3;', 3],
|
143
|
+
['- - - 3;', -3]
|
144
|
+
].each do |(source, predicted)|
|
145
|
+
lox = Loxxy::Interpreter.new
|
146
|
+
result = lox.evaluate(source)
|
147
|
+
expect(result.value == predicted).to be_truthy
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should evaluate the negation of an object' do
|
152
|
+
[
|
153
|
+
['!true;', false],
|
154
|
+
['!false;', true],
|
155
|
+
['!!true;', true],
|
156
|
+
['!123;', false],
|
157
|
+
['!0;', false],
|
158
|
+
['!nil;', true],
|
159
|
+
['!"";', false]
|
160
|
+
].each do |(source, predicted)|
|
161
|
+
lox = Loxxy::Interpreter.new
|
162
|
+
result = lox.evaluate(source)
|
163
|
+
expect(result.value == predicted).to be_truthy
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should evaluate the "conjunction" of two values' do
|
168
|
+
[
|
169
|
+
# Return the first falsey argument
|
170
|
+
['false and 1;', false],
|
171
|
+
['nil and 1;', nil],
|
172
|
+
['true and 1;', 1],
|
173
|
+
['1 and 2 and false;', false],
|
174
|
+
['1 and 2 and nil;', nil],
|
175
|
+
|
176
|
+
# Return the last argument if all are truthy
|
177
|
+
['1 and true;', true],
|
178
|
+
['0 and true;', true],
|
179
|
+
['"false" and 0;', 0],
|
180
|
+
['1 and 2 and 3;', 3]
|
181
|
+
|
182
|
+
# TODO test short-circuit at first false argument
|
183
|
+
].each do |(source, predicted)|
|
184
|
+
lox = Loxxy::Interpreter.new
|
185
|
+
result = lox.evaluate(source)
|
186
|
+
expect(result.value == predicted).to be_truthy
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should evaluate the "disjunction" of two values' do
|
191
|
+
[
|
192
|
+
# Return the first truthy argument
|
193
|
+
['1 or true;', 1],
|
194
|
+
['false or 1;', 1],
|
195
|
+
['nil or 1;', 1],
|
196
|
+
['false or false or true;', true],
|
197
|
+
['1 and 2 and nil;', nil],
|
198
|
+
|
199
|
+
# Return the last argument if all are falsey
|
200
|
+
['false or false;', false],
|
201
|
+
['nil or false;', false],
|
202
|
+
['false or nil;', nil],
|
203
|
+
['false or false or false;', false],
|
204
|
+
['false or false or nil;', nil]
|
205
|
+
|
206
|
+
# TODO test short-circuit at first false argument
|
207
|
+
].each do |(source, predicted)|
|
208
|
+
lox = Loxxy::Interpreter.new
|
209
|
+
result = lox.evaluate(source)
|
210
|
+
expect(result.value == predicted).to be_truthy
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'should supprt expressions between parentheses' do
|
215
|
+
[
|
216
|
+
['3 + 4 * 5;', 23],
|
217
|
+
['(3 + 4) * 5;', 35],
|
218
|
+
['(5 - (3 - 1)) + -(1);', 2]
|
219
|
+
].each do |(source, predicted)|
|
220
|
+
lox = Loxxy::Interpreter.new
|
221
|
+
result = lox.evaluate(source)
|
222
|
+
expect(result.value == predicted).to be_truthy
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'should evaluate an if statement' do
|
227
|
+
[
|
228
|
+
# Evaluate the 'then' expression if the condition is true.
|
229
|
+
['if (true) print "then-branch";', 'then-branch'],
|
230
|
+
['if (false) print "ignored";', ''],
|
231
|
+
# TODO: test with then block body
|
232
|
+
# TODO: test with assignment in if condition
|
233
|
+
|
234
|
+
# Evaluate the 'else' expression if the condition is false.
|
235
|
+
['if (true) print "then-branch"; else print "else-branch";', 'then-branch'],
|
236
|
+
['if (false) print "then-branch"; else print "else-branch";', 'else-branch'],
|
237
|
+
['if (0) print "then-branch"; else print "else-branch";', 'then-branch'],
|
238
|
+
['if (nil) print "then-branch"; else print "else-branch";', 'else-branch'],
|
239
|
+
# TODO: test with else block body
|
240
|
+
|
241
|
+
# TODO: A dangling else binds to the right-most if.
|
242
|
+
# ['if (true) if (false) print "bad"; else print "good";', 'good'],
|
243
|
+
# ['if (false) if (true) print "bad"; else print "worse";', 'bad']
|
244
|
+
].each do |(source, predicted)|
|
245
|
+
io = StringIO.new
|
246
|
+
cfg = { ostream: io }
|
247
|
+
lox = Loxxy::Interpreter.new(cfg)
|
248
|
+
result = lox.evaluate(source)
|
249
|
+
expect(io.string).to eq(predicted)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
54
253
|
it 'should print the hello world message' do
|
55
254
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
56
255
|
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|
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.20
|
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-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -89,10 +89,14 @@ files:
|
|
89
89
|
- lib/loxxy/ast/ast_visitor.rb
|
90
90
|
- lib/loxxy/ast/lox_binary_expr.rb
|
91
91
|
- lib/loxxy/ast/lox_compound_expr.rb
|
92
|
+
- lib/loxxy/ast/lox_grouping_expr.rb
|
93
|
+
- lib/loxxy/ast/lox_if_stmt.rb
|
92
94
|
- lib/loxxy/ast/lox_literal_expr.rb
|
95
|
+
- lib/loxxy/ast/lox_logical_expr.rb
|
93
96
|
- lib/loxxy/ast/lox_node.rb
|
94
97
|
- lib/loxxy/ast/lox_noop_expr.rb
|
95
98
|
- lib/loxxy/ast/lox_print_stmt.rb
|
99
|
+
- lib/loxxy/ast/lox_unary_expr.rb
|
96
100
|
- lib/loxxy/back_end/engine.rb
|
97
101
|
- lib/loxxy/datatype/all_datatypes.rb
|
98
102
|
- lib/loxxy/datatype/boolean.rb
|