rley 0.2.15 → 0.3.00

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/lib/rley/constants.rb +1 -1
  4. data/lib/rley/gfg/call_edge.rb +30 -0
  5. data/lib/rley/gfg/edge.rb +4 -0
  6. data/lib/rley/gfg/end_vertex.rb +1 -1
  7. data/lib/rley/gfg/epsilon_edge.rb +0 -4
  8. data/lib/rley/gfg/grm_flow_graph.rb +32 -7
  9. data/lib/rley/gfg/item_vertex.rb +71 -25
  10. data/lib/rley/gfg/non_terminal_vertex.rb +10 -1
  11. data/lib/rley/gfg/return_edge.rb +31 -0
  12. data/lib/rley/gfg/scan_edge.rb +2 -1
  13. data/lib/rley/gfg/shortcut_edge.rb +26 -0
  14. data/lib/rley/gfg/start_vertex.rb +2 -2
  15. data/lib/rley/gfg/vertex.rb +27 -1
  16. data/lib/rley/parse_forest_visitor.rb +115 -0
  17. data/lib/rley/parser/base_parser.rb +27 -0
  18. data/lib/rley/parser/dotted_item.rb +11 -0
  19. data/lib/rley/parser/earley_parser.rb +3 -15
  20. data/lib/rley/parser/gfg_chart.rb +106 -0
  21. data/lib/rley/parser/gfg_earley_parser.rb +139 -0
  22. data/lib/rley/parser/gfg_parsing.rb +384 -0
  23. data/lib/rley/parser/parse_entry.rb +148 -0
  24. data/lib/rley/parser/parse_entry_set.rb +104 -0
  25. data/lib/rley/parser/parse_entry_tracker.rb +56 -0
  26. data/lib/rley/parser/parse_forest_builder.rb +229 -0
  27. data/lib/rley/parser/parse_forest_factory.rb +54 -0
  28. data/lib/rley/parser/parse_walker_factory.rb +237 -0
  29. data/lib/rley/ptree/token_range.rb +14 -1
  30. data/lib/rley/sppf/alternative_node.rb +34 -0
  31. data/lib/rley/sppf/composite_node.rb +27 -0
  32. data/lib/rley/sppf/epsilon_node.rb +27 -0
  33. data/lib/rley/sppf/leaf_node.rb +12 -0
  34. data/lib/rley/sppf/non_terminal_node.rb +38 -0
  35. data/lib/rley/sppf/parse_forest.rb +48 -0
  36. data/lib/rley/sppf/sppf_node.rb +24 -0
  37. data/lib/rley/sppf/token_node.rb +29 -0
  38. data/lib/rley/syntax/grammar_builder.rb +16 -12
  39. data/lib/rley/syntax/grm_symbol.rb +6 -0
  40. data/lib/rley/syntax/terminal.rb +5 -0
  41. data/spec/rley/gfg/call_edge_spec.rb +51 -0
  42. data/spec/rley/gfg/end_vertex_spec.rb +1 -0
  43. data/spec/rley/gfg/grm_flow_graph_spec.rb +24 -2
  44. data/spec/rley/gfg/item_vertex_spec.rb +75 -6
  45. data/spec/rley/gfg/non_terminal_vertex_spec.rb +14 -0
  46. data/spec/rley/gfg/return_edge_spec.rb +51 -0
  47. data/spec/rley/gfg/shortcut_edge_spec.rb +43 -0
  48. data/spec/rley/gfg/vertex_spec.rb +52 -37
  49. data/spec/rley/parse_forest_visitor_spec.rb +238 -0
  50. data/spec/rley/parser/dotted_item_spec.rb +29 -8
  51. data/spec/rley/parser/gfg_chart_spec.rb +138 -0
  52. data/spec/rley/parser/gfg_earley_parser_spec.rb +918 -0
  53. data/spec/rley/parser/gfg_parsing_spec.rb +565 -0
  54. data/spec/rley/parser/parse_entry_set_spec.rb +179 -0
  55. data/spec/rley/parser/parse_entry_spec.rb +208 -0
  56. data/spec/rley/parser/parse_forest_builder_spec.rb +382 -0
  57. data/spec/rley/parser/parse_forest_factory_spec.rb +81 -0
  58. data/spec/rley/parser/parse_walker_factory_spec.rb +235 -0
  59. data/spec/rley/parser/state_set_spec.rb +4 -0
  60. data/spec/rley/sppf/alternative_node_spec.rb +72 -0
  61. data/spec/rley/sppf/antecedence_graph.rb +87 -0
  62. data/spec/rley/sppf/forest_representation.rb +136 -0
  63. data/spec/rley/sppf/gfg_representation.rb +111 -0
  64. data/spec/rley/sppf/non_terminal_node_spec.rb +64 -0
  65. data/spec/rley/support/ambiguous_grammar_helper.rb +36 -36
  66. data/spec/rley/support/expectation_helper.rb +36 -0
  67. data/spec/rley/support/grammar_helper.rb +28 -0
  68. data/spec/rley/support/grammar_sppf_helper.rb +25 -0
  69. data/spec/rley/syntax/grammar_builder_spec.rb +5 -0
  70. data/spec/rley/syntax/non_terminal_spec.rb +4 -0
  71. data/spec/rley/syntax/terminal_spec.rb +4 -0
  72. metadata +58 -2
@@ -0,0 +1,565 @@
1
+ require_relative '../../spec_helper'
2
+ require 'stringio'
3
+
4
+ require_relative '../../../lib/rley/syntax/non_terminal'
5
+ require_relative '../../../lib/rley/syntax/verbatim_symbol'
6
+ require_relative '../../../lib/rley/syntax/production'
7
+ require_relative '../../../lib/rley/syntax/grammar_builder'
8
+ require_relative '../../../lib/rley/parser/dotted_item'
9
+ require_relative '../../../lib/rley/parser/token'
10
+ require_relative '../../../lib/rley/parser/parse_tracer'
11
+ require_relative '../../../lib/rley/gfg/grm_flow_graph'
12
+ require_relative '../../../lib/rley/parser/grm_items_builder'
13
+ require_relative '../support/grammar_abc_helper'
14
+ require_relative '../support/grammar_b_expr_helper'
15
+ require_relative '../support/grammar_helper'
16
+
17
+
18
+ require_relative '../../../lib/rley/parser/gfg_earley_parser'
19
+ # Load the class under test
20
+ require_relative '../../../lib/rley/parser/gfg_parsing'
21
+
22
+ module Rley # Open this namespace to avoid module qualifier prefixes
23
+ module Parser # Open this namespace to avoid module qualifier prefixes
24
+ describe GFGParsing do
25
+ include GrammarABCHelper # Mix-in module with builder for grammar abc
26
+ include GrammarBExprHelper # Mix-in with builder for simple expressions
27
+ include GrammarHelper # Mix-in with method for creating token sequence
28
+
29
+ # Helper method. Create an array of dotted items
30
+ # from the given grammar
31
+ def build_items_for_grammar(aGrammar)
32
+ helper = Object.new
33
+ helper.extend(Parser::GrmItemsBuilder)
34
+ return helper.build_dotted_items(aGrammar)
35
+ end
36
+
37
+ # Factory method. Build a production with the given sequence
38
+ # of symbols as its rhs.
39
+ let(:grm1) do
40
+ builder = grammar_abc_builder
41
+ builder.grammar
42
+ end
43
+
44
+ let(:grm1_tokens) do
45
+ build_token_sequence(%w(a a b c c), grm1)
46
+ end
47
+
48
+ let(:grm1_token_b) { build_token_sequence(%w(b), grm1) }
49
+
50
+ # Helper method. Create an array of dotted items
51
+ # from the abc grammar
52
+ let(:items_from_grammar) { build_items_for_grammar(grm1) }
53
+ let(:sample_gfg) { GFG::GrmFlowGraph.new(items_from_grammar) }
54
+
55
+ let(:output) { StringIO.new('', 'w') }
56
+ let(:sample_tracer) { ParseTracer.new(0, output, grm1_tokens) }
57
+
58
+ # Default instantiation rule
59
+ subject do
60
+ GFGParsing.new(sample_gfg, grm1_tokens, sample_tracer)
61
+ end
62
+
63
+ context 'Initialization:' do
64
+ it 'should be created with a GFG, tokens, trace' do
65
+ expect { GFGParsing.new(sample_gfg, grm1_tokens, sample_tracer) }
66
+ .not_to raise_error
67
+ end
68
+
69
+ it 'should know the input tokens' do
70
+ expect(subject.tokens).to eq(grm1_tokens)
71
+ end
72
+
73
+ it 'should know its chart object' do
74
+ expect(subject.chart).to be_kind_of(GFGChart)
75
+ end
76
+
77
+ it 'should know the initial parse entry' do
78
+ expect(subject.initial_entry).to eq(subject.chart.initial_entry)
79
+ end
80
+
81
+ it 'should have no antecedence for the initial parse entry' do
82
+ expect(subject.antecedence.size).to eq(1)
83
+ expect(subject.antecedence.fetch(subject.initial_entry)).to be_empty
84
+ end
85
+
86
+ =begin
87
+ it 'should emit trace level 1 info' do
88
+ tracer = ParseTracer.new(1, output, grm1_tokens)
89
+ Parsing.new([ start_dotted_rule ], grm1_tokens, tracer)
90
+ expectations = <<-SNIPPET
91
+ ['a', 'a', 'b', 'c', 'c']
92
+ |. a . a . b . c . c .|
93
+ |> . . . . .| [0:0] S => . A
94
+ SNIPPET
95
+ expect(output.string).to eq(expectations)
96
+ end
97
+ =end
98
+ end # context
99
+
100
+ context 'Parsing:' do
101
+ # Utility method to fill the first entry set...
102
+ def fill_first_set()
103
+ subject.start_rule(subject.initial_entry, 0)
104
+ subject.call_rule(subject.chart[0].last, 0)
105
+ subject.start_rule(subject.chart[0].last, 0)
106
+ end
107
+
108
+ # Utility method to initialize the second entry set...
109
+ def seed_second_set()
110
+ # Cheating: we change surreptitiously the tokens to scan...
111
+ subject.instance_variable_set(:@tokens, grm1_token_b)
112
+
113
+ # Seeding second entry set...
114
+ subject.scan_rule(0)
115
+ end
116
+
117
+ # Utility method used to invoke the private method 'push_entry'
118
+ def push_entry(aParsing, *args)
119
+ aParsing.send(:push_entry, *args)
120
+ end
121
+
122
+ it 'should push a parse entry to a given chart entry set' do
123
+ expect(subject.chart[1]).to be_empty
124
+ a_vertex = sample_gfg.find_vertex('A => a . A c')
125
+
126
+ push_entry(subject, a_vertex, 1, 1, :scanning)
127
+ expect(subject.chart[1].size).to eq(1)
128
+ expect(subject.chart[1].first.vertex).to eq(a_vertex)
129
+
130
+ # Pushing twice the same state must be no-op
131
+ push_entry(subject, a_vertex, 1, 1, :scanning)
132
+ expect(subject.chart[1].size).to eq(1)
133
+
134
+ # Pushing to another entry set
135
+ push_entry(subject, a_vertex, 1, 2, :scanning)
136
+ expect(subject.chart[2].size).to eq(1)
137
+ end
138
+
139
+ it 'should complain when trying to push nil instead of vertex' do
140
+ err = StandardError
141
+ msg = 'Vertex may not be nil'
142
+ expect { push_entry(subject, nil, 1, 1, :start_rule) }
143
+ .to raise_error(err, msg)
144
+ end
145
+
146
+ it 'should use the start rule with initial entry' do
147
+ expect(subject.chart[0].size).to eq(1)
148
+ initial_entry = subject.initial_entry
149
+ subject.start_rule(initial_entry, 0)
150
+
151
+ expect(subject.chart[0].size).to eq(2)
152
+ new_entry = subject.chart[0].last
153
+ expect(new_entry.vertex.label).to eq('S => . A')
154
+ expect(subject.antecedence.fetch(new_entry)).to eq([initial_entry])
155
+ end
156
+
157
+ it 'should apply the call rule correctly' do
158
+ subject.start_rule(subject.initial_entry, 0)
159
+ # A parse entry with vertex 'S => . A' was added...
160
+ second_entry = subject.chart[0].last
161
+ subject.call_rule(second_entry, 0)
162
+
163
+ expect(subject.chart[0].size).to eq(3)
164
+ new_entry = subject.chart[0].last
165
+ expect(new_entry.vertex.label).to eq('.A')
166
+ expect(subject.antecedence.fetch(new_entry)).to eq([second_entry])
167
+ end
168
+
169
+ it 'should apply the start rule correctly' do
170
+ subject.start_rule(subject.chart[0].first, 0)
171
+ subject.call_rule(subject.chart[0].last, 0)
172
+ expect(subject.chart[0].size).to eq(3)
173
+ # Last entry is: (.A, 0)
174
+ dot_A_entry = subject.chart[0].last
175
+
176
+ subject.start_rule(dot_A_entry, 0)
177
+
178
+ # Expectations: two entries:
179
+ expected = ['A => . a A c', 'A => . b']
180
+ expect(subject.chart[0].size).to eq(5)
181
+ expect(subject.chart[0].pop.vertex.label).to eq(expected.last)
182
+ fourth_entry = subject.chart[0].last
183
+ expect(fourth_entry.vertex.label).to eq(expected.first)
184
+ expect(subject.antecedence.fetch(fourth_entry)).to eq([dot_A_entry])
185
+ end
186
+
187
+ it 'should apply the scan rule correctly' do
188
+ # Filling manually first entry set...
189
+ fill_first_set()
190
+ # There are two entries expecting a terminal:
191
+ # ['A => . a A c', 'A => . b']
192
+ fourth_entry = subject.chart[0].entries[3] # 'A => . a A c'
193
+
194
+ expect(subject.chart[1]).to be_empty
195
+ subject.scan_rule(0)
196
+ # Given that the scanned token is 'a'...
197
+ # Then a new entry is added in next entry set
198
+ expect(subject.chart[1].size).to eq(1)
199
+ last_entry = subject.chart[1].last
200
+
201
+ # Entry must be past the terminal symbol
202
+ expect(last_entry.vertex.label).to eq('A => a . A c')
203
+ expect(last_entry.origin).to eq(0)
204
+ expect(subject.antecedence.fetch(last_entry)).to eq([fourth_entry])
205
+ end
206
+
207
+ it 'should apply the exit rule correctly' do
208
+ # Filling manually first entry set...
209
+ fill_first_set()
210
+
211
+ # Initial manually first entry set...
212
+ seed_second_set()
213
+
214
+ # Given that the scanned token is 'b'...
215
+ # Then a new entry is added in next entry set
216
+ expect(subject.chart[1].size).to eq(1)
217
+ last_entry = subject.chart[1].last
218
+
219
+ # Entry must be past the terminal symbol
220
+ expect(last_entry.vertex.label).to eq('A => b .')
221
+ expect(last_entry.origin).to eq(0)
222
+
223
+ # Apply exit rule...
224
+ subject.exit_rule(last_entry, 1)
225
+ expect(subject.chart[1].size).to eq(2)
226
+ exit_entry = subject.chart[1].last
227
+ expect(exit_entry.vertex.label).to eq('A.')
228
+ expect(exit_entry.origin).to eq(0)
229
+ expect(subject.antecedence.fetch(exit_entry)).to eq([last_entry])
230
+ end
231
+
232
+ it 'should apply the end rule correctly' do
233
+ # Filling manually first entry set...
234
+ fill_first_set()
235
+
236
+ # Initial manually first entry set...
237
+ seed_second_set()
238
+ last_entry = subject.chart[1].last
239
+
240
+ # Given that the scanned token is 'b'...
241
+ # New entry must be past the terminal symbol
242
+ expect(last_entry.vertex.label).to eq('A => b .')
243
+
244
+ # Apply exit rule...
245
+ subject.exit_rule(last_entry, 1)
246
+ expect(subject.chart[1].size).to eq(2)
247
+ exit_entry = subject.chart[1].last
248
+ expect(exit_entry.vertex.label).to eq('A.')
249
+
250
+ # ... Now the end rule
251
+ subject.end_rule(subject.chart[1].last, 1)
252
+ expect(subject.chart[1].size).to eq(3)
253
+ end_entry = subject.chart[1].last
254
+ expect(end_entry.vertex.label).to eq('S => A .')
255
+ expect(end_entry.origin).to eq(0)
256
+ expect(subject.antecedence.fetch(end_entry)).to eq([exit_entry])
257
+ end
258
+ =begin
259
+
260
+
261
+
262
+ it 'should retrieve the parse states that expect a given terminal' do
263
+ item1 = DottedItem.new(prod_A1, 2)
264
+ item2 = DottedItem.new(prod_A1, 1)
265
+ subject.push_state(item1, 2, 2, :scanning)
266
+ subject.push_state(item2, 2, 2, :scanning)
267
+ states = subject.states_expecting(c_, 2, false)
268
+ expect(states.size).to eq(1)
269
+ expect(states[0].dotted_rule).to eq(item1)
270
+ end
271
+
272
+ it 'should update the states upon token match' do
273
+ # When a input token matches an expected terminal symbol
274
+ # then new parse states must be pushed to the following chart slot
275
+ expect(subject.chart[1]).to be_empty
276
+
277
+ item1 = DottedItem.new(prod_A1, 0)
278
+ item2 = DottedItem.new(prod_A2, 0)
279
+ subject.push_state(item1, 0, 0, :completion)
280
+ subject.push_state(item2, 0, 0, :completion)
281
+ subject.scanning(a_, 0) { |i| i } # Code block is mock
282
+
283
+ # Expected side effect: a new state at chart[1]
284
+ expect(subject.chart[1].size).to eq(1)
285
+ new_state = subject.chart[1].states[0]
286
+ expect(new_state.dotted_rule).to eq(item1)
287
+ expect(new_state.origin).to eq(0)
288
+ end
289
+ =end
290
+ end # context
291
+
292
+ context 'Parse forest building:' do
293
+
294
+ let(:sample_grammar1) do
295
+ builder = grammar_abc_builder
296
+ builder.grammar
297
+ end
298
+
299
+ let(:token_seq1) do
300
+ %w(a a b c c).map do |letter|
301
+ Token.new(letter, sample_grammar1.name2symbol[letter])
302
+ end
303
+ end
304
+
305
+ let(:b_expr_grammar) do
306
+ builder = grammar_expr_builder
307
+ builder.grammar
308
+ end
309
+
310
+ def grm_symbol(aSymbolName)
311
+ b_expr_grammar.name2symbol[aSymbolName]
312
+ end
313
+
314
+ subject do
315
+ parser = GFGEarleyParser.new(b_expr_grammar)
316
+ tokens = expr_tokenizer('2 + 3 * 4', b_expr_grammar)
317
+ parser.parse(tokens)
318
+ end
319
+
320
+ # Helper. Build a state tracker and a parse tree builder.
321
+ def prepare_parse_forest(aParsing)
322
+ # Accessing private methods by sending message
323
+ entry_tracker = aParsing.send(:new_entry_tracker)
324
+ builder = aParsing.send(:forest_builder, entry_tracker.entry_set_index)
325
+ return [entry_tracker, builder]
326
+ end
327
+ =begin
328
+ it 'should create the root of a parse forest' do
329
+ (entry_tracker, builder) = prepare_parse_forest(subject)
330
+ # The root node should correspond to the start symbol and
331
+ # its direct children should correspond to rhs of start production
332
+ expected_text = <<-SNIPPET
333
+ P[0, 5]
334
+ +- S[0, 5]
335
+ SNIPPET
336
+ root_text = builder.root.to_string(0)
337
+ expect(root_text).to eq(expected_text.chomp)
338
+
339
+ expect(entry_tracker.entry_set_index).to eq(subject.tokens.size)
340
+ expected_entry = 'P => S . | 0'
341
+ expect(entry_tracker.parse_entry.to_s).to eq(expected_entry)
342
+ expect(builder.current_node.to_string(0)).to eq('S[0, 5]')
343
+ end
344
+ =end
345
+ =begin
346
+ it 'should use a reduce item for a matched non-terminal' do
347
+ # Setup
348
+ (entry_tracker, builder) = prepare_parse_tree(subject)
349
+ # Same entry as in previous example
350
+
351
+ # Given matched symbol is S[0, 5]
352
+ # And its reduce item is S => S + M . | 0
353
+ # Then add child nodes corresponding to the rhs symbols
354
+ # And make M[?, 5] the current symbol
355
+ subject.insert_matched_symbol(entry_tracker, builder)
356
+ expected_text = <<-SNIPPET
357
+ P[0, 5]
358
+ +- S[0, 5]
359
+ +- S[0, ?]
360
+ +- +[?, ?]: '(nil)'
361
+ +- M[?, 5]
362
+ SNIPPET
363
+ root_text = builder.root.to_string(0)
364
+ expect(root_text).to eq(expected_text.chomp)
365
+ expected_entry = 'S => S + M . | 0'
366
+ expect(entry_tracker.parse_entry.to_s).to eq(expected_entry)
367
+ expect(entry_tracker.entry_set_index).to eq(5)
368
+ expect(builder.current_node.to_string(0)).to eq('M[?, 5]')
369
+
370
+ # Second similar test
371
+
372
+ # Given matched symbol is M[?, 5]
373
+ # And its reduce item is M => M * T . | 2
374
+ # Then add child nodes corresponding to the rhs symbols
375
+ # And make T[?, 5] the current symbol
376
+ subject.insert_matched_symbol(entry_tracker, builder)
377
+ expected_text = <<-SNIPPET
378
+ P[0, 5]
379
+ +- S[0, 5]
380
+ +- S[0, ?]
381
+ +- +[?, ?]: '(nil)'
382
+ +- M[2, 5]
383
+ +- M[2, ?]
384
+ +- *[?, ?]: '(nil)'
385
+ +- T[?, 5]
386
+ SNIPPET
387
+ root_text = builder.root.to_string(0)
388
+ expect(root_text).to eq(expected_text.chomp)
389
+ expected_entry = 'M => M * T . | 2'
390
+ expect(entry_tracker.parse_entry.to_s).to eq(expected_entry)
391
+ expect(entry_tracker.entry_set_index).to eq(5)
392
+ expect(builder.current_node.to_string(0)).to eq('T[?, 5]')
393
+ end
394
+
395
+
396
+
397
+ it 'should use a previous item for a terminal symbol' do
398
+ # Setup
399
+ (entry_tracker, builder) = prepare_parse_tree(subject)
400
+ 3.times do
401
+ subject.insert_matched_symbol(entry_tracker, builder)
402
+ end
403
+
404
+ # Given matched symbol is T[?, 5]
405
+ # And its reduce item is T => integer . | 4
406
+ # Then add child node corresponding to the rhs symbol
407
+ # And make integer[4, 5]: '(nil)' the current symbol
408
+ expected_text = <<-SNIPPET
409
+ P[0, 5]
410
+ +- S[0, 5]
411
+ +- S[0, ?]
412
+ +- +[?, ?]: '(nil)'
413
+ +- M[2, 5]
414
+ +- M[2, ?]
415
+ +- *[?, ?]: '(nil)'
416
+ +- T[4, 5]
417
+ +- integer[4, 5]: '(nil)'
418
+ SNIPPET
419
+ root_text = builder.root.to_string(0)
420
+ expect(root_text).to eq(expected_text.chomp)
421
+ expected_entry = 'T => integer . | 4'
422
+ expect(entry_tracker.parse_entry.to_s).to eq(expected_entry)
423
+ expect(entry_tracker.entry_set_index).to eq(5)
424
+ integer_repr = "integer[4, 5]: '(nil)'"
425
+ expect(builder.current_node.to_string(0)).to eq(integer_repr)
426
+
427
+ # Given current tree symbol is integer[4, 5]: '(nil)'
428
+ # And its previous item is T => . integer | 4
429
+ # Then attach the token to the terminal node
430
+ # And decrement the entry index by one
431
+ # Make *[?, ?]: '(nil)' the current symbol
432
+ subject.insert_matched_symbol(entry_tracker, builder)
433
+ expected_text = <<-SNIPPET
434
+ P[0, 5]
435
+ +- S[0, 5]
436
+ +- S[0, ?]
437
+ +- +[?, ?]: '(nil)'
438
+ +- M[2, 5]
439
+ +- M[2, ?]
440
+ +- *[?, ?]: '(nil)'
441
+ +- T[4, 5]
442
+ +- integer[4, 5]: '4'
443
+ SNIPPET
444
+ root_text = builder.root.to_string(0)
445
+ expect(root_text).to eq(expected_text.chomp)
446
+ expected_entry = 'T => . integer | 4'
447
+ expect(entry_tracker.parse_entry.to_s).to eq(expected_entry)
448
+ expect(entry_tracker.entry_set_index).to eq(4)
449
+ next_symbol = "*[?, ?]: '(nil)'"
450
+ expect(builder.current_node.to_string(0)).to eq(next_symbol)
451
+ end
452
+
453
+ it 'should handle [no symbol before dot, terminal tree node] case' do
454
+ # Setup
455
+ (entry_tracker, builder) = prepare_parse_tree(subject)
456
+ 4.times do
457
+ subject.insert_matched_symbol(entry_tracker, builder)
458
+ end
459
+
460
+ # Given current tree symbol is *[?, ?]: '(nil)'
461
+ # And current dotted item is T => . integer | 4
462
+ # When one retrieves the parse entry expecting the T
463
+ # Then new parse entry is changed to: M => M * . T | 2
464
+ subject.insert_matched_symbol(entry_tracker, builder)
465
+
466
+ expected_text = <<-SNIPPET
467
+ P[0, 5]
468
+ +- S[0, 5]
469
+ +- S[0, ?]
470
+ +- +[?, ?]: '(nil)'
471
+ +- M[2, 5]
472
+ +- M[2, ?]
473
+ +- *[?, ?]: '(nil)'
474
+ +- T[4, 5]
475
+ +- integer[4, 5]: '4'
476
+ SNIPPET
477
+ root_text = builder.root.to_string(0)
478
+ expect(root_text).to eq(expected_text.chomp)
479
+ expected_entry = 'M => M * . T | 2'
480
+ expect(entry_tracker.parse_entry.to_s).to eq(expected_entry)
481
+ expect(entry_tracker.entry_set_index).to eq(4)
482
+ next_symbol = "*[?, ?]: '(nil)'"
483
+ expect(builder.current_node.to_string(0)).to eq(next_symbol)
484
+
485
+ subject.insert_matched_symbol(entry_tracker, builder)
486
+ next_symbol = 'M[2, ?]'
487
+ expect(builder.current_node.to_string(0)).to eq(next_symbol)
488
+ end
489
+
490
+ it 'should handle the end of parse tree generation' do
491
+ # Begin setup
492
+ is_done = false
493
+ (entry_tracker, builder) = prepare_parse_tree(subject)
494
+ 16.times do
495
+ is_done = subject.insert_matched_symbol(entry_tracker, builder)
496
+ end
497
+
498
+ expected_text = <<-SNIPPET
499
+ P[0, 5]
500
+ +- S[0, 5]
501
+ +- S[0, 1]
502
+ +- M[0, 1]
503
+ +- T[0, 1]
504
+ +- integer[0, 1]: '2'
505
+ +- +[1, 2]: '+'
506
+ +- M[2, 5]
507
+ +- M[2, 3]
508
+ +- T[2, 3]
509
+ +- integer[2, 3]: '3'
510
+ +- *[3, 4]: '*'
511
+ +- T[4, 5]
512
+ +- integer[4, 5]: '4'
513
+ SNIPPET
514
+ root_text = builder.root.to_string(0)
515
+ expect(root_text).to eq(expected_text.chomp)
516
+
517
+ expected_entry = 'T => . integer | 0'
518
+ expect(entry_tracker.parse_entry.to_s).to eq(expected_entry)
519
+ expect(entry_tracker.entry_set_index).to eq(0)
520
+ expect(is_done).to eq(true)
521
+ end
522
+
523
+
524
+
525
+ it 'should build the parse tree for a simple non-ambiguous grammar' do
526
+ parser = EarleyParser.new(sample_grammar1)
527
+ instance = parser.parse(token_seq1)
528
+ ptree = instance.parse_tree
529
+ expect(ptree).to be_kind_of(PTree::ParseTree)
530
+ end
531
+
532
+ it 'should build the parse tree for a simple expression grammar' do
533
+ parser = EarleyParser.new(b_expr_grammar)
534
+ tokens = expr_tokenizer('2 + 3 * 4', b_expr_grammar)
535
+ instance = parser.parse(tokens)
536
+ ptree = instance.parse_tree
537
+ expect(ptree).to be_kind_of(PTree::ParseTree)
538
+
539
+ # Expect parse tree:
540
+ expected_text = <<-SNIPPET
541
+ P[0, 5]
542
+ +- S[0, 5]
543
+ +- S[0, 1]
544
+ +- M[0, 1]
545
+ +- T[0, 1]
546
+ +- integer[0, 1]: '2'
547
+ +- +[1, 2]: '+'
548
+ +- M[2, 5]
549
+ +- M[2, 3]
550
+ +- T[2, 3]
551
+ +- integer[2, 3]: '3'
552
+ +- *[3, 4]: '*'
553
+ +- T[4, 5]
554
+ +- integer[4, 5]: '4'
555
+ SNIPPET
556
+ actual = ptree.root.to_string(0)
557
+ expect(actual).to eq(expected_text.chomp)
558
+ end
559
+ =end
560
+ end # context
561
+ end # describe
562
+ end # module
563
+ end # module
564
+
565
+ # End of file