loxxy 0.0.20 → 0.0.25
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 +92 -31
- data/README.md +47 -10
- data/lib/loxxy/ast/all_lox_nodes.rb +5 -0
- data/lib/loxxy/ast/ast_builder.rb +42 -2
- data/lib/loxxy/ast/ast_visitor.rb +47 -1
- data/lib/loxxy/ast/lox_assign_expr.rb +27 -0
- data/lib/loxxy/ast/lox_block_stmt.rb +23 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +1 -1
- data/lib/loxxy/ast/lox_seq_decl.rb +17 -0
- data/lib/loxxy/ast/lox_var_stmt.rb +28 -0
- data/lib/loxxy/ast/lox_variable_expr.rb +26 -0
- data/lib/loxxy/back_end/engine.rb +49 -2
- data/lib/loxxy/back_end/entry.rb +41 -0
- data/lib/loxxy/back_end/environment.rb +66 -0
- data/lib/loxxy/back_end/symbol_table.rb +135 -0
- data/lib/loxxy/back_end/variable.rb +30 -0
- data/lib/loxxy/datatype/builtin_datatype.rb +6 -0
- data/lib/loxxy/front_end/grammar.rb +7 -7
- data/lib/loxxy/interpreter.rb +1 -1
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/engine_spec.rb +9 -0
- data/spec/back_end/environment_spec.rb +74 -0
- data/spec/back_end/symbol_table_spec.rb +142 -0
- data/spec/back_end/variable_spec.rb +79 -0
- data/spec/front_end/parser_spec.rb +21 -19
- data/spec/interpreter_spec.rb +83 -4
- metadata +17 -2
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'entry'
|
4
|
+
require_relative '../datatype/all_datatypes'
|
5
|
+
|
6
|
+
module Loxxy
|
7
|
+
module BackEnd
|
8
|
+
# Representation of a Lox variable.
|
9
|
+
# It is a named slot that can be associated with a value at the time.
|
10
|
+
class Variable
|
11
|
+
include Entry # Add expected behaviour for symbol table entries
|
12
|
+
|
13
|
+
# @return [Datatype::BuiltinDatatype] the value assigned to the variable
|
14
|
+
attr_reader :value
|
15
|
+
|
16
|
+
# Create a variable with given name and initial value
|
17
|
+
# @param aName [String] The name of the variable
|
18
|
+
# @param aValue [Datatype::BuiltinDatatype] the initial assigned value
|
19
|
+
def initialize(aName, aValue = Datatype::Nil.instance)
|
20
|
+
init_name(aName)
|
21
|
+
assign(aValue)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param aValue [Datatype::BuiltinDatatype] the assigned value
|
25
|
+
def assign(aValue)
|
26
|
+
@value = aValue
|
27
|
+
end
|
28
|
+
end # class
|
29
|
+
end # module
|
30
|
+
end # module
|
@@ -62,6 +62,12 @@ module Loxxy
|
|
62
62
|
value.to_s # Default implementation...
|
63
63
|
end
|
64
64
|
|
65
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
66
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
67
|
+
def accept(visitor)
|
68
|
+
visitor.visit_builtin(self)
|
69
|
+
end
|
70
|
+
|
65
71
|
protected
|
66
72
|
|
67
73
|
def validated_value(aValue)
|
@@ -30,8 +30,8 @@ module Loxxy
|
|
30
30
|
rule('program' => 'declaration_plus EOF').as 'lox_program'
|
31
31
|
|
32
32
|
# Declarations: bind an identifier to something
|
33
|
-
rule('declaration_plus' => 'declaration_plus declaration')
|
34
|
-
rule('declaration_plus' => 'declaration')
|
33
|
+
rule('declaration_plus' => 'declaration_plus declaration').as 'declaration_plus_more'
|
34
|
+
rule('declaration_plus' => 'declaration').as 'declaration_plus_end'
|
35
35
|
rule('declaration' => 'classDecl')
|
36
36
|
rule('declaration' => 'funDecl')
|
37
37
|
rule('declaration' => 'varDecl')
|
@@ -46,8 +46,8 @@ module Loxxy
|
|
46
46
|
|
47
47
|
rule('funDecl' => 'FUN function')
|
48
48
|
|
49
|
-
rule('varDecl' => 'VAR IDENTIFIER SEMICOLON')
|
50
|
-
rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON')
|
49
|
+
rule('varDecl' => 'VAR IDENTIFIER SEMICOLON').as 'var_declaration'
|
50
|
+
rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
|
51
51
|
|
52
52
|
# Statements: produce side effects, but don't introduce bindings
|
53
53
|
rule('statement' => 'exprStmt')
|
@@ -76,14 +76,14 @@ module Loxxy
|
|
76
76
|
rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
|
77
77
|
rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
|
78
78
|
rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement')
|
79
|
-
rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
|
79
|
+
rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE').as 'block_stmt'
|
80
80
|
rule('block' => 'LEFT_BRACE RIGHT_BRACE')
|
81
81
|
|
82
82
|
# Expressions: produce values
|
83
83
|
rule('expression_opt' => 'expression')
|
84
84
|
rule('expression_opt' => [])
|
85
85
|
rule('expression' => 'assignment')
|
86
|
-
rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
|
86
|
+
rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
|
87
87
|
rule('assignment' => 'logic_or')
|
88
88
|
rule('owner_opt' => 'call DOT')
|
89
89
|
rule('owner_opt' => [])
|
@@ -137,7 +137,7 @@ module Loxxy
|
|
137
137
|
rule('primary' => 'THIS')
|
138
138
|
rule('primary' => 'NUMBER').as 'literal_expr'
|
139
139
|
rule('primary' => 'STRING').as 'literal_expr'
|
140
|
-
rule('primary' => 'IDENTIFIER')
|
140
|
+
rule('primary' => 'IDENTIFIER').as 'variable_expr'
|
141
141
|
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
|
142
142
|
rule('primary' => 'SUPER DOT IDENTIFIER')
|
143
143
|
|
data/lib/loxxy/interpreter.rb
CHANGED
@@ -31,7 +31,7 @@ module Loxxy
|
|
31
31
|
ast_tree = parser.parse(lox_input)
|
32
32
|
visitor = Ast::ASTVisitor.new(ast_tree)
|
33
33
|
|
34
|
-
# Back-end launches the tree walking &
|
34
|
+
# Back-end launches the tree walking & responds to visit events
|
35
35
|
# by executing the code determined by the visited AST node.
|
36
36
|
engine = BackEnd::Engine.new(config)
|
37
37
|
engine.execute(visitor)
|
data/lib/loxxy/version.rb
CHANGED
@@ -31,8 +31,17 @@ module Loxxy
|
|
31
31
|
context 'Listening to visitor events:' do
|
32
32
|
let(:greeting) { Datatype::LXString.new('Hello, world') }
|
33
33
|
let(:sample_pos) { double('fake-position') }
|
34
|
+
let(:var_decl) { Ast::LoxVarStmt.new(sample_pos, 'greeting', greeting) }
|
34
35
|
let(:lit_expr) { Ast::LoxLiteralExpr.new(sample_pos, greeting) }
|
35
36
|
|
37
|
+
|
38
|
+
it "should react to 'after_var_stmt' event" do
|
39
|
+
expect { subject.after_var_stmt(var_decl) }.not_to raise_error
|
40
|
+
current_env = subject.symbol_table.current_env
|
41
|
+
expect(current_env.defns['greeting']).to be_kind_of(Variable)
|
42
|
+
expect(current_env.defns['greeting'].value).to eq(greeting)
|
43
|
+
end
|
44
|
+
|
36
45
|
it "should react to 'before_literal_expr' event" do
|
37
46
|
expect { subject.before_literal_expr(lit_expr) }.not_to raise_error
|
38
47
|
expect(subject.stack.pop).to eq(greeting)
|
@@ -0,0 +1,74 @@
|
|
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/back_end/environment'
|
7
|
+
|
8
|
+
module Loxxy
|
9
|
+
module BackEnd
|
10
|
+
describe Environment do
|
11
|
+
let(:foo) { Datatype::LXString.new('foo') }
|
12
|
+
let(:bar) { Datatype::LXString.new('bar') }
|
13
|
+
let(:mother) { Environment.new }
|
14
|
+
subject { Environment.new(mother) }
|
15
|
+
|
16
|
+
# Shortand factory method.
|
17
|
+
def var(aName, aValue)
|
18
|
+
Variable.new(aName, aValue)
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'Initialization:' do
|
22
|
+
it 'could be initialized without argument' do
|
23
|
+
expect { Environment.new }.not_to raise_error
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'could be initialized with a parent environment' do
|
27
|
+
expect { Environment.new(mother) }.not_to raise_error
|
28
|
+
end
|
29
|
+
|
30
|
+
it "shouldn't have definitions by default" do
|
31
|
+
expect(subject.defns).to be_empty
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should know its parent (if any)' do
|
35
|
+
expect(subject.enclosing).to eq(mother)
|
36
|
+
end
|
37
|
+
end # context
|
38
|
+
|
39
|
+
context 'Provided services:' do
|
40
|
+
it 'should accept the addition of a variable' do
|
41
|
+
subject.insert(var('a', foo))
|
42
|
+
expect(subject.defns).not_to be_empty
|
43
|
+
var_a = subject.defns['a']
|
44
|
+
expect(var_a).to be_kind_of(Variable)
|
45
|
+
expect(var_a.name).to eq('a')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should accept the addition of multiple variables' do
|
49
|
+
subject.insert(var('a', foo))
|
50
|
+
expect(subject.defns).not_to be_empty
|
51
|
+
|
52
|
+
subject.insert(var('b', bar))
|
53
|
+
var_b = subject.defns['b']
|
54
|
+
expect(var_b).to be_kind_of(Variable)
|
55
|
+
expect(var_b.name).to eq('b')
|
56
|
+
end
|
57
|
+
|
58
|
+
# it 'should set the suffix of just created variable' do
|
59
|
+
# subject.insert(var('a'))
|
60
|
+
# var_a = subject.defns['a']
|
61
|
+
# expect(var_a.suffix).to eq("_#{subject.object_id.to_s(16)}")
|
62
|
+
# end
|
63
|
+
|
64
|
+
# it 'should complain when variable names collide' do
|
65
|
+
# subject.insert(var('c'))
|
66
|
+
# expect(subject.defns['c']).to be_kind_of(Datatype::Variable)
|
67
|
+
# err = StandardError
|
68
|
+
# err_msg = "Variable with name 'c' already exists."
|
69
|
+
# expect { subject.insert(var('c')) }.to raise_error(err, err_msg)
|
70
|
+
# end
|
71
|
+
end # context
|
72
|
+
end # describe
|
73
|
+
end # module
|
74
|
+
end # module
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../spec_helper' # Use the RSpec framework
|
4
|
+
require_relative '../../lib/loxxy/back_end/variable'
|
5
|
+
|
6
|
+
# Load the class under test
|
7
|
+
require_relative '../../lib/loxxy/back_end/symbol_table'
|
8
|
+
|
9
|
+
module Loxxy
|
10
|
+
module BackEnd
|
11
|
+
describe SymbolTable do
|
12
|
+
subject { SymbolTable.new }
|
13
|
+
|
14
|
+
context 'Initialization:' do
|
15
|
+
it 'should be initialized without argument' do
|
16
|
+
expect { SymbolTable.new }.not_to raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should have a root BackEnd' do
|
20
|
+
expect(subject.root).not_to be_nil
|
21
|
+
expect(subject.current_env).to eq(subject.root)
|
22
|
+
expect(subject.root).to be_kind_of(BackEnd::Environment)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "shouldn't have names at initialization" do
|
26
|
+
expect(subject.name2envs).to be_empty
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should be empty at initialization' do
|
30
|
+
expect(subject).to be_empty
|
31
|
+
end
|
32
|
+
end # context
|
33
|
+
|
34
|
+
context 'Provided services:' do
|
35
|
+
def var(aName)
|
36
|
+
Variable.new(aName)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should allow the addition of a variable' do
|
40
|
+
expect { subject.insert(var('q')) }.not_to raise_error
|
41
|
+
expect(subject).not_to be_empty
|
42
|
+
expect(subject.name2envs['q']).to be_kind_of(Array)
|
43
|
+
expect(subject.name2envs['q'].size).to eq(1)
|
44
|
+
expect(subject.name2envs['q'].first).to eq(subject.current_env)
|
45
|
+
expect(subject.current_env.defns['q']).to be_kind_of(BackEnd::Variable)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should allow the addition of several labels for same env' do
|
49
|
+
subject.insert(var('q'))
|
50
|
+
# expect(i_name).to match(/^q_[0-9a-z]*$/)
|
51
|
+
|
52
|
+
expect { subject.insert(var('x')) }.not_to raise_error
|
53
|
+
expect(subject.name2envs['x']).to be_kind_of(Array)
|
54
|
+
expect(subject.name2envs['x'].first).to eq(subject.current_env)
|
55
|
+
expect(subject.current_env.defns['x']).to be_kind_of(BackEnd::Variable)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should allow the entry into a new scope' do
|
59
|
+
subject.insert(var('q'))
|
60
|
+
new_env = BackEnd::Environment.new
|
61
|
+
expect { subject.enter_environment(new_env) }.not_to raise_error
|
62
|
+
expect(subject.current_env).to eq(new_env)
|
63
|
+
expect(subject.current_env.enclosing).to eq(subject.root)
|
64
|
+
expect(subject.name2envs['q']).to eq([subject.root])
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should allow the addition of same name in different scopes' do
|
68
|
+
subject.insert(var('q'))
|
69
|
+
subject.enter_environment(BackEnd::Environment.new)
|
70
|
+
subject.insert(var('q'))
|
71
|
+
expect(subject.name2envs['q']).to be_kind_of(Array)
|
72
|
+
expect(subject.name2envs['q'].size).to eq(2)
|
73
|
+
expect(subject.name2envs['q'].first).to eq(subject.root)
|
74
|
+
expect(subject.name2envs['q'].last).to eq(subject.current_env)
|
75
|
+
expect(subject.current_env.defns['q']).to be_kind_of(BackEnd::Variable)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should allow the removal of a scope' do
|
79
|
+
subject.insert(var('q'))
|
80
|
+
new_env = BackEnd::Environment.new
|
81
|
+
subject.enter_environment(new_env)
|
82
|
+
subject.insert(var('q'))
|
83
|
+
expect(subject.name2envs['q'].size).to eq(2)
|
84
|
+
|
85
|
+
expect { subject.leave_environment }.not_to raise_error
|
86
|
+
expect(subject.current_env).to eq(subject.root)
|
87
|
+
expect(subject.name2envs['q'].size).to eq(1)
|
88
|
+
expect(subject.name2envs['q']).to eq([subject.root])
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should allow the search of an entry based on its name' do
|
92
|
+
subject.insert(var('q'))
|
93
|
+
subject.insert(var('x'))
|
94
|
+
subject.enter_environment(BackEnd::Environment.new)
|
95
|
+
subject.insert(var('q'))
|
96
|
+
subject.insert(var('y'))
|
97
|
+
|
98
|
+
# Search for unknown name
|
99
|
+
expect(subject.lookup('z')).to be_nil
|
100
|
+
|
101
|
+
# Search for existing unique names
|
102
|
+
expect(subject.lookup('y')).to eq(subject.current_env.defns['y'])
|
103
|
+
expect(subject.lookup('x')).to eq(subject.root.defns['x'])
|
104
|
+
|
105
|
+
# Search for redefined name
|
106
|
+
expect(subject.lookup('q')).to eq(subject.current_env.defns['q'])
|
107
|
+
end
|
108
|
+
|
109
|
+
# it 'should allow the search of an entry based on its i_name' do
|
110
|
+
# subject.insert(var('q'))
|
111
|
+
# i_name_x = subject.insert(var('x'))
|
112
|
+
# subject.enter_environment(BackEnd::Environment.new)
|
113
|
+
# i_name_q2 = subject.insert(var('q'))
|
114
|
+
# i_name_y = subject.insert(var('y'))
|
115
|
+
|
116
|
+
# # Search for unknown i_name
|
117
|
+
# expect(subject.lookup_i_name('dummy')).to be_nil
|
118
|
+
|
119
|
+
# curr_scope = subject.current_env
|
120
|
+
# # # Search for existing unique names
|
121
|
+
# expect(subject.lookup_i_name(i_name_y)).to eq(curr_scope.defns['y'])
|
122
|
+
# expect(subject.lookup_i_name(i_name_x)).to eq(subject.root.defns['x'])
|
123
|
+
|
124
|
+
# # Search for redefined name
|
125
|
+
# expect(subject.lookup_i_name(i_name_q2)).to eq(curr_scope.defns['q'])
|
126
|
+
# end
|
127
|
+
|
128
|
+
it 'should list all the variables defined in all the szcope chain' do
|
129
|
+
subject.insert(var('q'))
|
130
|
+
subject.enter_environment(BackEnd::Environment.new)
|
131
|
+
subject.insert(var('x'))
|
132
|
+
subject.enter_environment(BackEnd::Environment.new)
|
133
|
+
subject.insert(var('y'))
|
134
|
+
subject.insert(var('x'))
|
135
|
+
|
136
|
+
vars = subject.all_variables
|
137
|
+
expect(vars.map(&:name)).to eq(%w[q x y x])
|
138
|
+
end
|
139
|
+
end # context
|
140
|
+
end # describe
|
141
|
+
end # module
|
142
|
+
end # module
|
@@ -0,0 +1,79 @@
|
|
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/back_end/variable'
|
7
|
+
|
8
|
+
|
9
|
+
module Loxxy
|
10
|
+
module BackEnd
|
11
|
+
describe Variable do
|
12
|
+
let(:sample_name) { 'iAmAVariable' }
|
13
|
+
let(:sample_value) { 'here is my value' }
|
14
|
+
subject { Variable.new(sample_name, sample_value) }
|
15
|
+
|
16
|
+
context 'Initialization:' do
|
17
|
+
it 'should be initialized with a name and a value, or...' do
|
18
|
+
expect { Variable.new(sample_name, sample_value) }.not_to raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should be initialized with just a name' do
|
22
|
+
expect { Variable.new(sample_name) }.not_to raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should know its name' do
|
26
|
+
expect(subject.name).to eq(sample_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should have a frozen name' do
|
30
|
+
expect(subject.name).to be_frozen
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should know its value (if provided)' do
|
34
|
+
expect(subject.value).to eq(sample_value)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should have a nil value otherwise' do
|
38
|
+
instance = Variable.new(sample_name)
|
39
|
+
expect(instance.value).to eq(Datatype::Nil.instance)
|
40
|
+
end
|
41
|
+
|
42
|
+
# it 'should know its default internal name' do
|
43
|
+
# # By default: internal name == label
|
44
|
+
# expect(subject.i_name).to eq(subject.label)
|
45
|
+
# end
|
46
|
+
|
47
|
+
# it 'should have a nil suffix' do
|
48
|
+
# expect(subject.suffix).to be_nil
|
49
|
+
# end
|
50
|
+
end # context
|
51
|
+
|
52
|
+
context 'Provided service:' do
|
53
|
+
let(:sample_suffix) { 'sample-suffix' }
|
54
|
+
it 'should have a label equal to its user-defined name' do
|
55
|
+
# expect(subject.label).to eq(subject.name)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should accept a suffix' do
|
59
|
+
# expect { subject.suffix = sample_suffix }.not_to raise_error
|
60
|
+
# expect(subject.suffix).to eq(sample_suffix)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should calculate its internal name' do
|
64
|
+
# # Rule: empty suffix => internal name == label
|
65
|
+
# subject.suffix = ''
|
66
|
+
# expect(subject.i_name).to eq(subject.label)
|
67
|
+
|
68
|
+
# # Rule: suffix starting underscore: internal name = label + suffix
|
69
|
+
# subject.suffix = '_10'
|
70
|
+
# expect(subject.i_name).to eq(subject.label + subject.suffix)
|
71
|
+
|
72
|
+
# # Rule: ... otherwise: internal name == suffix
|
73
|
+
# subject.suffix = sample_suffix
|
74
|
+
# expect(subject.i_name).to eq(subject.suffix)
|
75
|
+
end
|
76
|
+
end # context
|
77
|
+
end # describe
|
78
|
+
end # module
|
79
|
+
end # module
|
@@ -64,7 +64,8 @@ module Loxxy
|
|
64
64
|
it 'should parse a false literal' do
|
65
65
|
input = 'false;'
|
66
66
|
ptree = subject.parse(input)
|
67
|
-
|
67
|
+
expect(ptree.root).to be_kind_of(Ast::LoxSeqDecl)
|
68
|
+
leaf = ptree.root.subnodes[0]
|
68
69
|
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
69
70
|
expect(leaf.literal).to be_equal(Datatype::False.instance)
|
70
71
|
end
|
@@ -72,7 +73,8 @@ module Loxxy
|
|
72
73
|
it 'should parse a true literal' do
|
73
74
|
input = 'true;'
|
74
75
|
ptree = subject.parse(input)
|
75
|
-
|
76
|
+
expect(ptree.root).to be_kind_of(Ast::LoxSeqDecl)
|
77
|
+
leaf = ptree.root.subnodes[0]
|
76
78
|
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
77
79
|
expect(leaf.literal).to be_equal(Datatype::True.instance)
|
78
80
|
end
|
@@ -81,7 +83,7 @@ module Loxxy
|
|
81
83
|
inputs = %w[1234; 12.34;]
|
82
84
|
inputs.each do |source|
|
83
85
|
ptree = subject.parse(source)
|
84
|
-
leaf = ptree.root
|
86
|
+
leaf = ptree.root.subnodes[0]
|
85
87
|
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
86
88
|
expect(leaf.literal).to be_kind_of(Datatype::Number)
|
87
89
|
expect(leaf.literal.value).to eq(source.to_f)
|
@@ -96,7 +98,7 @@ module Loxxy
|
|
96
98
|
]
|
97
99
|
inputs.each do |source|
|
98
100
|
ptree = subject.parse(source)
|
99
|
-
leaf = ptree.root
|
101
|
+
leaf = ptree.root.subnodes[0]
|
100
102
|
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
101
103
|
expect(leaf.literal).to be_kind_of(Datatype::LXString)
|
102
104
|
expect(leaf.literal.value).to eq(source.gsub(/(^")|(";$)/, ''))
|
@@ -106,7 +108,7 @@ module Loxxy
|
|
106
108
|
it 'should parse a nil literal' do
|
107
109
|
input = 'nil;'
|
108
110
|
ptree = subject.parse(input)
|
109
|
-
leaf = ptree.root
|
111
|
+
leaf = ptree.root.subnodes[0]
|
110
112
|
expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
|
111
113
|
expect(leaf.literal).to be_equal(Datatype::Nil.instance)
|
112
114
|
end
|
@@ -119,7 +121,7 @@ module Loxxy
|
|
119
121
|
print "Hello, world!";
|
120
122
|
LOX_END
|
121
123
|
ptree = subject.parse(program)
|
122
|
-
prnt_stmt = ptree.root
|
124
|
+
prnt_stmt = ptree.root.subnodes[0]
|
123
125
|
expect(prnt_stmt).to be_kind_of(Ast::LoxPrintStmt)
|
124
126
|
expect(prnt_stmt.subnodes[0]).to be_kind_of(Ast::LoxLiteralExpr)
|
125
127
|
expect(prnt_stmt.subnodes[0].literal).to be_kind_of(Loxxy::Datatype::LXString)
|
@@ -131,7 +133,7 @@ LOX_END
|
|
131
133
|
it 'should parse the addition of two number literals' do
|
132
134
|
input = '123 + 456;'
|
133
135
|
ptree = subject.parse(input)
|
134
|
-
expr = ptree.root
|
136
|
+
expr = ptree.root.subnodes[0]
|
135
137
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
136
138
|
expect(expr.operator).to eq(:+)
|
137
139
|
expect(expr.operands[0].literal.value).to eq(123)
|
@@ -141,7 +143,7 @@ LOX_END
|
|
141
143
|
it 'should parse the subtraction of two number literals' do
|
142
144
|
input = '4 - 3;'
|
143
145
|
ptree = subject.parse(input)
|
144
|
-
expr = ptree.root
|
146
|
+
expr = ptree.root.subnodes[0]
|
145
147
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
146
148
|
expect(expr.operator).to eq(:-)
|
147
149
|
expect(expr.operands[0].literal.value).to eq(4)
|
@@ -151,7 +153,7 @@ LOX_END
|
|
151
153
|
it 'should parse multiple additive operations' do
|
152
154
|
input = '5 + 2 - 3;'
|
153
155
|
ptree = subject.parse(input)
|
154
|
-
expr = ptree.root
|
156
|
+
expr = ptree.root.subnodes[0]
|
155
157
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
156
158
|
expect(expr.operator).to eq(:-)
|
157
159
|
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
@@ -164,7 +166,7 @@ LOX_END
|
|
164
166
|
it 'should parse the division of two number literals' do
|
165
167
|
input = '8 / 2;'
|
166
168
|
ptree = subject.parse(input)
|
167
|
-
expr = ptree.root
|
169
|
+
expr = ptree.root.subnodes[0]
|
168
170
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
169
171
|
expect(expr.operator).to eq(:/)
|
170
172
|
expect(expr.operands[0].literal.value).to eq(8)
|
@@ -174,7 +176,7 @@ LOX_END
|
|
174
176
|
it 'should parse the product of two number literals' do
|
175
177
|
input = '12.34 * 0.3;'
|
176
178
|
ptree = subject.parse(input)
|
177
|
-
expr = ptree.root
|
179
|
+
expr = ptree.root.subnodes[0]
|
178
180
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
179
181
|
expect(expr.operator).to eq(:*)
|
180
182
|
expect(expr.operands[0].literal.value).to eq(12.34)
|
@@ -184,7 +186,7 @@ LOX_END
|
|
184
186
|
it 'should parse multiple multiplicative operations' do
|
185
187
|
input = '5 * 2 / 3;'
|
186
188
|
ptree = subject.parse(input)
|
187
|
-
expr = ptree.root
|
189
|
+
expr = ptree.root.subnodes[0]
|
188
190
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
189
191
|
expect(expr.operator).to eq(:/)
|
190
192
|
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
@@ -197,7 +199,7 @@ LOX_END
|
|
197
199
|
it 'should parse combination of terms and factors' do
|
198
200
|
input = '5 + 2 / 3;'
|
199
201
|
ptree = subject.parse(input)
|
200
|
-
expr = ptree.root
|
202
|
+
expr = ptree.root.subnodes[0]
|
201
203
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
202
204
|
expect(expr.operator).to eq(:+)
|
203
205
|
expect(expr.operands[0].literal.value).to eq(5)
|
@@ -212,7 +214,7 @@ LOX_END
|
|
212
214
|
it 'should parse the concatenation of two string literals' do
|
213
215
|
input = '"Lo" + "ve";'
|
214
216
|
ptree = subject.parse(input)
|
215
|
-
expr = ptree.root
|
217
|
+
expr = ptree.root.subnodes[0]
|
216
218
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
217
219
|
expect(expr.operator).to eq(:+)
|
218
220
|
expect(expr.operands[0].literal.value).to eq('Lo')
|
@@ -225,7 +227,7 @@ LOX_END
|
|
225
227
|
%w[> >= < <=].each do |predicate|
|
226
228
|
input = "3 #{predicate} 2;"
|
227
229
|
ptree = subject.parse(input)
|
228
|
-
expr = ptree.root
|
230
|
+
expr = ptree.root.subnodes[0]
|
229
231
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
230
232
|
expect(expr.operator).to eq(predicate.to_sym)
|
231
233
|
expect(expr.operands[0].literal.value).to eq(3)
|
@@ -239,7 +241,7 @@ LOX_END
|
|
239
241
|
%w[!= ==].each do |predicate|
|
240
242
|
input = "3 #{predicate} 2;"
|
241
243
|
ptree = subject.parse(input)
|
242
|
-
expr = ptree.root
|
244
|
+
expr = ptree.root.subnodes[0]
|
243
245
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
244
246
|
expect(expr.operator).to eq(predicate.to_sym)
|
245
247
|
expect(expr.operands[0].literal.value).to eq(3)
|
@@ -250,7 +252,7 @@ LOX_END
|
|
250
252
|
it 'should parse combination of equality expressions' do
|
251
253
|
input = '5 != 2 == false; // A bit contrived example'
|
252
254
|
ptree = subject.parse(input)
|
253
|
-
expr = ptree.root
|
255
|
+
expr = ptree.root.subnodes[0]
|
254
256
|
expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
|
255
257
|
expect(expr.operator).to eq(:==)
|
256
258
|
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
@@ -266,7 +268,7 @@ LOX_END
|
|
266
268
|
%w[or and].each do |connector|
|
267
269
|
input = "5 > 2 #{connector} 3 <= 4;"
|
268
270
|
ptree = subject.parse(input)
|
269
|
-
expr = ptree.root
|
271
|
+
expr = ptree.root.subnodes[0]
|
270
272
|
expect(expr).to be_kind_of(Ast::LoxLogicalExpr)
|
271
273
|
expect(expr.operator).to eq(connector.to_sym)
|
272
274
|
expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
|
@@ -283,7 +285,7 @@ LOX_END
|
|
283
285
|
it 'should parse a combinations of logical expressions' do
|
284
286
|
input = '4 > 3 and 1 < 2 or 4 >= 5;'
|
285
287
|
ptree = subject.parse(input)
|
286
|
-
expr = ptree.root
|
288
|
+
expr = ptree.root.subnodes[0]
|
287
289
|
expect(expr).to be_kind_of(Ast::LoxLogicalExpr)
|
288
290
|
expect(expr.operator).to eq(:or) # or has lower precedence than and
|
289
291
|
expect(expr.operands[0]).to be_kind_of(Ast::LoxLogicalExpr)
|