loxxy 0.0.17 → 0.0.22

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.
@@ -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
- leaf = ptree.root
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
- leaf = ptree.root
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)
@@ -262,12 +264,12 @@ LOX_END
262
264
  end # context
263
265
 
264
266
  context 'Parsing logical expressions' do
265
- it 'should parse the logical operations betweentwo sub-expression' do
267
+ it 'should parse the logical operations between two sub-expression' do
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
270
- expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
271
+ expr = ptree.root.subnodes[0]
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)
273
275
  expect(expr.operands[0].operator).to eq(:>)
@@ -283,10 +285,10 @@ 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
287
- expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
288
+ expr = ptree.root.subnodes[0]
289
+ expect(expr).to be_kind_of(Ast::LoxLogicalExpr)
288
290
  expect(expr.operator).to eq(:or) # or has lower precedence than and
289
- expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
291
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxLogicalExpr)
290
292
  expect(expr.operands[0].operator).to eq(:and)
291
293
  conjuncts = expr.operands[0].operands
292
294
  expect(conjuncts[0]).to be_kind_of(Ast::LoxBinaryExpr)
@@ -52,7 +52,12 @@ module Loxxy
52
52
  end # context
53
53
 
54
54
  context 'Evaluating Lox code:' do
55
- let(:hello_world) { 'print "Hello, world!";' }
55
+ let(:hello_world) do
56
+ lox =<<-LOX_END
57
+ var greeting = "Hello"; // Declaring a variable
58
+ print greeting + ", " + "world!"; // ... Playing with concatenation
59
+ LOX_END
60
+ end
56
61
 
57
62
  it 'should evaluate core data types' do
58
63
  result = subject.evaluate('true; // Not false')
@@ -164,6 +169,110 @@ module Loxxy
164
169
  end
165
170
  end
166
171
 
172
+ it 'should evaluate the "conjunction" of two values' do
173
+ [
174
+ # Return the first falsey argument
175
+ ['false and 1;', false],
176
+ ['nil and 1;', nil],
177
+ ['true and 1;', 1],
178
+ ['1 and 2 and false;', false],
179
+ ['1 and 2 and nil;', nil],
180
+
181
+ # Return the last argument if all are truthy
182
+ ['1 and true;', true],
183
+ ['0 and true;', true],
184
+ ['"false" and 0;', 0],
185
+ ['1 and 2 and 3;', 3]
186
+
187
+ # TODO test short-circuit at first false argument
188
+ ].each do |(source, predicted)|
189
+ lox = Loxxy::Interpreter.new
190
+ result = lox.evaluate(source)
191
+ expect(result.value == predicted).to be_truthy
192
+ end
193
+ end
194
+
195
+ it 'should evaluate the "disjunction" of two values' do
196
+ [
197
+ # Return the first truthy argument
198
+ ['1 or true;', 1],
199
+ ['false or 1;', 1],
200
+ ['nil or 1;', 1],
201
+ ['false or false or true;', true],
202
+ ['1 and 2 and nil;', nil],
203
+
204
+ # Return the last argument if all are falsey
205
+ ['false or false;', false],
206
+ ['nil or false;', false],
207
+ ['false or nil;', nil],
208
+ ['false or false or false;', false],
209
+ ['false or false or nil;', nil]
210
+
211
+ # TODO test short-circuit at first false argument
212
+ ].each do |(source, predicted)|
213
+ lox = Loxxy::Interpreter.new
214
+ result = lox.evaluate(source)
215
+ expect(result.value == predicted).to be_truthy
216
+ end
217
+ end
218
+
219
+ it 'should support expressions between parentheses' do
220
+ [
221
+ ['3 + 4 * 5;', 23],
222
+ ['(3 + 4) * 5;', 35],
223
+ ['(5 - (3 - 1)) + -(1);', 2]
224
+ ].each do |(source, predicted)|
225
+ lox = Loxxy::Interpreter.new
226
+ result = lox.evaluate(source)
227
+ expect(result.value == predicted).to be_truthy
228
+ end
229
+ end
230
+
231
+ it 'should evaluate an if statement' do
232
+ [
233
+ # Evaluate the 'then' expression if the condition is true.
234
+ ['if (true) print "then-branch";', 'then-branch'],
235
+ ['if (false) print "ignored";', ''],
236
+ # TODO: test with then block body
237
+ # TODO: test with assignment in if condition
238
+
239
+ # Evaluate the 'else' expression if the condition is false.
240
+ ['if (true) print "then-branch"; else print "else-branch";', 'then-branch'],
241
+ ['if (false) print "then-branch"; else print "else-branch";', 'else-branch'],
242
+ ['if (0) print "then-branch"; else print "else-branch";', 'then-branch'],
243
+ ['if (nil) print "then-branch"; else print "else-branch";', 'else-branch'],
244
+ # TODO: test with else block body
245
+
246
+ # TODO: A dangling else binds to the right-most if.
247
+ # ['if (true) if (false) print "bad"; else print "good";', 'good'],
248
+ # ['if (false) if (true) print "bad"; else print "worse";', 'bad']
249
+ ].each do |(source, predicted)|
250
+ io = StringIO.new
251
+ cfg = { ostream: io }
252
+ lox = Loxxy::Interpreter.new(cfg)
253
+ result = lox.evaluate(source)
254
+ expect(io.string).to eq(predicted)
255
+ end
256
+ end
257
+
258
+ it 'should accept variable declarations' do
259
+ # Variable with initialization value
260
+ var_decl = 'var iAmAVariable = "here is my value";'
261
+ expect { subject.evaluate(var_decl) }.not_to raise_error
262
+
263
+ # Variable without initialization value
264
+ expect { subject.evaluate('var iAmNil;') }.not_to raise_error
265
+ end
266
+
267
+ it 'should accept variable mention' do
268
+ program = <<-LOX_END
269
+ var foo = "bar";
270
+ print foo;
271
+ LOX_END
272
+ expect { subject.evaluate(program) }.not_to raise_error
273
+ expect(sample_cfg[:ostream].string).to eq('bar')
274
+ end
275
+
167
276
  it 'should print the hello world message' do
168
277
  expect { subject.evaluate(hello_world) }.not_to raise_error
169
278
  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.17
4
+ version: 0.0.22
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-12 00:00:00.000000000 Z
11
+ date: 2021-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -89,12 +89,22 @@ 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_seq_decl.rb
96
100
  - lib/loxxy/ast/lox_unary_expr.rb
101
+ - lib/loxxy/ast/lox_var_stmt.rb
102
+ - lib/loxxy/ast/lox_variable_expr.rb
97
103
  - lib/loxxy/back_end/engine.rb
104
+ - lib/loxxy/back_end/entry.rb
105
+ - lib/loxxy/back_end/environment.rb
106
+ - lib/loxxy/back_end/symbol_table.rb
107
+ - lib/loxxy/back_end/variable.rb
98
108
  - lib/loxxy/datatype/all_datatypes.rb
99
109
  - lib/loxxy/datatype/boolean.rb
100
110
  - lib/loxxy/datatype/builtin_datatype.rb
@@ -112,6 +122,9 @@ files:
112
122
  - lib/loxxy/version.rb
113
123
  - loxxy.gemspec
114
124
  - spec/back_end/engine_spec.rb
125
+ - spec/back_end/environment_spec.rb
126
+ - spec/back_end/symbol_table_spec.rb
127
+ - spec/back_end/variable_spec.rb
115
128
  - spec/datatype/boolean_spec.rb
116
129
  - spec/datatype/lx_string_spec.rb
117
130
  - spec/datatype/nil_spec.rb
@@ -148,6 +161,9 @@ specification_version: 4
148
161
  summary: An implementation of the Lox programming language. WIP
149
162
  test_files:
150
163
  - spec/back_end/engine_spec.rb
164
+ - spec/back_end/environment_spec.rb
165
+ - spec/back_end/symbol_table_spec.rb
166
+ - spec/back_end/variable_spec.rb
151
167
  - spec/datatype/boolean_spec.rb
152
168
  - spec/datatype/lx_string_spec.rb
153
169
  - spec/datatype/nil_spec.rb