loxxy 0.0.16 → 0.0.21

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.
@@ -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 & reponds to visit events
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.16'
4
+ VERSION = '0.0.21'
5
5
  end
@@ -31,8 +31,16 @@ 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
+ it "should react to 'after_var_stmt' event" do
38
+ expect { subject.after_var_stmt(var_decl) }.not_to raise_error
39
+ current_env = subject.symbol_table.current_env
40
+ expect(current_env.defns['greeting']).to be_kind_of(Variable)
41
+ expect(current_env.defns['greeting'].value).to eq(greeting)
42
+ end
43
+
36
44
  it "should react to 'before_literal_expr' event" do
37
45
  expect { subject.before_literal_expr(lit_expr) }.not_to raise_error
38
46
  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.parent).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
+ i_name = 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.parent).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
@@ -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 betweentwo sub-expression' do
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::LoxBinaryExpr)
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::LoxBinaryExpr)
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::LoxBinaryExpr)
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)
@@ -107,6 +107,158 @@ module Loxxy
107
107
  end
108
108
  end
109
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 support 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
+
253
+ it 'should accept variable declarations' do
254
+ # Variable with initialization value
255
+ var_decl = 'var iAmAVariable = "here is my value";'
256
+ expect { subject.evaluate(var_decl) }.not_to raise_error
257
+
258
+ # Variable without initialization value
259
+ expect { subject.evaluate('var iAmNil;') }.not_to raise_error
260
+ end
261
+
110
262
  it 'should print the hello world message' do
111
263
  expect { subject.evaluate(hello_world) }.not_to raise_error
112
264
  expect(sample_cfg[:ostream].string).to eq('Hello, world!')