loxxy 0.0.19 → 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/CHANGELOG.md +14 -1
- data/README.md +38 -2
- data/lib/loxxy/ast/all_lox_nodes.rb +1 -0
- data/lib/loxxy/ast/ast_builder.rb +8 -0
- data/lib/loxxy/ast/ast_visitor.rb +8 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +35 -0
- data/lib/loxxy/back_end/engine.rb +11 -0
- data/lib/loxxy/front_end/grammar.rb +3 -3
- data/lib/loxxy/version.rb +1 -1
- data/spec/interpreter_spec.rb +27 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6628d902c258d0aa2d589fd0960ab812addbfe8f0fbf162cfb627a90fbc1a40
|
4
|
+
data.tar.gz: c1fe1f5ce4b3dc7109c70783b78953fd72ffbbd837d58f8350d2a03615f80222
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 301160cc1a00e819cc13cfb3273985b6af1ddadd94b236e4e7be0b346744355327be69d3b2d338ece3b6148a905d10a91dc3f6ef828912b7750efcdb0f4e5238
|
7
|
+
data.tar.gz: cc5bb436b6e487d7752180d42d046d5a09a6817878428d757b65b26f2955133985598b55e7d63c13d249ebef110914a3a2b8292777a2857b15dcba885c74c106
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
## [0.0.
|
1
|
+
## [0.0.20] - 2021-01-15
|
2
|
+
- The interpreter supports the `if` ... `else` statement.
|
3
|
+
|
4
|
+
## Added
|
5
|
+
- Class `Ast::LoxItStmt`, AST node specific for `if` `else` statements
|
6
|
+
- Method `Ast::ASTBuilder#reduce_if_stmt` as semantic action for if ... else
|
7
|
+
- Method `Ast::ASTVisitor#visit_if_stmt` for visiting `LoxIfStmt` nodes
|
8
|
+
- Method `Engine::after_if_stmt` implementation of the control flow
|
9
|
+
|
10
|
+
## [0.0.19] - 2021-01-14
|
2
11
|
- The interpreter supports expressions between parentheses (grouping).
|
3
12
|
|
4
13
|
## Added
|
@@ -7,6 +16,10 @@
|
|
7
16
|
- Method `Ast::ASTVisitor#visit_grouping_expr` for visiting grouping expressions
|
8
17
|
- Method `Engine::after_grouping_expr`for the evaluation of grouping expressions
|
9
18
|
|
19
|
+
## Changed
|
20
|
+
- File `grammar.rb` rules for if ... else were given names in order to activate semantic actions.
|
21
|
+
- File `README.md` updated with little `if ... else` documentation.
|
22
|
+
|
10
23
|
## [0.0.18] - 2021-01-13
|
11
24
|
- The interpreter can evaluate `and`, `or`expressions.
|
12
25
|
|
data/README.md
CHANGED
@@ -174,6 +174,7 @@ Loxxy supports the following statements:
|
|
174
174
|
-[Comparison expressions](#comparison-expressions)
|
175
175
|
-[Logical expressions](#logical-expressions)
|
176
176
|
-[Grouping expressions](#grouping-expressions)
|
177
|
+
-[If Statement](#if-statement)
|
177
178
|
-[Print Statement](#print-statement)
|
178
179
|
|
179
180
|
#### Expressions
|
@@ -245,7 +246,7 @@ REMINDER: In __Lox__, `false` and `nil` are considered falsey, everything else i
|
|
245
246
|
`!!true; // => true`
|
246
247
|
`!0; // => false`
|
247
248
|
|
248
|
-
|
249
|
+
#### Grouping expressions
|
249
250
|
Use parentheses `(` `)` for a better control in expression/operator precedence.
|
250
251
|
|
251
252
|
``` javascript
|
@@ -253,8 +254,43 @@ print 3 + 4 * 5; // => 23
|
|
253
254
|
print (3 + 4) * 5; // => 35
|
254
255
|
```
|
255
256
|
|
257
|
+
#### If statement
|
256
258
|
|
257
|
-
|
259
|
+
Based on a given condition, an if statement executes one of two statements:
|
260
|
+
``` javascript
|
261
|
+
if (condition) {
|
262
|
+
print "then-branch";
|
263
|
+
} else {
|
264
|
+
print "else-branch";
|
265
|
+
}
|
266
|
+
```
|
267
|
+
|
268
|
+
As for other languages, the `else` part is optional.
|
269
|
+
##### Warning: nested `if`...`else`
|
270
|
+
Call it a bug ... Nested `if` `else` control flow structure aren't yet supported by __Loxxy__.
|
271
|
+
The culprit has a name: [the dangling else](https://en.wikipedia.org/wiki/Dangling_else).
|
272
|
+
|
273
|
+
The problem in a nutshell: in a nested if ... else ... statement like this:
|
274
|
+
``` javascript
|
275
|
+
'if (true) if (false) print "bad"; else print "good";
|
276
|
+
```
|
277
|
+
... there is an ambiguity. Indeed, according to the __Lox__ grammar, the `else` could be bound
|
278
|
+
either to the first `if` or to the second one.
|
279
|
+
This ambiguity is usually lifted by applying an ad-hoc rule: an `else` is aways bound to the most
|
280
|
+
recent (rightmost) `if`.
|
281
|
+
Being a generic parsing library, `Rley` doesn't apply any of these supplemental rules.
|
282
|
+
As a consequence,it complains about the found ambiguity and stops the parsing...
|
283
|
+
Although `Rley` can cope with ambiguities, this requires the use of an advanced data structure
|
284
|
+
called `Shared Packed Parse Forest (SPPF)`.
|
285
|
+
SPPF are much more complex to handle than the `common` parse trees present in most compiler or interpreter books.
|
286
|
+
Therefore, a future version of `Rley` will incorporate the capability to define disambuiguation rules.
|
287
|
+
|
288
|
+
In the meantime, the `Loxxy` will progress on other __Lox__ features like:
|
289
|
+
- Variables,
|
290
|
+
- Block structures...
|
291
|
+
|
292
|
+
|
293
|
+
#### Print Statement
|
258
294
|
|
259
295
|
The statement print + expression + ; prints the result of the expression to stdout.
|
260
296
|
|
@@ -158,6 +158,14 @@ module Loxxy
|
|
158
158
|
return_first_child(range, tokens, theChildren) # Discard the semicolon
|
159
159
|
end
|
160
160
|
|
161
|
+
# rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
|
162
|
+
def reduce_if_stmt(_production, _range, tokens, theChildren)
|
163
|
+
condition = theChildren[1]
|
164
|
+
then_stmt = theChildren[2]
|
165
|
+
else_stmt = theChildren[3]
|
166
|
+
LoxIfStmt.new(tokens[0].position, condition, then_stmt, else_stmt)
|
167
|
+
end
|
168
|
+
|
161
169
|
# rule('printStmt' => 'PRINT expression SEMICOLON')
|
162
170
|
def reduce_print_stmt(_production, _range, tokens, theChildren)
|
163
171
|
Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
|
@@ -51,6 +51,14 @@ module Loxxy
|
|
51
51
|
broadcast(:after_ptree, aParseTree)
|
52
52
|
end
|
53
53
|
|
54
|
+
# Visit event. The visitor is about to visit a if statement.
|
55
|
+
# @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
|
56
|
+
def visit_if_stmt(anIfStmt)
|
57
|
+
broadcast(:before_if_stmt, anIfStmt)
|
58
|
+
traverse_subnodes(anIfStmt) # The condition is visited/evaluated here...
|
59
|
+
broadcast(:after_if_stmt, anIfStmt, self)
|
60
|
+
end
|
61
|
+
|
54
62
|
# Visit event. The visitor is about to visit a print statement.
|
55
63
|
# @param aPrintStmt [AST::LOXPrintStmt] the print statement node to visit
|
56
64
|
def visit_print_stmt(aPrintStmt)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxIfStmt < LoxCompoundExpr
|
8
|
+
# @return [LoxNode] code of then branch
|
9
|
+
attr_reader :then_stmt
|
10
|
+
|
11
|
+
# @return [LoxNode, NilClass] code of else branch
|
12
|
+
attr_reader :else_stmt
|
13
|
+
|
14
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
|
+
# @param subExpr [Loxxy::Ast::LoxNode]
|
16
|
+
def initialize(aPosition, condExpr, thenStmt, elseStmt)
|
17
|
+
super(aPosition, [condExpr])
|
18
|
+
@then_stmt = thenStmt
|
19
|
+
@else_stmt = elseStmt
|
20
|
+
end
|
21
|
+
|
22
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
23
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
24
|
+
def accept(visitor)
|
25
|
+
visitor.visit_if_stmt(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Accessor to the condition expression
|
29
|
+
# @return [LoxNode]
|
30
|
+
def condition
|
31
|
+
subnodes[0]
|
32
|
+
end
|
33
|
+
end # class
|
34
|
+
end # module
|
35
|
+
end # module
|
@@ -33,7 +33,18 @@ module Loxxy
|
|
33
33
|
stack.empty? ? Datatype::Nil.instance : stack.pop
|
34
34
|
end
|
35
35
|
|
36
|
+
##########################################################################
|
36
37
|
# Visit event handling
|
38
|
+
##########################################################################
|
39
|
+
def after_if_stmt(anIfStmt, aVisitor)
|
40
|
+
# Retrieve the result of the condition evaluation
|
41
|
+
condition = stack.pop
|
42
|
+
if condition.truthy?
|
43
|
+
result = anIfStmt.then_stmt.accept(aVisitor)
|
44
|
+
elsif anIfStmt.else_stmt
|
45
|
+
anIfStmt.else_stmt.accept(aVisitor)
|
46
|
+
end
|
47
|
+
end
|
37
48
|
|
38
49
|
def after_print_stmt(_printStmt)
|
39
50
|
tos = stack.pop
|
@@ -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'
|
data/lib/loxxy/version.rb
CHANGED
data/spec/interpreter_spec.rb
CHANGED
@@ -223,6 +223,33 @@ module Loxxy
|
|
223
223
|
end
|
224
224
|
end
|
225
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
|
+
|
226
253
|
it 'should print the hello world message' do
|
227
254
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
228
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
|
@@ -90,6 +90,7 @@ files:
|
|
90
90
|
- lib/loxxy/ast/lox_binary_expr.rb
|
91
91
|
- lib/loxxy/ast/lox_compound_expr.rb
|
92
92
|
- lib/loxxy/ast/lox_grouping_expr.rb
|
93
|
+
- lib/loxxy/ast/lox_if_stmt.rb
|
93
94
|
- lib/loxxy/ast/lox_literal_expr.rb
|
94
95
|
- lib/loxxy/ast/lox_logical_expr.rb
|
95
96
|
- lib/loxxy/ast/lox_node.rb
|