rley 0.2.01 → 0.2.02

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.
@@ -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