loxxy 0.0.9 → 0.0.14

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,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './front_end/parser'
4
+ require_relative './ast/ast_visitor'
5
+ require_relative './back_end/engine'
6
+
7
+ module Loxxy
8
+ # A Lox tree-walking interpreter.
9
+ # It acts as a facade object that:
10
+ # - hides the internal plumbings of the front-end and back-end parts.
11
+ # - delegates all the core work to its subordinate objects.
12
+ # @note WIP: very crude implementation.
13
+ class Interpreter
14
+ # return [Hash]
15
+ attr_reader :config
16
+
17
+ # @param theOptions [Hash]
18
+ def initialize(theOptions = {})
19
+ @config = theOptions
20
+ end
21
+
22
+ # Evaluate the given Lox program.
23
+ # Return the result of the last executed expression (if any)
24
+ # @param lox_input [String] Lox program to evaluate
25
+ # @return [Loxxy::Datatype::BuiltinDatatype]
26
+ def evaluate(lox_input)
27
+ # Front-end scans, parses the input and blurps an AST...
28
+ parser = FrontEnd::Parser.new
29
+
30
+ # The AST is the data object passed to the back-end
31
+ ast_tree = parser.parse(lox_input)
32
+ visitor = Ast::ASTVisitor.new(ast_tree)
33
+
34
+ # Back-end launches the tree walking & reponds to visit events
35
+ # by executing the code determined by the visited AST node.
36
+ engine = BackEnd::Engine.new(config)
37
+ engine.execute(visitor)
38
+ end
39
+ end # class
40
+ end # module
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.9'
4
+ VERSION = '0.0.14'
5
5
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+ require 'stringio'
5
+
6
+ # Load the class under test
7
+ require_relative '../../lib/loxxy/back_end/engine'
8
+
9
+ module Loxxy
10
+ module BackEnd
11
+ describe Engine do
12
+ let(:sample_options) do
13
+ { ostream: StringIO.new }
14
+ end
15
+ subject { Engine.new(sample_options) }
16
+
17
+ context 'Initialization:' do
18
+ it 'should accept a option Hash at initialization' do
19
+ expect { Engine.new(sample_options) }.not_to raise_error
20
+ end
21
+
22
+ it 'should know its config options' do
23
+ expect(subject.config).to eq(sample_options)
24
+ end
25
+
26
+ it 'should have an empty stack' do
27
+ expect(subject.stack).to be_empty
28
+ end
29
+ end
30
+
31
+ context 'Listening to visitor events:' do
32
+ let(:greeting) { Datatype::LXString.new('Hello, world') }
33
+ let(:sample_pos) { double('fake-position') }
34
+ let(:lit_expr) { Ast::LoxLiteralExpr.new(sample_pos, greeting) }
35
+
36
+ it "should react to 'before_literal_expr' event" do
37
+ expect { subject.before_literal_expr(lit_expr) }.not_to raise_error
38
+ expect(subject.stack.pop).to eq(greeting)
39
+ end
40
+ end
41
+ end # describe
42
+ end # module
43
+ end # module
@@ -0,0 +1,31 @@
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/datatype/boolean'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe Boolean do
11
+ let(:thruth_value) { false }
12
+ subject { Boolean.new(thruth_value) }
13
+
14
+ context 'Initialization:' do
15
+ it 'should accept a boolean value at initialization' do
16
+ expect { Boolean.new(thruth_value) }.not_to raise_error
17
+ end
18
+
19
+ it 'should know its value' do
20
+ expect(subject.value).to eq(thruth_value)
21
+ end
22
+ end
23
+
24
+ context 'Provided services:' do
25
+ it 'should give its display representation' do
26
+ expect(subject.to_str).to eq('false')
27
+ end
28
+ end
29
+ end # describe
30
+ end # module
31
+ end # module
@@ -22,12 +22,34 @@ module Loxxy
22
22
  end
23
23
 
24
24
  context 'Provided services:' do
25
- it 'compares with another string' do
26
- expect(subject).to eq(sample_text)
27
- expect(subject).to eq(LXString.new(sample_text.dup))
25
+ it 'should give its display representation' do
26
+ expect(subject.to_str).to eq(sample_text)
27
+ end
28
+
29
+ it 'compares with another Lox string' do
30
+ result = subject == LXString.new(sample_text.dup)
31
+ expect(result).to be_true
32
+
33
+ result = subject == LXString.new('other-text')
34
+ expect(result).to be_false
35
+
36
+ result = subject == LXString.new('')
37
+ expect(result).to be_false
38
+
39
+ # Two empty strings are equal
40
+ result = LXString.new('') == LXString.new('')
41
+ expect(result).to be_true
42
+ end
43
+
44
+ it 'compares with a Ruby string' do
45
+ result = subject == sample_text.dup
46
+ expect(result).to be_true
47
+
48
+ result = subject == 'other-text'
49
+ expect(result).to be_false
28
50
 
29
- expect(subject).not_to eq('')
30
- expect(subject).not_to eq('other-text')
51
+ result = subject == ''
52
+ expect(result).to be_false
31
53
  end
32
54
  end
33
55
  end # describe
@@ -0,0 +1,26 @@
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/datatype/boolean'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe Nil do
11
+ subject { Nil.instance }
12
+
13
+ context 'Initialization:' do
14
+ it 'should know its value' do
15
+ expect(subject.value).to be_nil
16
+ end
17
+ end
18
+
19
+ context 'Provided services:' do
20
+ it 'should give its display representation' do
21
+ expect(subject.to_str).to eq('nil')
22
+ end
23
+ end
24
+ end # describe
25
+ end # module
26
+ end # module
@@ -0,0 +1,47 @@
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/datatype/number'
7
+
8
+ module Loxxy
9
+ module Datatype
10
+ describe Number do
11
+ let(:sample_value) { -12.34 }
12
+ subject { Number.new(sample_value) }
13
+
14
+ context 'Initialization:' do
15
+ it 'should accept a Numeric value at initialization' do
16
+ expect { Number.new(sample_value) }.not_to raise_error
17
+ end
18
+
19
+ it 'should know its value' do
20
+ expect(subject.value).to eq(sample_value)
21
+ end
22
+ end
23
+
24
+ context 'Provided services:' do
25
+ it 'should compare with other Lox numbers' do
26
+ result = subject == Number.new(sample_value)
27
+ expect(result).to be_true
28
+
29
+ result = subject == Number.new(5)
30
+ expect(result).to be_false
31
+ end
32
+
33
+ it 'should compare with Ruby numbers' do
34
+ result = subject == sample_value
35
+ expect(result).to be_true
36
+
37
+ result = subject == 5
38
+ expect(result).to be_false
39
+ end
40
+
41
+ it 'should give its display representation' do
42
+ expect(subject.to_str).to eq(sample_value.to_s)
43
+ end
44
+ end
45
+ end # describe
46
+ end # module
47
+ end # module
@@ -37,7 +37,7 @@ module Loxxy
37
37
  # Parse results MUST to comply to grammar rule:
38
38
  # program => declaration_star EOF
39
39
  # where the declaration_star MUST be empty
40
- expect(aParseTree.root.symbol.name).to eq('EOF')
40
+ expect(aParseTree.root).to be_kind_of(Ast::LoxNoopExpr)
41
41
  end
42
42
 
43
43
  it 'should cope with an empty input' do
@@ -64,7 +64,7 @@ module Loxxy
64
64
  it 'should parse a false literal' do
65
65
  input = 'false;'
66
66
  ptree = subject.parse(input)
67
- leaf = walk_subnodes(ptree.root, [0, 0])
67
+ leaf = ptree.root
68
68
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
69
69
  expect(leaf.literal).to be_equal(Datatype::False.instance)
70
70
  end
@@ -72,7 +72,7 @@ module Loxxy
72
72
  it 'should parse a true literal' do
73
73
  input = 'true;'
74
74
  ptree = subject.parse(input)
75
- leaf = walk_subnodes(ptree.root, [0, 0])
75
+ leaf = ptree.root
76
76
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
77
77
  expect(leaf.literal).to be_equal(Datatype::True.instance)
78
78
  end
@@ -81,7 +81,7 @@ module Loxxy
81
81
  inputs = %w[1234; 12.34;]
82
82
  inputs.each do |source|
83
83
  ptree = subject.parse(source)
84
- leaf = walk_subnodes(ptree.root, [0, 0])
84
+ leaf = ptree.root
85
85
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
86
86
  expect(leaf.literal).to be_kind_of(Datatype::Number)
87
87
  expect(leaf.literal.value).to eq(source.to_f)
@@ -96,7 +96,7 @@ module Loxxy
96
96
  ]
97
97
  inputs.each do |source|
98
98
  ptree = subject.parse(source)
99
- leaf = walk_subnodes(ptree.root, [0, 0])
99
+ leaf = ptree.root
100
100
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
101
101
  expect(leaf.literal).to be_kind_of(Datatype::LXString)
102
102
  expect(leaf.literal.value).to eq(source.gsub(/(^")|(";$)/, ''))
@@ -106,7 +106,7 @@ module Loxxy
106
106
  it 'should parse a nil literal' do
107
107
  input = 'nil;'
108
108
  ptree = subject.parse(input)
109
- leaf = walk_subnodes(ptree.root, [0, 0])
109
+ leaf = ptree.root
110
110
  expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
111
111
  expect(leaf.literal).to be_equal(Datatype::Nil.instance)
112
112
  end
@@ -119,21 +119,11 @@ module Loxxy
119
119
  print "Hello, world!";
120
120
  LOX_END
121
121
  ptree = subject.parse(program)
122
- root = ptree.root
123
- expect(root.symbol.name).to eq('program')
124
- (prnt_stmt, eof) = root.subnodes
125
- expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
126
- expect(prnt_stmt.symbol.name).to eq('printStmt')
127
- expect(prnt_stmt.subnodes.size).to eq(3)
128
- expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
129
- expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
130
- expect(prnt_stmt.subnodes[1]).to be_kind_of(Loxxy::Ast::LoxLiteralExpr)
131
- expect(prnt_stmt.subnodes[1].literal).to be_kind_of(Loxxy::Datatype::LXString)
132
- expect(prnt_stmt.subnodes[1].literal.value).to eq('Hello, world!')
133
- expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
134
- expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
135
- expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
136
- expect(eof.symbol.name).to eq('EOF')
122
+ prnt_stmt = ptree.root
123
+ expect(prnt_stmt).to be_kind_of(Ast::LoxPrintStmt)
124
+ expect(prnt_stmt.subnodes[0]).to be_kind_of(Ast::LoxLiteralExpr)
125
+ expect(prnt_stmt.subnodes[0].literal).to be_kind_of(Loxxy::Datatype::LXString)
126
+ expect(prnt_stmt.subnodes[0].literal.value).to eq('Hello, world!')
137
127
  end
138
128
  end # context
139
129
 
@@ -141,10 +131,7 @@ LOX_END
141
131
  it 'should parse the addition of two number literals' do
142
132
  input = '123 + 456;'
143
133
  ptree = subject.parse(input)
144
- parent = ptree.root.subnodes[0]
145
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
146
- expect(parent.symbol.name).to eq('exprStmt')
147
- expr = parent.subnodes[0]
134
+ expr = ptree.root
148
135
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
149
136
  expect(expr.operator).to eq(:+)
150
137
  expect(expr.operands[0].literal.value).to eq(123)
@@ -154,10 +141,7 @@ LOX_END
154
141
  it 'should parse the subtraction of two number literals' do
155
142
  input = '4 - 3;'
156
143
  ptree = subject.parse(input)
157
- parent = ptree.root.subnodes[0]
158
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
159
- expect(parent.symbol.name).to eq('exprStmt')
160
- expr = parent.subnodes[0]
144
+ expr = ptree.root
161
145
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
162
146
  expect(expr.operator).to eq(:-)
163
147
  expect(expr.operands[0].literal.value).to eq(4)
@@ -167,10 +151,7 @@ LOX_END
167
151
  it 'should parse multiple additive operations' do
168
152
  input = '5 + 2 - 3;'
169
153
  ptree = subject.parse(input)
170
- parent = ptree.root.subnodes[0]
171
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
172
- expect(parent.symbol.name).to eq('exprStmt')
173
- expr = parent.subnodes[0]
154
+ expr = ptree.root
174
155
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
175
156
  expect(expr.operator).to eq(:-)
176
157
  expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
@@ -183,10 +164,7 @@ LOX_END
183
164
  it 'should parse the division of two number literals' do
184
165
  input = '8 / 2;'
185
166
  ptree = subject.parse(input)
186
- parent = ptree.root.subnodes[0]
187
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
188
- expect(parent.symbol.name).to eq('exprStmt')
189
- expr = parent.subnodes[0]
167
+ expr = ptree.root
190
168
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
191
169
  expect(expr.operator).to eq(:/)
192
170
  expect(expr.operands[0].literal.value).to eq(8)
@@ -196,10 +174,7 @@ LOX_END
196
174
  it 'should parse the product of two number literals' do
197
175
  input = '12.34 * 0.3;'
198
176
  ptree = subject.parse(input)
199
- parent = ptree.root.subnodes[0]
200
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
201
- expect(parent.symbol.name).to eq('exprStmt')
202
- expr = parent.subnodes[0]
177
+ expr = ptree.root
203
178
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
204
179
  expect(expr.operator).to eq(:*)
205
180
  expect(expr.operands[0].literal.value).to eq(12.34)
@@ -209,10 +184,7 @@ LOX_END
209
184
  it 'should parse multiple multiplicative operations' do
210
185
  input = '5 * 2 / 3;'
211
186
  ptree = subject.parse(input)
212
- parent = ptree.root.subnodes[0]
213
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
214
- expect(parent.symbol.name).to eq('exprStmt')
215
- expr = parent.subnodes[0]
187
+ expr = ptree.root
216
188
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
217
189
  expect(expr.operator).to eq(:/)
218
190
  expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
@@ -225,10 +197,7 @@ LOX_END
225
197
  it 'should parse combination of terms and factors' do
226
198
  input = '5 + 2 / 3;'
227
199
  ptree = subject.parse(input)
228
- parent = ptree.root.subnodes[0]
229
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
230
- expect(parent.symbol.name).to eq('exprStmt')
231
- expr = parent.subnodes[0]
200
+ expr = ptree.root
232
201
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
233
202
  expect(expr.operator).to eq(:+)
234
203
  expect(expr.operands[0].literal.value).to eq(5)
@@ -243,10 +212,7 @@ LOX_END
243
212
  it 'should parse the concatenation of two string literals' do
244
213
  input = '"Lo" + "ve";'
245
214
  ptree = subject.parse(input)
246
- parent = ptree.root.subnodes[0]
247
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
248
- expect(parent.symbol.name).to eq('exprStmt')
249
- expr = parent.subnodes[0]
215
+ expr = ptree.root
250
216
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
251
217
  expect(expr.operator).to eq(:+)
252
218
  expect(expr.operands[0].literal.value).to eq('Lo')
@@ -255,20 +221,87 @@ LOX_END
255
221
  end # context
256
222
 
257
223
  context 'Parsing comparison expressions' do
258
- it 'should parse the comparison of two number literals' do
259
- %w[> >= < <=].each do |predicate|
224
+ it 'should parse the comparison of two number literals' do
225
+ %w[> >= < <=].each do |predicate|
226
+ input = "3 #{predicate} 2;"
227
+ ptree = subject.parse(input)
228
+ expr = ptree.root
229
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
230
+ expect(expr.operator).to eq(predicate.to_sym)
231
+ expect(expr.operands[0].literal.value).to eq(3)
232
+ expect(expr.operands[1].literal.value).to eq(2)
233
+ end
234
+ end
235
+ end # context
236
+
237
+ context 'Parsing equality expressions' do
238
+ it 'should parse the equality of two number literals' do
239
+ %w[!= ==].each do |predicate|
260
240
  input = "3 #{predicate} 2;"
261
241
  ptree = subject.parse(input)
262
- parent = ptree.root.subnodes[0]
263
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
264
- expect(parent.symbol.name).to eq('exprStmt')
265
- expr = parent.subnodes[0]
242
+ expr = ptree.root
266
243
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
267
244
  expect(expr.operator).to eq(predicate.to_sym)
268
245
  expect(expr.operands[0].literal.value).to eq(3)
269
246
  expect(expr.operands[1].literal.value).to eq(2)
270
247
  end
271
248
  end
249
+
250
+ it 'should parse combination of equality expressions' do
251
+ input = '5 != 2 == false; // A bit contrived example'
252
+ ptree = subject.parse(input)
253
+ expr = ptree.root
254
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
255
+ expect(expr.operator).to eq(:==)
256
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
257
+ expect(expr.operands[0].operator).to eq(:!=)
258
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
259
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
260
+ expect(expr.operands[1].literal.value).to be_falsey
261
+ end
262
+ end # context
263
+
264
+ context 'Parsing logical expressions' do
265
+ it 'should parse the logical operations betweentwo sub-expression' do
266
+ %w[or and].each do |connector|
267
+ input = "5 > 2 #{connector} 3 <= 4;"
268
+ ptree = subject.parse(input)
269
+ expr = ptree.root
270
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
271
+ expect(expr.operator).to eq(connector.to_sym)
272
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
273
+ expect(expr.operands[0].operator).to eq(:>)
274
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
275
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
276
+ expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
277
+ expect(expr.operands[1].operator).to eq(:<=)
278
+ expect(expr.operands[1].operands[0].literal.value).to eq(3)
279
+ expect(expr.operands[1].operands[1].literal.value).to eq(4)
280
+ end
281
+ end
282
+
283
+ it 'should parse a combinations of logical expressions' do
284
+ input = '4 > 3 and 1 < 2 or 4 >= 5;'
285
+ ptree = subject.parse(input)
286
+ expr = ptree.root
287
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
288
+ expect(expr.operator).to eq(:or) # or has lower precedence than and
289
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
290
+ expect(expr.operands[0].operator).to eq(:and)
291
+ conjuncts = expr.operands[0].operands
292
+ expect(conjuncts[0]).to be_kind_of(Ast::LoxBinaryExpr)
293
+ expect(conjuncts[0].operator).to eq(:>)
294
+ expect(conjuncts[0].operands[0].literal.value).to eq(4)
295
+ expect(conjuncts[0].operands[1].literal.value).to eq(3)
296
+ expect(conjuncts[1]).to be_kind_of(Ast::LoxBinaryExpr)
297
+ expect(conjuncts[1].operator).to eq(:<)
298
+ expect(conjuncts[1].operands[0].literal.value).to eq(1)
299
+ expect(conjuncts[1].operands[1].literal.value).to eq(2)
300
+ expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
301
+ expect(expr.operands[1].operator).to eq(:>=)
302
+ expect(expr.operands[1].operands[0].literal.value).to eq(4)
303
+ expect(expr.operands[1].operands[1].literal.value).to eq(5)
304
+ end
272
305
  end # context
273
306
  end # describe
274
307
  end # module