loxxy 0.0.26 → 0.0.27
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 +15 -1
- data/README.md +12 -2
- data/lib/loxxy/ast/all_lox_nodes.rb +1 -0
- data/lib/loxxy/ast/ast_builder.rb +23 -0
- data/lib/loxxy/ast/ast_visitor.rb +8 -0
- data/lib/loxxy/ast/lox_for_stmt.rb +42 -0
- data/lib/loxxy/back_end/engine.rb +17 -0
- data/lib/loxxy/front_end/grammar.rb +4 -4
- data/lib/loxxy/version.rb +1 -1
- data/spec/interpreter_spec.rb +38 -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: 47ae4c0af1698de7187c530ec5feec9390a664ff681aae6c47a36e351bda229d
|
4
|
+
data.tar.gz: ff309645b8e71608e6a3801d4a4be558b8e86d59d658af4ec2eb796989e88455
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73bea958f39a8cb97ed2e59c049ab58006845519a642d20e72cbdd554be60cb3b553f84dcdd64a36410796f83e87bb3239eee9933148b821b074916f4198a810
|
7
|
+
data.tar.gz: a3235fd86a5c99c0ecf581be106e53d59a0643b3ac6299fbd0eccd0f5eacc5a15b63df3a22fb4edef8b30314fa0360432b017d9a2417668e365e2b9dd3f8b2b8
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,22 @@
|
|
1
|
+
## [0.0.27] - 2021-01-24
|
2
|
+
- The interpreter implements `while` loops.
|
3
|
+
|
4
|
+
### Added
|
5
|
+
- Class `Ast::LoxForStmt` a node that represents a `for` statement
|
6
|
+
- Method `Ast::ASTBuilder#reduce_for_stmt`
|
7
|
+
- Method `Ast::ASTBuilder#reduce_for_control` creates an `Ast::LoxForStmt` node
|
8
|
+
- Method `Ast::ASTVisitor#visit_for_stmt` for visiting an `Ast::LoxWhileStmt` node
|
9
|
+
- Method `BackEnd::Engine#before_for_stmt` builds a new environment for the loop variable
|
10
|
+
- Method `BackEnd::Engine#after_for_stmt` implements most of the `for` control flow
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- File `README.md` updated.
|
14
|
+
|
1
15
|
## [0.0.26] - 2021-01-22
|
2
16
|
- The interpreter implements `while` loops.
|
3
17
|
|
4
18
|
### Added
|
5
|
-
- Class `Ast::LoxWhileStmt` a node that represents a while statement
|
19
|
+
- Class `Ast::LoxWhileStmt` a node that represents a `while` statement
|
6
20
|
- Method `Ast::ASTBuilder#reduce_while_stmt` creates an `Ast::LoxWhileStmt` node
|
7
21
|
- Method `Ast::ASTVisitor#visit_while_stmt` for visiting an `Ast::LoxWhileStmt` node
|
8
22
|
- Method `BackEnd::Engine#after_while_stmt` implements the while looping structure
|
data/README.md
CHANGED
@@ -14,7 +14,7 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
|
|
14
14
|
|
15
15
|
### Current status
|
16
16
|
The project is still in inception and the interpreter is being implemented...
|
17
|
-
Currently it can execute a
|
17
|
+
Currently it can execute a subset of __Lox__ language.
|
18
18
|
|
19
19
|
But the __loxxy__ gem hosts also a parser class `RawPaser` that can parse, in principle, any valid Lox input.
|
20
20
|
|
@@ -141,7 +141,8 @@ Here are the language features currently supported by the interpreter:
|
|
141
141
|
- [Datatypes](#datatypes)
|
142
142
|
- [Statements](#statements)
|
143
143
|
-[Expressions](#expressions)
|
144
|
-
- [Variable declarations](#var-statement)
|
144
|
+
- [Variable declarations](#var-statement)
|
145
|
+
- [For statement](#for-statement)
|
145
146
|
- [If Statement](#if-statement)
|
146
147
|
- [Print Statement](#print-statement)
|
147
148
|
- [While Statement](#while-statement)
|
@@ -279,6 +280,15 @@ var iAmNil; // __Lox__ initializes variables to nil by default;
|
|
279
280
|
print iAmNil; // output: nil
|
280
281
|
```
|
281
282
|
|
283
|
+
#### For statement
|
284
|
+
|
285
|
+
Similar to the `for` statement in `C` language
|
286
|
+
``` javascript
|
287
|
+
for (var a = 1; a < 10; a = a + 1) {
|
288
|
+
print a; // Output: 123456789
|
289
|
+
}
|
290
|
+
```
|
291
|
+
|
282
292
|
#### If statement
|
283
293
|
|
284
294
|
Based on a given condition, an if statement executes one of two statements:
|
@@ -180,6 +180,29 @@ module Loxxy
|
|
180
180
|
Ast::LoxVarStmt.new(tokens[1].position, var_name, theChildren[3])
|
181
181
|
end
|
182
182
|
|
183
|
+
# rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
|
184
|
+
def reduce_for_stmt(_production, _range, _tokens, theChildren)
|
185
|
+
for_stmt = theChildren[2]
|
186
|
+
for_stmt.body_stmt = theChildren[4]
|
187
|
+
for_stmt
|
188
|
+
end
|
189
|
+
|
190
|
+
# rule('forControl' => 'forInitialization forTest forUpdate')
|
191
|
+
def reduce_for_control(_production, _range, tokens, theChildren)
|
192
|
+
(init, test, update) = theChildren
|
193
|
+
Ast::LoxForStmt.new(tokens[0].position, init, test, update)
|
194
|
+
end
|
195
|
+
|
196
|
+
# rule('forInitialization' => 'SEMICOLON')
|
197
|
+
def reduce_empty_for_initialization(_production, _range, _tokens, _theChildren)
|
198
|
+
nil
|
199
|
+
end
|
200
|
+
|
201
|
+
# rule('forTest' => 'expression_opt SEMICOLON')
|
202
|
+
def reduce_for_test(_production, range, tokens, theChildren)
|
203
|
+
return_first_child(range, tokens, theChildren)
|
204
|
+
end
|
205
|
+
|
183
206
|
# rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
|
184
207
|
def reduce_if_stmt(_production, _range, tokens, theChildren)
|
185
208
|
condition = theChildren[1]
|
@@ -67,6 +67,14 @@ module Loxxy
|
|
67
67
|
broadcast(:after_var_stmt, aVarStmt)
|
68
68
|
end
|
69
69
|
|
70
|
+
# Visit event. The visitor is about to visit a for statement.
|
71
|
+
# @param aForStmt [AST::LOXForStmt] the for statement node to visit
|
72
|
+
def visit_for_stmt(aForStmt)
|
73
|
+
broadcast(:before_for_stmt, aForStmt)
|
74
|
+
traverse_subnodes(aForStmt) # The condition is visited/evaluated here...
|
75
|
+
broadcast(:after_for_stmt, aForStmt, self)
|
76
|
+
end
|
77
|
+
|
70
78
|
# Visit event. The visitor is about to visit a if statement.
|
71
79
|
# @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
|
72
80
|
def visit_if_stmt(anIfStmt)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxForStmt < LoxCompoundExpr
|
8
|
+
# @return [LoxNode] test expression
|
9
|
+
attr_reader :test_expr
|
10
|
+
|
11
|
+
# @return [LoxNode] update expression
|
12
|
+
attr_reader :update_expr
|
13
|
+
|
14
|
+
# @return [LoxNode] body statement
|
15
|
+
attr_accessor :body_stmt
|
16
|
+
|
17
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
18
|
+
# @param initialization [Loxxy::Ast::LoxNode]
|
19
|
+
# @param testExpr [Loxxy::Ast::LoxNode]
|
20
|
+
# @param updateExpr [Loxxy::Ast::LoxNode]
|
21
|
+
# @param body [Loxxy::Ast::LoxNode]
|
22
|
+
def initialize(aPosition, initialization, testExpr, updateExpr)
|
23
|
+
child = initialization ? [initialization] : []
|
24
|
+
super(aPosition, child)
|
25
|
+
@test_expr = testExpr
|
26
|
+
@update_expr = updateExpr
|
27
|
+
end
|
28
|
+
|
29
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
30
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
31
|
+
def accept(visitor)
|
32
|
+
visitor.visit_for_stmt(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Accessor to the condition expression
|
36
|
+
# @return [LoxNode]
|
37
|
+
def condition
|
38
|
+
subnodes[0]
|
39
|
+
end
|
40
|
+
end # class
|
41
|
+
end # module
|
42
|
+
end # module
|
@@ -51,6 +51,23 @@ module Loxxy
|
|
51
51
|
symbol_table.insert(new_var)
|
52
52
|
end
|
53
53
|
|
54
|
+
def before_for_stmt(aForStmt)
|
55
|
+
before_block_stmt(aForStmt)
|
56
|
+
end
|
57
|
+
|
58
|
+
def after_for_stmt(aForStmt, aVisitor)
|
59
|
+
loop do
|
60
|
+
aForStmt.test_expr.accept(aVisitor)
|
61
|
+
condition = stack.pop
|
62
|
+
break unless condition.truthy?
|
63
|
+
|
64
|
+
aForStmt.body_stmt.accept(aVisitor)
|
65
|
+
aForStmt.update_expr&.accept(aVisitor)
|
66
|
+
stack.pop
|
67
|
+
end
|
68
|
+
after_block_stmt(aForStmt)
|
69
|
+
end
|
70
|
+
|
54
71
|
def after_if_stmt(anIfStmt, aVisitor)
|
55
72
|
# Retrieve the result of the condition evaluation
|
56
73
|
condition = stack.pop
|
@@ -60,12 +60,12 @@ module Loxxy
|
|
60
60
|
|
61
61
|
rule('exprStmt' => 'expression SEMICOLON').as 'exprStmt'
|
62
62
|
|
63
|
-
rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
|
64
|
-
rule('forControl' => 'forInitialization forTest forUpdate')
|
63
|
+
rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement').as 'for_stmt'
|
64
|
+
rule('forControl' => 'forInitialization forTest forUpdate').as 'for_control'
|
65
65
|
rule('forInitialization' => 'varDecl')
|
66
66
|
rule('forInitialization' => 'exprStmt')
|
67
|
-
rule('forInitialization' => 'SEMICOLON')
|
68
|
-
rule('forTest' => 'expression_opt SEMICOLON')
|
67
|
+
rule('forInitialization' => 'SEMICOLON').as 'empty_for_initialization'
|
68
|
+
rule('forTest' => 'expression_opt SEMICOLON').as 'for_test'
|
69
69
|
rule('forUpdate' => 'expression_opt')
|
70
70
|
|
71
71
|
rule('ifStmt' => 'IF ifCondition statement elsePart_opt').as 'if_stmt'
|
data/lib/loxxy/version.rb
CHANGED
data/spec/interpreter_spec.rb
CHANGED
@@ -358,6 +358,44 @@ LOX_END
|
|
358
358
|
expect(sample_cfg[:ostream].string).to eq('012')
|
359
359
|
end
|
360
360
|
|
361
|
+
it 'should implement single statement for loops' do
|
362
|
+
program = <<-LOX_END
|
363
|
+
// Single-expression body.
|
364
|
+
for (var c = 0; c < 3;) print c = c + 1;
|
365
|
+
// output: 1
|
366
|
+
// output: 2
|
367
|
+
// output: 3
|
368
|
+
LOX_END
|
369
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
370
|
+
expect(sample_cfg[:ostream].string).to eq('123')
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'should implement for loops with block body' do
|
374
|
+
program = <<-LOX_END
|
375
|
+
// Block body.
|
376
|
+
for (var a = 0; a < 3; a = a + 1) {
|
377
|
+
print a;
|
378
|
+
}
|
379
|
+
// output: 0
|
380
|
+
// output: 1
|
381
|
+
// output: 2
|
382
|
+
LOX_END
|
383
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
384
|
+
expect(sample_cfg[:ostream].string).to eq('012')
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'should implement for loops without initialization' do
|
388
|
+
program = <<-LOX_END
|
389
|
+
var i = 0;
|
390
|
+
// No variable in initialization.
|
391
|
+
for (; i < 2; i = i + 1) print i;
|
392
|
+
// output: 0
|
393
|
+
// output: 1
|
394
|
+
LOX_END
|
395
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
396
|
+
expect(sample_cfg[:ostream].string).to eq('01')
|
397
|
+
end
|
398
|
+
|
361
399
|
it 'should print the hello world message' do
|
362
400
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
363
401
|
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.27
|
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-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -91,6 +91,7 @@ files:
|
|
91
91
|
- lib/loxxy/ast/lox_binary_expr.rb
|
92
92
|
- lib/loxxy/ast/lox_block_stmt.rb
|
93
93
|
- lib/loxxy/ast/lox_compound_expr.rb
|
94
|
+
- lib/loxxy/ast/lox_for_stmt.rb
|
94
95
|
- lib/loxxy/ast/lox_grouping_expr.rb
|
95
96
|
- lib/loxxy/ast/lox_if_stmt.rb
|
96
97
|
- lib/loxxy/ast/lox_literal_expr.rb
|