rley 0.2.01 → 0.2.02

Sign up to get free protection for your applications and to get access to all the features.
@@ -16,6 +16,19 @@ module Rley # This module is used as a namespace
16
16
  children << aChildNode
17
17
  end
18
18
 
19
+ # Emit a (formatted) string representation of the node.
20
+ # Mainly used for diagnosis/debugging purposes.
21
+ def to_string(indentation)
22
+ connector = '+- '
23
+ selfie = super(indentation)
24
+ prefix = "\n" + (' ' * connector.size * indentation) + connector
25
+ children_repr = children.reduce('') do |sub_result, child|
26
+ sub_result << prefix + child.to_string(indentation + 1)
27
+ end
28
+
29
+ return selfie + children_repr
30
+ end
31
+
19
32
  # Part of the 'visitee' role in Visitor design pattern.
20
33
  # @param aVisitor[ParseTreeVisitor] the visitor
21
34
  def accept(aVisitor)
@@ -17,6 +17,13 @@ module Rley # This module is used as a namespace
17
17
  def range=(aRange)
18
18
  range.assign(aRange)
19
19
  end
20
+
21
+ # Emit a (formatted) string representation of the node.
22
+ # Mainly used for diagnosis/debugging purposes.
23
+ def to_string(indentation)
24
+
25
+ return "#{symbol.name}#{range.to_string(indentation)}"
26
+ end
20
27
  end # class
21
28
  end # module
22
29
  end # module
@@ -10,6 +10,13 @@ module Rley # This module is used as a namespace
10
10
  super(aTerminalSymbol, aRange)
11
11
  end
12
12
 
13
+ # Emit a (formatted) string representation of the node.
14
+ # Mainly used for diagnosis/debugging purposes.
15
+ def to_string(indentation)
16
+ value = token.nil? ? '(nil)' : token.lexeme
17
+ super(indentation) + ": '#{value}'"
18
+ end
19
+
13
20
  # Part of the 'visitee' role in Visitor design pattern.
14
21
  # @param aVisitor[ParseTreeVisitor] the visitor
15
22
  def accept(aVisitor)
@@ -42,6 +42,24 @@ module Rley # This module is used as a namespace
42
42
  assign_high(aRange) if high.nil?
43
43
  end
44
44
 
45
+ # Tell whether the given index value lies outside the range
46
+ def out_of_range?(index)
47
+ result = false
48
+ result = true if !low.nil? && index < low
49
+ result = true if !high.nil? && index > high
50
+
51
+ return result
52
+ end
53
+
54
+ # Emit a (formatted) string representation of the range.
55
+ # Mainly used for diagnosis/debugging purposes.
56
+ def to_string(_indentation)
57
+ low_text = low.nil? ? '?' : low.to_s
58
+ high_text = high.nil? ? '?' : high.to_s
59
+
60
+ return "[#{low_text}, #{high_text}]"
61
+ end
62
+
45
63
  private
46
64
 
47
65
  def assign_low(aRange)
@@ -70,8 +70,7 @@ module Rley # Re-open the module to get rid of qualified names
70
70
  end
71
71
  end # context
72
72
 
73
-
74
-
73
+
75
74
  context 'Formatting events:' do
76
75
  it 'should render a parse tree in JSON' do
77
76
  instance = Json.new(destination)
@@ -6,6 +6,7 @@ require_relative '../../../lib/rley/syntax/production'
6
6
  require_relative '../../../lib/rley/syntax/grammar_builder'
7
7
  require_relative '../../../lib/rley/parser/token'
8
8
  require_relative '../../../lib/rley/parser/dotted_item'
9
+ require_relative '../support/ambiguous_grammar_helper'
9
10
  # Load the class under test
10
11
  require_relative '../../../lib/rley/parser/earley_parser'
11
12
 
@@ -38,9 +39,9 @@ module Rley # Open this namespace to avoid module qualifier prefixes
38
39
 
39
40
  # Grammar 1: A very simple language
40
41
  # (based on example in N. Wirth "Compiler Construction" book, p. 6)
41
- # S ::= A.
42
- # A ::= "a" A "c".
43
- # A ::= "b".
42
+ # S => A.
43
+ # A => "a" A "c".
44
+ # A => "b".
44
45
  # Let's create the grammar piece by piece
45
46
  let(:nt_S) { Syntax::NonTerminal.new('S') }
46
47
  let(:nt_A) { Syntax::NonTerminal.new('A') }
@@ -333,7 +334,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
333
334
  compare_state_texts(parse_result.chart[1], expected)
334
335
  end
335
336
 
336
- it 'should parse an ambiguous grammar' do
337
+ it 'should parse an ambiguous grammar (I)' do
337
338
  # Grammar 3: A ambiguous arithmetic expression language
338
339
  # (based on example in article on Earley's algorithm in Wikipedia)
339
340
  # P => S.
@@ -442,6 +443,78 @@ module Rley # Open this namespace to avoid module qualifier prefixes
442
443
  compare_state_texts(parse_result.chart[5], expected)
443
444
  end
444
445
 
446
+ it 'should parse an ambiguous grammar (II)' do
447
+ self.extend(AmbiguousGrammarHelper)
448
+ grammar = grammar_builder.grammar
449
+ instance = EarleyParser.new(grammar)
450
+ tokens = tokenize('abc + def + ghi', grammar)
451
+ expect { instance.parse(tokens) }.not_to raise_error
452
+ parse_result = instance.parse(tokens)
453
+ expect(parse_result.success?).to eq(true)
454
+
455
+ ###################### S(0): . abc + def + ghi
456
+ # Expectation chart[0]:
457
+ expected = [
458
+ 'S => . E | 0', # Start rule
459
+ 'E => . E + E | 0', # predict from (1)
460
+ 'E => . id | 0' # predict from (1)
461
+ ]
462
+ compare_state_texts(parse_result.chart[0], expected)
463
+
464
+ ###################### S(1): abc . + def + ghi
465
+ # Expectation chart[1]:
466
+ expected = [
467
+ 'E => id . | 0', # scan from S(0) 3
468
+ 'S => E . | 0', # complete from (1) and S(0) 2
469
+ 'E => E . + E | 0' # complete from (1) and S(0) 3
470
+ ]
471
+ compare_state_texts(parse_result.chart[1], expected)
472
+
473
+ ###################### S(2): abc + . def + ghi
474
+ # Expectation chart[2]:
475
+ expected = [
476
+ 'E => E + . E | 0', # Scan from S(1) 3
477
+ 'E => . E + E | 2', # predict from (1)
478
+ 'E => . id | 2' # predict from (1)
479
+ ]
480
+ compare_state_texts(parse_result.chart[2], expected)
481
+
482
+ ###################### S(3): abc + def . + ghi
483
+ # Expectation chart[3]:
484
+ expected = [
485
+ 'E => id . | 2', # Scan from S(2) 3
486
+ 'E => E + E . | 0', # complete from (1) and S(2) 1
487
+ 'E => E . + E | 2', # complete from (1) and S(2) 2
488
+ 'S => E . | 0', # complete from (1) and S(0) 1
489
+ 'E => E . + E | 0' # complete from (1) and S(0) 2
490
+ ]
491
+ compare_state_texts(parse_result.chart[3], expected)
492
+
493
+ ###################### S(4): abc + def + . ghi
494
+ # Expectation chart[4]:
495
+ expected = [
496
+ 'E => E + . E | 2', # Scan from S(3) 3
497
+ 'E => E + . E | 0', # Scan from S(3) 5
498
+ 'E => . E + E | 4', # predict from (1)
499
+ 'E => . id | 4' # predict from (1)
500
+ ]
501
+ compare_state_texts(parse_result.chart[4], expected)
502
+
503
+ ###################### S(5): abc + def + ghi .
504
+ # Expectation chart[5]:
505
+ expected = [
506
+ 'E => id . | 4', # Scan from S(4) 4
507
+ 'E => E + E . | 2', # complete from (1) and S(4) 1
508
+ 'E => E + E . | 0', # complete from (1) and S(4) 2
509
+ 'E => E . + E | 4', # complete from (1) and S(4) 3
510
+ 'E => E . + E | 2', # complete from (1) and S(2) 2
511
+ 'S => E . | 0', # complete from (1) and S(0) 1
512
+ 'E => E . + E | 0', # complete from (1) and S(0) 2
513
+ ]
514
+ compare_state_texts(parse_result.chart[5], expected)
515
+ end
516
+
517
+
445
518
 
446
519
  it 'should parse an invalid simple input' do
447
520
  # Parse an erroneous input (b is missing)
@@ -21,8 +21,8 @@ module Rley # Open this namespace to avoid module qualifier prefixes
21
21
  let(:small_a) { grammar_abc.name2symbol['a'] }
22
22
  let(:small_b) { grammar_abc.name2symbol['b'] }
23
23
  let(:small_c) { grammar_abc.name2symbol['c'] }
24
-
25
- let(:start_prod) { grammar_abc.start_production }
24
+
25
+ let(:start_prod) { grammar_abc.start_production }
26
26
 
27
27
  let(:tokens_abc) do
28
28
  %w(a a b c c).map do |letter|
@@ -34,7 +34,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
34
34
  parser = EarleyParser.new(grammar_abc)
35
35
  parser.parse(tokens_abc)
36
36
  end
37
-
37
+
38
38
  subject { ParseTreeBuilder.new(start_prod, { low: 0, high: 5 }) }
39
39
 
40
40
  context 'Initialization:' do
@@ -71,7 +71,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
71
71
  # Add children to A
72
72
  other_state = sample_parsing.chart.state_sets.last.states.first
73
73
  subject.use_complete_state(other_state)
74
-
74
+
75
75
  # Tree is:
76
76
  # S[0,5]
77
77
  # +- A[0,5]
@@ -85,7 +85,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
85
85
  end
86
86
  expect(child1.children[0].range.low).to eq(0)
87
87
  expect(child1.children[-1].range.high).to eq(5)
88
-
88
+
89
89
  subject.move_down # ... to c
90
90
  subject.range = { low: 4 }
91
91
  expect(child1.children[-1].range.low).to eq(4)
@@ -132,46 +132,47 @@ module Rley # Open this namespace to avoid module qualifier prefixes
132
132
  subject.use_complete_state(other_state)
133
133
 
134
134
  # Tree is:
135
- # S[0,?]
136
- # +- A[0,?]
137
- # +- a[0, ?]
138
- # +- A[?, ?]
139
- # +- a[?, ?]
140
- # +- A[?, ?]
141
- # +- c [?, ?]
142
- # +- c[?, ?]
135
+ tree_snapshot = <<-SNIPPET
136
+ S[0, 5]
137
+ +- A[0, 5]
138
+ +- a[0, ?]: '(nil)'
139
+ +- A[1, ?]
140
+ +- a[1, ?]: '(nil)'
141
+ +- A[?, ?]
142
+ +- c[?, ?]: '(nil)'
143
+ +- c[?, 5]: '(nil)'
144
+ SNIPPET
145
+ expect(subject.root.to_string(0)).to eq(tree_snapshot.chomp)
143
146
 
144
147
  subject.move_down # ...to grand-grand-child c
145
- expect(subject.current_node.symbol).to eq(small_c)
148
+ expect(subject.current_node.to_string(0)).to eq("c[?, ?]: '(nil)'")
146
149
 
147
150
  subject.move_back # ...to grand-grand-child A
148
- expect(subject.current_node.symbol).to eq(capital_a)
151
+ expect(subject.current_node.to_string(0)).to eq('A[?, ?]')
149
152
 
150
153
  subject.move_back # ...to grand-grand-child a
151
- expect(subject.current_node.symbol).to eq(small_a)
154
+ expect(subject.current_node.to_string(0)).to eq("a[1, ?]: '(nil)'")
152
155
 
153
- subject.move_back # ...to grand-child A
154
- expect(subject.current_node.symbol).to eq(capital_a)
155
-
156
156
  subject.move_back # ...to grand-child a
157
- expect(subject.current_node.symbol).to eq(small_a)
158
-
159
- subject.move_back # ...to child A
160
- expect(subject.current_node.symbol).to eq(capital_a)
157
+ expect(subject.current_node.to_string(0)).to eq("a[0, ?]: '(nil)'")
161
158
 
162
159
  subject.move_back # ...to S
163
- expect(subject.current_node.symbol).to eq(capital_s)
160
+ expect(subject.current_node.symbol).to eq(capital_s)
161
+ end
162
+
163
+ it 'should move through deeply nested structure' do
164
+
164
165
  end
165
166
  end # context
166
-
167
- context 'Parse tree building:' do
167
+
168
+ context 'Parse tree building:' do
168
169
  it 'should build a parse tree' do
169
170
  expect(subject.parse_tree).to be_kind_of(PTree::ParseTree)
170
171
  actual = subject.parse_tree
171
172
  expect(actual.root).to eq(subject.root)
172
173
  end
173
174
  end # context
174
-
175
+
175
176
  end # describe
176
177
  end # module
177
178
  end # module
@@ -21,9 +21,9 @@ module Rley # Open this namespace to avoid module qualifier prefixes
21
21
  include GrammarBExprHelper # Mix-in with builder for simple expressions
22
22
 
23
23
  # Grammar 1: A very simple language
24
- # S ::= A.
25
- # A ::= "a" A "c".
26
- # A ::= "b".
24
+ # S => A.
25
+ # A => "a" A "c".
26
+ # A => "b".
27
27
  let(:nt_S) { Syntax::NonTerminal.new('S') }
28
28
  let(:nt_A) { Syntax::NonTerminal.new('A') }
29
29
  let(:a_) { Syntax::VerbatimSymbol.new('a') }
@@ -139,6 +139,215 @@ module Rley # Open this namespace to avoid module qualifier prefixes
139
139
  b_expr_grammar.name2symbol[aSymbolName]
140
140
  end
141
141
 
142
+ subject do
143
+ parser = EarleyParser.new(b_expr_grammar)
144
+ tokens = expr_tokenizer('2 + 3 * 4', b_expr_grammar)
145
+ instance = parser.parse(tokens)
146
+ end
147
+
148
+ # Helper. Build a state tracker and a parse tree builder.
149
+ def prepare_parse_tree(aParsing)
150
+ # Accessing private methods by sending message
151
+ state_tracker = aParsing.send(:new_state_tracker)
152
+ builder = aParsing.send(:tree_builder, state_tracker.state_set_index)
153
+ return [state_tracker, builder]
154
+ end
155
+
156
+
157
+ it 'should create the root of a parse tree' do
158
+ (state_tracker, builder) = prepare_parse_tree(subject)
159
+ # The root node should correspond to the start symbol and
160
+ # its direct children should correspond to rhs of start production
161
+ expected_text = <<-SNIPPET
162
+ P[0, 5]
163
+ +- S[0, 5]
164
+ SNIPPET
165
+ root_text = builder.root.to_string(0)
166
+ expect(root_text).to eq(expected_text.chomp)
167
+
168
+ expect(state_tracker.state_set_index).to eq(subject.tokens.size)
169
+ expected_state = 'P => S . | 0'
170
+ expect(state_tracker.parse_state.to_s).to eq(expected_state)
171
+ expect(builder.current_node.to_string(0)).to eq('S[0, 5]')
172
+ end
173
+
174
+ it 'should use a reduce item for a matched non-terminal' do
175
+ # Setup
176
+ (state_tracker, builder) = prepare_parse_tree(subject)
177
+ # Same state as in previous example
178
+
179
+ # Given matched symbol is S[0, 5]
180
+ # And its reduce item is S => S + M . | 0
181
+ # Then add child nodes corresponding to the rhs symbols
182
+ # And make M[?, 5] the current symbol
183
+ subject.insert_matched_symbol(state_tracker, builder)
184
+ expected_text = <<-SNIPPET
185
+ P[0, 5]
186
+ +- S[0, 5]
187
+ +- S[0, ?]
188
+ +- +[?, ?]: '(nil)'
189
+ +- M[?, 5]
190
+ SNIPPET
191
+ root_text = builder.root.to_string(0)
192
+ expect(root_text).to eq(expected_text.chomp)
193
+ expected_state = 'S => S + M . | 0'
194
+ expect(state_tracker.parse_state.to_s).to eq(expected_state)
195
+ expect(state_tracker.state_set_index).to eq(5)
196
+ expect(builder.current_node.to_string(0)).to eq('M[?, 5]')
197
+
198
+ # Second similar test
199
+
200
+ # Given matched symbol is M[?, 5]
201
+ # And its reduce item is M => M * T . | 2
202
+ # Then add child nodes corresponding to the rhs symbols
203
+ # And make T[?, 5] the current symbol
204
+ subject.insert_matched_symbol(state_tracker, builder)
205
+ expected_text = <<-SNIPPET
206
+ P[0, 5]
207
+ +- S[0, 5]
208
+ +- S[0, ?]
209
+ +- +[?, ?]: '(nil)'
210
+ +- M[2, 5]
211
+ +- M[2, ?]
212
+ +- *[?, ?]: '(nil)'
213
+ +- T[?, 5]
214
+ SNIPPET
215
+ root_text = builder.root.to_string(0)
216
+ expect(root_text).to eq(expected_text.chomp)
217
+ expected_state = 'M => M * T . | 2'
218
+ expect(state_tracker.parse_state.to_s).to eq(expected_state)
219
+ expect(state_tracker.state_set_index).to eq(5)
220
+ expect(builder.current_node.to_string(0)).to eq('T[?, 5]')
221
+ end
222
+
223
+
224
+
225
+ it 'should use a previous item for a terminal symbol' do
226
+ # Setup
227
+ (state_tracker, builder) = prepare_parse_tree(subject)
228
+ 3.times do
229
+ subject.insert_matched_symbol(state_tracker, builder)
230
+ end
231
+
232
+ # Given matched symbol is T[?, 5]
233
+ # And its reduce item is T => integer . | 4
234
+ # Then add child node corresponding to the rhs symbol
235
+ # And make integer[4, 5]: '(nil)' the current symbol
236
+ expected_text = <<-SNIPPET
237
+ P[0, 5]
238
+ +- S[0, 5]
239
+ +- S[0, ?]
240
+ +- +[?, ?]: '(nil)'
241
+ +- M[2, 5]
242
+ +- M[2, ?]
243
+ +- *[?, ?]: '(nil)'
244
+ +- T[4, 5]
245
+ +- integer[4, 5]: '(nil)'
246
+ SNIPPET
247
+ root_text = builder.root.to_string(0)
248
+ expect(root_text).to eq(expected_text.chomp)
249
+ expected_state = 'T => integer . | 4'
250
+ expect(state_tracker.parse_state.to_s).to eq(expected_state)
251
+ expect(state_tracker.state_set_index).to eq(5)
252
+ expect(builder.current_node.to_string(0)).to eq("integer[4, 5]: '(nil)'")
253
+
254
+ # Given current tree symbol is integer[4, 5]: '(nil)'
255
+ # And its previous item is T => . integer | 4
256
+ # Then attach the token to the terminal node
257
+ # And decrement the state index by one
258
+ # Make *[?, ?]: '(nil)' the current symbol
259
+ subject.insert_matched_symbol(state_tracker, builder)
260
+ expected_text = <<-SNIPPET
261
+ P[0, 5]
262
+ +- S[0, 5]
263
+ +- S[0, ?]
264
+ +- +[?, ?]: '(nil)'
265
+ +- M[2, 5]
266
+ +- M[2, ?]
267
+ +- *[?, ?]: '(nil)'
268
+ +- T[4, 5]
269
+ +- integer[4, 5]: '4'
270
+ SNIPPET
271
+ root_text = builder.root.to_string(0)
272
+ expect(root_text).to eq(expected_text.chomp)
273
+ expected_state = 'T => . integer | 4'
274
+ expect(state_tracker.parse_state.to_s).to eq(expected_state)
275
+ expect(state_tracker.state_set_index).to eq(4)
276
+ next_symbol = "*[?, ?]: '(nil)'"
277
+ expect(builder.current_node.to_string(0)).to eq(next_symbol)
278
+ end
279
+
280
+ it 'should handle [no symbol before dot, terminal tree node] case' do
281
+ # Setup
282
+ (state_tracker, builder) = prepare_parse_tree(subject)
283
+ 4.times do
284
+ subject.insert_matched_symbol(state_tracker, builder)
285
+ end
286
+
287
+ # Given current tree symbol is *[?, ?]: '(nil)'
288
+ # And current dotted item is T => . integer | 4
289
+ # When one retrieves the parse state expecting the T
290
+ # Then new parse state is changed to: M => M * . T | 2
291
+ subject.insert_matched_symbol(state_tracker, builder)
292
+
293
+ expected_text = <<-SNIPPET
294
+ P[0, 5]
295
+ +- S[0, 5]
296
+ +- S[0, ?]
297
+ +- +[?, ?]: '(nil)'
298
+ +- M[2, 5]
299
+ +- M[2, ?]
300
+ +- *[?, ?]: '(nil)'
301
+ +- T[4, 5]
302
+ +- integer[4, 5]: '4'
303
+ SNIPPET
304
+ root_text = builder.root.to_string(0)
305
+ expect(root_text).to eq(expected_text.chomp)
306
+ expected_state = 'M => M * . T | 2'
307
+ expect(state_tracker.parse_state.to_s).to eq(expected_state)
308
+ expect(state_tracker.state_set_index).to eq(4)
309
+ next_symbol = "*[?, ?]: '(nil)'"
310
+ expect(builder.current_node.to_string(0)).to eq(next_symbol)
311
+
312
+ subject.insert_matched_symbol(state_tracker, builder)
313
+ next_symbol = 'M[2, ?]'
314
+ expect(builder.current_node.to_string(0)).to eq(next_symbol)
315
+ end
316
+
317
+ it 'should handle the end of parse tree generation' do
318
+ # Begin setup
319
+ is_done = false
320
+ (state_tracker, builder) = prepare_parse_tree(subject)
321
+ 16.times do
322
+ is_done = subject.insert_matched_symbol(state_tracker, builder)
323
+ end
324
+
325
+ expected_text = <<-SNIPPET
326
+ P[0, 5]
327
+ +- S[0, 5]
328
+ +- S[0, 1]
329
+ +- M[0, 1]
330
+ +- T[0, 1]
331
+ +- integer[0, 1]: '2'
332
+ +- +[1, 2]: '+'
333
+ +- M[2, 5]
334
+ +- M[2, 3]
335
+ +- T[2, 3]
336
+ +- integer[2, 3]: '3'
337
+ +- *[3, 4]: '*'
338
+ +- T[4, 5]
339
+ +- integer[4, 5]: '4'
340
+ SNIPPET
341
+ root_text = builder.root.to_string(0)
342
+ expect(root_text).to eq(expected_text.chomp)
343
+
344
+ expected_state = 'T => . integer | 0'
345
+ expect(state_tracker.parse_state.to_s).to eq(expected_state)
346
+ expect(state_tracker.state_set_index).to eq(0)
347
+ expect(is_done).to eq(true)
348
+ end
349
+
350
+
142
351
 
143
352
  it 'should build the parse tree for a simple non-ambiguous grammar' do
144
353
  parser = EarleyParser.new(sample_grammar1)
@@ -155,76 +364,24 @@ module Rley # Open this namespace to avoid module qualifier prefixes
155
364
  expect(ptree).to be_kind_of(PTree::ParseTree)
156
365
 
157
366
  # Expect parse tree:
158
- # P[0, 5]
159
- # +- S[0, 5]
160
- # +- S[0, 1]
161
- # +- M[0, 1]
162
- # +- T[0, 1]
163
- # +- integer(2)[0, 1]
164
- # +- +[?, ?]
165
- # +- M[2, 5]
166
- expect(ptree.root.symbol). to eq(grm_symbol('P'))
167
- expect(ptree.root.range). to eq([0, 5])
168
- expect(ptree.root.children.size). to eq(1)
169
-
170
- node = ptree.root.children[0] # S
171
- expect(node.symbol). to eq(grm_symbol('S'))
172
- expect(node.range). to eq([0, 5])
173
- expect(node.children.size). to eq(3)
174
-
175
- (node_s, node_plus, node_m) = node.children
176
- expect(node_s.symbol).to eq(grm_symbol('S'))
177
- expect(node_s.range).to eq(low: 0, high: 1)
178
- expect(node_s.children.size).to eq(1)
179
- expect(node_plus.symbol).to eq(grm_symbol('+'))
180
- expect(node_plus.range).to eq(low: 0, high: 1) # TODO: fix this
181
- expect(node_plus.token.lexeme). to eq('+')
182
- expect(node_m.symbol).to eq(grm_symbol('M'))
183
- expect(node_m.range).to eq(low: 2, high: 5)
184
- expect(node_m.children.size).to eq(3)
185
-
186
- node = node_s.children[0] # M
187
- expect(node.symbol).to eq(grm_symbol('M'))
188
- expect(node.range).to eq([0, 1])
189
- expect(node.children.size).to eq(1)
190
-
191
- node = node.children[0] # T
192
- expect(node.symbol).to eq(grm_symbol('T'))
193
- expect(node.range).to eq([0, 1])
194
- expect(node.children.size).to eq(1)
195
-
196
- node = node.children[0] # integer(2)
197
- expect(node.symbol).to eq(grm_symbol('integer'))
198
- expect(node.range).to eq([0, 1])
199
- expect(node.token.lexeme).to eq('2')
200
-
201
- (node_m2, node_star, node_t3) = node_m.children
202
- expect(node_m2.symbol).to eq(grm_symbol('M'))
203
- expect(node_m2.range).to eq([2, 3])
204
- expect(node_m2.children.size).to eq(1)
205
-
206
- node_t2 = node_m2.children[0] # T
207
- expect(node_t2.symbol).to eq(grm_symbol('T'))
208
- expect(node_t2.range).to eq([2, 3])
209
- expect(node_t2.children.size).to eq(1)
210
-
211
- node = node_t2.children[0] # integer(3)
212
- expect(node.symbol).to eq(grm_symbol('integer'))
213
- expect(node.range).to eq([2, 3])
214
- expect(node.token.lexeme).to eq('3')
215
-
216
- expect(node_star.symbol).to eq(grm_symbol('*'))
217
- expect(node_star.range).to eq([2, 3]) # Fix this
218
- expect(node_star.token.lexeme). to eq('*')
219
-
220
- expect(node_t3.symbol).to eq(grm_symbol('T'))
221
- expect(node_t3.range).to eq([4, 5])
222
- expect(node_t3.children.size).to eq(1)
223
-
224
- node = node_t3.children[0] # integer(4)
225
- expect(node.symbol).to eq(grm_symbol('integer'))
226
- expect(node.range).to eq([4, 5])
227
- expect(node.token.lexeme).to eq('4')
367
+ expected_text = <<-SNIPPET
368
+ P[0, 5]
369
+ +- S[0, 5]
370
+ +- S[0, 1]
371
+ +- M[0, 1]
372
+ +- T[0, 1]
373
+ +- integer[0, 1]: '2'
374
+ +- +[1, 2]: '+'
375
+ +- M[2, 5]
376
+ +- M[2, 3]
377
+ +- T[2, 3]
378
+ +- integer[2, 3]: '3'
379
+ +- *[3, 4]: '*'
380
+ +- T[4, 5]
381
+ +- integer[4, 5]: '4'
382
+ SNIPPET
383
+ actual = ptree.root.to_string(0)
384
+ expect(actual).to eq(expected_text.chomp)
228
385
  end
229
386
  end # context
230
387
  end # describe