loxxy 0.0.11 → 0.0.16

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.
@@ -13,6 +13,28 @@ module Loxxy
13
13
  def initialize
14
14
  super(true)
15
15
  end
16
+
17
+ # Is this object representing the true value in Lox?
18
+ # @return [TrueClass]
19
+ def true?
20
+ true
21
+ end
22
+
23
+ # Check for equality of a Lox True with another Lox object
24
+ # @param other [Datatype::True, TrueClass, Object]
25
+ # @return [Datatype::Boolean]
26
+ def ==(other)
27
+ thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
28
+ thruthy ? True.instance : False.instance
29
+ end
30
+
31
+ # Check for inequality of a Lox True with another Lox object
32
+ # @param other [Datatype::BuiltinDatatype, TrueClass, Object]
33
+ # @return [Datatype::Boolean]
34
+ def !=(other)
35
+ thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
36
+ thruthy ? False.instance : True.instance
37
+ end
16
38
  end # class
17
39
 
18
40
  True.instance.freeze # Make the sole instance immutable
@@ -26,8 +26,8 @@ module Loxxy
26
26
  add_terminals('EOF')
27
27
 
28
28
  # Top-level rule that matches an entire Lox program
29
- rule('program' => 'EOF')
30
- rule('program' => 'declaration_plus EOF')
29
+ rule('program' => 'EOF').as 'null_program'
30
+ rule('program' => 'declaration_plus EOF').as 'lox_program'
31
31
 
32
32
  # Declarations: bind an identifier to something
33
33
  rule('declaration_plus' => 'declaration_plus declaration')
@@ -58,7 +58,7 @@ module Loxxy
58
58
  rule('statement' => 'whileStmt')
59
59
  rule('statement' => 'block')
60
60
 
61
- rule('exprStmt' => 'expression SEMICOLON')
61
+ rule('exprStmt' => 'expression SEMICOLON').as 'exprStmt'
62
62
 
63
63
  rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
64
64
  rule('forControl' => 'forInitialization forTest forUpdate')
@@ -73,7 +73,7 @@ module Loxxy
73
73
  rule('elsePart_opt' => 'ELSE statement')
74
74
  rule('elsePart_opt' => [])
75
75
 
76
- rule('printStmt' => 'PRINT expression SEMICOLON')
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
79
  rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
@@ -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.11'
4
+ VERSION = '0.0.16'
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,39 @@ 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
50
+
51
+ result = subject == ''
52
+ expect(result).to be_false
53
+ end
28
54
 
29
- expect(subject).not_to eq('')
30
- expect(subject).not_to eq('other-text')
55
+ it 'performs the concatenation with another string' do
56
+ concatenation = LXString.new('str') + LXString.new('ing')
57
+ expect(concatenation == 'string').to be_true
31
58
  end
32
59
  end
33
60
  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,57 @@
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
+
45
+ it 'should compute the addition with another number' do
46
+ addition = subject + Number.new(10)
47
+ expect(addition == -2.34).to be_true
48
+ end
49
+
50
+ it 'should compute the subtraction with another number' do
51
+ subtraction = subject - Number.new(10)
52
+ expect(subtraction == -22.34).to be_true
53
+ end
54
+ end
55
+ end # describe
56
+ end # module
57
+ 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')
@@ -259,10 +225,7 @@ LOX_END
259
225
  %w[> >= < <=].each do |predicate|
260
226
  input = "3 #{predicate} 2;"
261
227
  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]
228
+ expr = ptree.root
266
229
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
267
230
  expect(expr.operator).to eq(predicate.to_sym)
268
231
  expect(expr.operands[0].literal.value).to eq(3)
@@ -276,10 +239,7 @@ LOX_END
276
239
  %w[!= ==].each do |predicate|
277
240
  input = "3 #{predicate} 2;"
278
241
  ptree = subject.parse(input)
279
- parent = ptree.root.subnodes[0]
280
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
281
- expect(parent.symbol.name).to eq('exprStmt')
282
- expr = parent.subnodes[0]
242
+ expr = ptree.root
283
243
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
284
244
  expect(expr.operator).to eq(predicate.to_sym)
285
245
  expect(expr.operands[0].literal.value).to eq(3)
@@ -290,10 +250,7 @@ LOX_END
290
250
  it 'should parse combination of equality expressions' do
291
251
  input = '5 != 2 == false; // A bit contrived example'
292
252
  ptree = subject.parse(input)
293
- parent = ptree.root.subnodes[0]
294
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
295
- expect(parent.symbol.name).to eq('exprStmt')
296
- expr = parent.subnodes[0]
253
+ expr = ptree.root
297
254
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
298
255
  expect(expr.operator).to eq(:==)
299
256
  expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
@@ -309,10 +266,7 @@ LOX_END
309
266
  %w[or and].each do |connector|
310
267
  input = "5 > 2 #{connector} 3 <= 4;"
311
268
  ptree = subject.parse(input)
312
- parent = ptree.root.subnodes[0]
313
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
314
- expect(parent.symbol.name).to eq('exprStmt')
315
- expr = parent.subnodes[0]
269
+ expr = ptree.root
316
270
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
317
271
  expect(expr.operator).to eq(connector.to_sym)
318
272
  expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
@@ -329,10 +283,7 @@ LOX_END
329
283
  it 'should parse a combinations of logical expressions' do
330
284
  input = '4 > 3 and 1 < 2 or 4 >= 5;'
331
285
  ptree = subject.parse(input)
332
- parent = ptree.root.subnodes[0]
333
- expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
334
- expect(parent.symbol.name).to eq('exprStmt')
335
- expr = parent.subnodes[0]
286
+ expr = ptree.root
336
287
  expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
337
288
  expect(expr.operator).to eq(:or) # or has lower precedence than and
338
289
  expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)