rley 0.2.15 → 0.3.00

Sign up to get free protection for your applications and to get access to all the features.
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