rley 0.7.08 → 0.8.03

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/.rubocop.yml +29 -5
  3. data/CHANGELOG.md +28 -4
  4. data/README.md +4 -5
  5. data/examples/NLP/nano_eng/nano_en_demo.rb +7 -11
  6. data/examples/NLP/nano_eng/nano_grammar.rb +18 -18
  7. data/examples/data_formats/JSON/json_ast_builder.rb +9 -18
  8. data/examples/data_formats/JSON/json_demo.rb +1 -2
  9. data/examples/data_formats/JSON/json_grammar.rb +11 -11
  10. data/examples/general/calc_iter1/calc_grammar.rb +5 -4
  11. data/examples/general/calc_iter2/calc_grammar.rb +9 -9
  12. data/examples/general/left.rb +1 -1
  13. data/examples/general/right.rb +1 -1
  14. data/lib/rley/base/dotted_item.rb +5 -0
  15. data/lib/rley/base/grm_items_builder.rb +6 -0
  16. data/lib/rley/constants.rb +1 -1
  17. data/lib/rley/engine.rb +2 -2
  18. data/lib/rley/interface.rb +16 -0
  19. data/lib/rley/notation/all_notation_nodes.rb +4 -0
  20. data/lib/rley/notation/ast_builder.rb +185 -0
  21. data/lib/rley/notation/ast_node.rb +44 -0
  22. data/lib/rley/notation/ast_visitor.rb +115 -0
  23. data/lib/rley/notation/grammar.rb +49 -0
  24. data/lib/rley/notation/grammar_builder.rb +505 -0
  25. data/lib/rley/notation/grouping_node.rb +23 -0
  26. data/lib/rley/notation/parser.rb +56 -0
  27. data/lib/rley/notation/sequence_node.rb +35 -0
  28. data/lib/rley/notation/symbol_node.rb +29 -0
  29. data/lib/rley/notation/tokenizer.rb +180 -0
  30. data/lib/rley/parse_rep/ast_base_builder.rb +44 -0
  31. data/lib/rley/parser/gfg_chart.rb +101 -6
  32. data/lib/rley/parser/gfg_earley_parser.rb +1 -1
  33. data/lib/rley/parser/gfg_parsing.rb +5 -3
  34. data/lib/rley/parser/parse_entry_set.rb +1 -1
  35. data/lib/rley/syntax/{grammar_builder.rb → base_grammar_builder.rb} +53 -15
  36. data/lib/rley/syntax/grm_symbol.rb +1 -1
  37. data/lib/rley/syntax/match_closest.rb +43 -0
  38. data/lib/rley/syntax/production.rb +6 -0
  39. data/lib/rley.rb +1 -1
  40. data/spec/rley/engine_spec.rb +6 -6
  41. data/spec/rley/gfg/grm_flow_graph_spec.rb +2 -2
  42. data/spec/rley/notation/grammar_builder_spec.rb +302 -0
  43. data/spec/rley/notation/parser_spec.rb +183 -0
  44. data/spec/rley/notation/tokenizer_spec.rb +364 -0
  45. data/spec/rley/parse_rep/ast_builder_spec.rb +0 -1
  46. data/spec/rley/parse_rep/groucho_spec.rb +1 -1
  47. data/spec/rley/parse_rep/parse_forest_builder_spec.rb +1 -1
  48. data/spec/rley/parse_rep/parse_forest_factory_spec.rb +2 -2
  49. data/spec/rley/parse_rep/parse_tree_factory_spec.rb +1 -1
  50. data/spec/rley/parser/dangling_else_spec.rb +447 -0
  51. data/spec/rley/parser/gfg_earley_parser_spec.rb +118 -10
  52. data/spec/rley/parser/gfg_parsing_spec.rb +2 -1
  53. data/spec/rley/parser/parse_walker_factory_spec.rb +2 -2
  54. data/spec/rley/support/ambiguous_grammar_helper.rb +2 -2
  55. data/spec/rley/support/grammar_abc_helper.rb +2 -2
  56. data/spec/rley/support/grammar_ambig01_helper.rb +2 -2
  57. data/spec/rley/support/grammar_arr_int_helper.rb +2 -2
  58. data/spec/rley/support/grammar_b_expr_helper.rb +2 -2
  59. data/spec/rley/support/grammar_int_seq_helper.rb +51 -0
  60. data/spec/rley/support/grammar_l0_helper.rb +2 -2
  61. data/spec/rley/support/grammar_pb_helper.rb +2 -2
  62. data/spec/rley/support/grammar_sppf_helper.rb +2 -2
  63. data/spec/rley/syntax/{grammar_builder_spec.rb → base_grammar_builder_spec.rb} +29 -11
  64. data/spec/rley/syntax/match_closest_spec.rb +46 -0
  65. data/spec/rley/syntax/production_spec.rb +4 -0
  66. metadata +29 -14
  67. data/lib/rley/parser/parse_state.rb +0 -78
  68. data/lib/rley/parser/parse_state_tracker.rb +0 -59
  69. data/lib/rley/parser/state_set.rb +0 -100
  70. data/spec/rley/parser/parse_state_spec.rb +0 -125
  71. data/spec/rley/parser/parse_tracer_spec.rb +0 -200
  72. data/spec/rley/parser/state_set_spec.rb +0 -130
@@ -203,7 +203,6 @@ module Rley # Open this namespace to avoid module qualifier prefixes
203
203
  stack = get_stack(subject)
204
204
 
205
205
  next_event('visit P. | 0 7')
206
-
207
206
  next_event('visit P => arr . | 0 7')
208
207
  # stack: [P[0, 7]]
209
208
  expect(stack.last.children.size).to eq(1)
@@ -22,7 +22,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
22
22
  include ExpectationHelper # Mix-in with expectation on parse entry sets
23
23
 
24
24
  let(:sample_grammar) do
25
- builder = Rley::Syntax::GrammarBuilder.new do
25
+ builder = Rley::Syntax::BaseGrammarBuilder.new do
26
26
  add_terminals('N', 'V', 'Pro') # N(oun), V(erb), Pro(noun)
27
27
  add_terminals('Det', 'P') # Det(erminer), P(reposition)
28
28
  rule 'S' => 'NP VP'
@@ -23,7 +23,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
23
23
  # "SPPF=Style Parsing From Earley Recognizers" in
24
24
  # Notes in Theoretical Computer Science 203, (2008), pp. 53-67
25
25
  # contains a hidden left recursion and a cycle
26
- builder = Syntax::GrammarBuilder.new do
26
+ builder = Syntax::BaseGrammarBuilder.new do
27
27
  add_terminals('a', 'b')
28
28
  rule 'Phi' => 'S'
29
29
  rule 'S' => 'A T'
@@ -4,7 +4,7 @@ require_relative '../../spec_helper'
4
4
 
5
5
  require_relative '../../../lib/rley/parser/gfg_earley_parser'
6
6
 
7
- require_relative '../../../lib/rley/syntax/grammar_builder'
7
+ require_relative '../../../lib/rley/syntax/base_grammar_builder'
8
8
  require_relative '../support/grammar_helper'
9
9
  require_relative '../support/expectation_helper'
10
10
 
@@ -22,7 +22,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
22
22
  # "SPPF-Style Parsing From Earley Recognizers" in
23
23
  # Notes in Theoretical Computer Science 203, (2008), pp. 53-67
24
24
  # contains a hidden left recursion and a cycle
25
- builder = Syntax::GrammarBuilder.new do
25
+ builder = Syntax::BaseGrammarBuilder.new do
26
26
  add_terminals('a', 'b')
27
27
  rule 'Phi' => 'S'
28
28
  rule 'S' => 'A T'
@@ -3,7 +3,7 @@
3
3
  require_relative '../../spec_helper'
4
4
 
5
5
  require_relative '../../../lib/rley/parser/gfg_earley_parser'
6
- require_relative '../../../lib/rley/syntax/grammar_builder'
6
+ require_relative '../../../lib/rley/syntax/base_grammar_builder'
7
7
  require_relative '../support/grammar_helper'
8
8
  require_relative '../support/grammar_abc_helper'
9
9
  require_relative '../support/expectation_helper'
@@ -0,0 +1,447 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../spec_helper'
4
+ require 'stringio'
5
+ require_relative '../../../lib/rley/syntax/match_closest'
6
+ require_relative '../../../lib/rley/syntax/non_terminal'
7
+ require_relative '../../../lib/rley/syntax/production'
8
+ require_relative '../../../lib/rley/syntax/base_grammar_builder'
9
+ require_relative '../../../lib/rley/lexical/token'
10
+ require_relative '../../../lib/rley/base/dotted_item'
11
+ require_relative '../../../lib/rley/parser/gfg_parsing'
12
+
13
+ require_relative '../support/expectation_helper'
14
+
15
+ # Load the class under test
16
+ require_relative '../../../lib/rley/parser/gfg_earley_parser'
17
+
18
+ module Rley # Open this namespace to avoid module qualifier prefixes
19
+ module Parser # Open this namespace to avoid module qualifier prefixes
20
+ describe GFGEarleyParser do
21
+ include ExpectationHelper # Mix-in with expectation on parse entry sets
22
+
23
+ # rubocop: disable Lint/ConstantDefinitionInBlock
24
+
25
+ Keyword = {
26
+ 'else' => 'ELSE',
27
+ 'false' => 'FALSE',
28
+ 'if' => 'IF',
29
+ 'then' => 'THEN',
30
+ 'true' => 'TRUE'
31
+ }.freeze
32
+ # rubocop: enable Lint/ConstantDefinitionInBlock
33
+
34
+ def tokenizer(aTextToParse)
35
+ scanner = StringScanner.new(aTextToParse)
36
+ tokens = []
37
+
38
+ loop do
39
+ scanner.skip(/\s+/)
40
+ break if scanner.eos?
41
+
42
+ curr_pos = scanner.pos
43
+ lexeme = scanner.scan(/\S+/)
44
+
45
+ term_name = Keyword[lexeme]
46
+ unless term_name
47
+ if lexeme =~ /\d+/
48
+ term_name = 'INTEGER'
49
+ else
50
+ err_msg = "Unknown token '#{lexeme}'"
51
+ raise StandardError, err_msg
52
+ end
53
+ end
54
+ pos = Rley::Lexical::Position.new(1, curr_pos + 1)
55
+ tokens << Rley::Lexical::Token.new(lexeme, term_name, pos)
56
+ end
57
+
58
+ tokens
59
+ end
60
+
61
+ let(:input) { 'if false then if true then 1 else 2' }
62
+
63
+ context 'Ambiguous parse: ' do
64
+ # Factory method. Creates a grammar builder for a simple grammar.
65
+ def grammar_if_else_amb
66
+ builder = Rley::Syntax::BaseGrammarBuilder.new do
67
+ add_terminals('IF', 'THEN', 'ELSE')
68
+ add_terminals('FALSE', 'TRUE', 'INTEGER')
69
+
70
+ rule 'program' => 'stmt'
71
+ rule 'stmt' => 'IF boolean THEN stmt'
72
+ rule 'stmt' => 'IF boolean THEN stmt ELSE stmt'
73
+ rule 'stmt' => 'literal'
74
+ rule 'literal' => 'boolean'
75
+ rule 'literal' => 'INTEGER'
76
+ rule 'boolean' => 'FALSE'
77
+ rule 'boolean' => 'TRUE'
78
+ end
79
+
80
+ builder.grammar
81
+ end
82
+
83
+ subject { GFGEarleyParser.new(grammar_if_else_amb) }
84
+
85
+ it 'should parse a valid simple input' do
86
+ tokens = tokenizer(input)
87
+ parse_result = subject.parse(tokens)
88
+ expect(parse_result.success?).to eq(true)
89
+ expect(parse_result.ambiguous?).to eq(true)
90
+ ######################
91
+ # Expectation chart[0]:
92
+ expected = [
93
+ '.program | 0', # initialization
94
+ 'program => . stmt | 0', # start rule
95
+ '.stmt | 0', # call rule
96
+ 'stmt => . IF boolean THEN stmt | 0', # start rule
97
+ 'stmt => . IF boolean THEN stmt ELSE stmt | 0', # start rule
98
+ 'stmt => . literal | 0', # start rule
99
+ '.literal | 0', # call rule
100
+ 'literal => . boolean | 0', # start rule
101
+ 'literal => . INTEGER | 0', # start rule
102
+ '.boolean | 0', # call rule
103
+ 'boolean => . FALSE | 0', # start rule
104
+ 'boolean => . TRUE | 0' # start rule
105
+ ]
106
+ compare_entry_texts(parse_result.chart[0], expected)
107
+ expected_terminals(parse_result.chart[0], %w[FALSE IF INTEGER TRUE])
108
+
109
+ ######################
110
+ # Expectation chart[1]:
111
+ expected = [
112
+ 'stmt => IF . boolean THEN stmt | 0', # start rule
113
+ 'stmt => IF . boolean THEN stmt ELSE stmt | 0', # start rule
114
+ '.boolean | 1',
115
+ 'boolean => . FALSE | 1', # start rule
116
+ 'boolean => . TRUE | 1' # start rule
117
+ ]
118
+ result1 = parse_result.chart[1]
119
+ expect(result1.entries.size).to eq(5)
120
+ compare_entry_texts(result1, expected)
121
+ expected_terminals(result1, %w[FALSE TRUE])
122
+
123
+ ######################
124
+ # Expectation chart[2]:
125
+ expected = [
126
+ 'boolean => FALSE . | 1',
127
+ 'boolean. | 1',
128
+ 'stmt => IF boolean . THEN stmt | 0',
129
+ 'stmt => IF boolean . THEN stmt ELSE stmt | 0'
130
+ ]
131
+ result2 = parse_result.chart[2]
132
+ expect(result2.entries.size).to eq(4)
133
+ compare_entry_texts(result2, expected)
134
+ expected_terminals(result2, %w[THEN])
135
+
136
+ ######################
137
+ # Expectation chart[3]:
138
+ expected = [
139
+ 'stmt => IF boolean THEN . stmt | 0',
140
+ 'stmt => IF boolean THEN . stmt ELSE stmt | 0',
141
+ '.stmt | 3',
142
+ 'stmt => . IF boolean THEN stmt | 3',
143
+ 'stmt => . IF boolean THEN stmt ELSE stmt | 3',
144
+ 'stmt => . literal | 3',
145
+ '.literal | 3',
146
+ 'literal => . boolean | 3',
147
+ 'literal => . INTEGER | 3',
148
+ '.boolean | 3',
149
+ 'boolean => . FALSE | 3',
150
+ 'boolean => . TRUE | 3'
151
+ ]
152
+ result3 = parse_result.chart[3]
153
+ expect(result3.entries.size).to eq(12)
154
+ compare_entry_texts(result3, expected)
155
+ expected_terminals(result3, %w[FALSE IF INTEGER TRUE])
156
+
157
+
158
+ ######################
159
+ # Expectation chart[4]:
160
+ expected = [
161
+ 'stmt => IF . boolean THEN stmt | 3',
162
+ 'stmt => IF . boolean THEN stmt ELSE stmt | 3',
163
+ '.boolean | 4',
164
+ 'boolean => . FALSE | 4',
165
+ 'boolean => . TRUE | 4'
166
+ ]
167
+ result4 = parse_result.chart[4]
168
+ expect(result4.entries.size).to eq(5)
169
+ compare_entry_texts(result4, expected)
170
+ expected_terminals(result4, %w[FALSE TRUE])
171
+
172
+ ######################
173
+ # Expectation chart[5]:
174
+ expected = [
175
+ 'boolean => TRUE . | 4',
176
+ 'boolean. | 4',
177
+ 'stmt => IF boolean . THEN stmt | 3',
178
+ 'stmt => IF boolean . THEN stmt ELSE stmt | 3'
179
+ ]
180
+ result5 = parse_result.chart[5]
181
+ expect(result5.entries.size).to eq(4)
182
+ compare_entry_texts(result5, expected)
183
+ expected_terminals(result5, %w[THEN])
184
+
185
+ ######################
186
+ # Expectation chart[6]:
187
+ expected = [
188
+ 'stmt => IF boolean THEN . stmt | 3',
189
+ 'stmt => IF boolean THEN . stmt ELSE stmt | 3',
190
+ '.stmt | 6',
191
+ 'stmt => . IF boolean THEN stmt | 6',
192
+ 'stmt => . IF boolean THEN stmt ELSE stmt | 6',
193
+ 'stmt => . literal | 6',
194
+ '.literal | 6',
195
+ 'literal => . boolean | 6',
196
+ 'literal => . INTEGER | 6',
197
+ '.boolean | 6',
198
+ 'boolean => . FALSE | 6',
199
+ 'boolean => . TRUE | 6'
200
+ ]
201
+ result6 = parse_result.chart[6]
202
+ expect(result6.entries.size).to eq(12)
203
+ compare_entry_texts(result6, expected)
204
+ expected_terminals(result6, %w[FALSE IF INTEGER TRUE])
205
+
206
+ ######################
207
+ # Expectation chart[7]:
208
+ expected = [
209
+ 'literal => INTEGER . | 6',
210
+ 'literal. | 6',
211
+ 'stmt => literal . | 6',
212
+ 'stmt. | 6',
213
+ 'stmt => IF boolean THEN stmt . | 3',
214
+ 'stmt => IF boolean THEN stmt . ELSE stmt | 3',
215
+ 'stmt. | 3',
216
+ 'stmt => IF boolean THEN stmt . | 0',
217
+ 'stmt => IF boolean THEN stmt . ELSE stmt | 0',
218
+ 'stmt. | 0',
219
+ 'program => stmt . | 0',
220
+ 'program. | 0'
221
+ ]
222
+ result7 = parse_result.chart[7]
223
+ expect(result7.entries.size).to eq(12)
224
+ compare_entry_texts(result7, expected)
225
+ expected_terminals(result7, %w[ELSE])
226
+
227
+ # Expectation chart[8]:
228
+ expected = [
229
+ 'stmt => IF boolean THEN stmt ELSE . stmt | 3',
230
+ 'stmt => IF boolean THEN stmt ELSE . stmt | 0',
231
+ '.stmt | 8',
232
+ 'stmt => . IF boolean THEN stmt | 8',
233
+ 'stmt => . IF boolean THEN stmt ELSE stmt | 8',
234
+ 'stmt => . literal | 8',
235
+ '.literal | 8',
236
+ 'literal => . boolean | 8',
237
+ 'literal => . INTEGER | 8',
238
+ '.boolean | 8',
239
+ 'boolean => . FALSE | 8',
240
+ 'boolean => . TRUE | 8'
241
+ ]
242
+ result8 = parse_result.chart[8]
243
+ expect(result8.entries.size).to eq(12)
244
+ compare_entry_texts(result8, expected)
245
+ expected_terminals(result8, %w[FALSE IF INTEGER TRUE])
246
+
247
+ ######################
248
+ # Expectation chart[9]:
249
+ expected = [
250
+ 'literal => INTEGER . | 8',
251
+ 'literal. | 8',
252
+ 'stmt => literal . | 8',
253
+ 'stmt. | 8',
254
+ 'stmt => IF boolean THEN stmt ELSE stmt . | 3',
255
+ 'stmt => IF boolean THEN stmt ELSE stmt . | 0',
256
+ 'stmt. | 3',
257
+ 'stmt. | 0',
258
+ 'stmt => IF boolean THEN stmt . | 0',
259
+ 'stmt => IF boolean THEN stmt . ELSE stmt | 0',
260
+ 'program => stmt . | 0',
261
+ 'program. | 0'
262
+ ]
263
+ result9 = parse_result.chart[9]
264
+ expect(result9.entries.size).to eq(12)
265
+ compare_entry_texts(result9, expected)
266
+ expected_terminals(result9, %w[ELSE])
267
+
268
+ ######################
269
+ # Expectation chart[10]:
270
+ result10 = parse_result.chart[10]
271
+ expect(result10).to be_nil
272
+
273
+ # The parse is ambiguous since there more than one dotted item
274
+ # that matches the stmt. | 0 exit node on chart[9]:
275
+ # stmt => IF boolean THEN stmt ELSE stmt . | 0'
276
+ # stmt => IF boolean THEN stmt . | 0'
277
+ #
278
+ # This is related to the "dangling else problem"
279
+ end
280
+ end # context
281
+
282
+ context 'Disambiguated parse: ' do
283
+ def match_else_with_if(grammar)
284
+ # Brittle code
285
+ prod = grammar.rules[2]
286
+ constraint = Syntax::MatchClosest.new(prod.rhs.members, 4, 'IF')
287
+ prod.constraints << constraint
288
+ end
289
+
290
+ # Factory method. Creates a grammar builder for a simple grammar.
291
+ def grammar_if_else
292
+ builder = Rley::Syntax::BaseGrammarBuilder.new do
293
+ add_terminals('IF', 'THEN', 'ELSE')
294
+ add_terminals('FALSE', 'TRUE', 'INTEGER')
295
+
296
+ rule 'program' => 'stmt'
297
+ rule 'stmt' => 'IF boolean THEN stmt'
298
+
299
+ # To prevent dangling else issue, the ELSE must match the closest preceding IF
300
+ # rule 'stmt' => 'IF boolean THEN stmt ELSE{closest IF} stmt'
301
+ rule 'stmt' => 'IF boolean THEN stmt ELSE stmt'
302
+ rule 'stmt' => 'literal'
303
+ rule 'literal' => 'boolean'
304
+ rule 'literal' => 'INTEGER'
305
+ rule 'boolean' => 'FALSE'
306
+ rule 'boolean' => 'TRUE'
307
+ end
308
+
309
+ grm = builder.grammar
310
+ match_else_with_if(grm)
311
+
312
+ grm
313
+ end
314
+
315
+ subject { GFGEarleyParser.new(grammar_if_else) }
316
+
317
+ it 'should cope with dangling else problem' do
318
+ tokens = tokenizer(input)
319
+ parse_result = subject.parse(tokens)
320
+ expect(parse_result.success?).to eq(true)
321
+ expect(parse_result.ambiguous?).to eq(true)
322
+ ######################
323
+ # Expectation chart[0]:
324
+ expected = [
325
+ '.program | 0', # initialization
326
+ 'program => . stmt | 0', # start rule
327
+ '.stmt | 0', # call rule
328
+ 'stmt => . IF boolean THEN stmt | 0', # start rule
329
+ 'stmt => . IF boolean THEN stmt ELSE stmt | 0', # start rule
330
+ 'stmt => . literal | 0', # start rule
331
+ '.literal | 0', # call rule
332
+ 'literal => . boolean | 0', # start rule
333
+ 'literal => . INTEGER | 0', # start rule
334
+ '.boolean | 0', # call rule
335
+ 'boolean => . FALSE | 0', # start rule
336
+ 'boolean => . TRUE | 0' # start rule
337
+ ]
338
+ compare_entry_texts(parse_result.chart[0], expected)
339
+ expected_terminals(parse_result.chart[0], %w[FALSE IF INTEGER TRUE])
340
+
341
+ # The parser should work as the previous version...
342
+ # we skip chart[2] and chart[3]
343
+ ######################
344
+ # Expectation chart[4]:
345
+ expected = [
346
+ 'stmt => IF . boolean THEN stmt | 3',
347
+ 'stmt => IF . boolean THEN stmt ELSE stmt | 3',
348
+ '.boolean | 4',
349
+ 'boolean => . FALSE | 4',
350
+ 'boolean => . TRUE | 4'
351
+ ]
352
+ result4 = parse_result.chart[4]
353
+ expect(result4.entries.size).to eq(5)
354
+ compare_entry_texts(result4, expected)
355
+ expected_terminals(result4, %w[FALSE TRUE])
356
+
357
+ ######################
358
+ # Before reading ELSE
359
+ # Expectation chart[7]:
360
+ expected = [
361
+ 'literal => INTEGER . | 6',
362
+ 'literal. | 6',
363
+ 'stmt => literal . | 6',
364
+ 'stmt. | 6',
365
+ 'stmt => IF boolean THEN stmt . | 3',
366
+ 'stmt => IF boolean THEN stmt . ELSE stmt | 3',
367
+ 'stmt. | 3',
368
+ 'stmt => IF boolean THEN stmt . | 0',
369
+ 'stmt => IF boolean THEN stmt . ELSE stmt | 0',
370
+ 'stmt. | 0',
371
+ 'program => stmt . | 0',
372
+ 'program. | 0'
373
+ ]
374
+ result7 = parse_result.chart[7]
375
+ expect(result7.entries.size).to eq(12)
376
+ compare_entry_texts(result7, expected)
377
+ expected_terminals(result7, %w[ELSE])
378
+
379
+ ######################
380
+ # After reading ELSE
381
+ # Expectation chart[8]:
382
+ expected = [
383
+ 'stmt => IF boolean THEN stmt ELSE . stmt | 3',
384
+ # 'stmt => IF boolean THEN stmt ELSE . stmt | 0', # Excluded
385
+ '.stmt | 8',
386
+ 'stmt => . IF boolean THEN stmt | 8',
387
+ 'stmt => . IF boolean THEN stmt ELSE stmt | 8',
388
+ 'stmt => . literal | 8',
389
+ '.literal | 8',
390
+ 'literal => . boolean | 8',
391
+ 'literal => . INTEGER | 8',
392
+ '.boolean | 8',
393
+ 'boolean => . FALSE | 8',
394
+ 'boolean => . TRUE | 8'
395
+ ]
396
+ result8 = parse_result.chart[8]
397
+ # found = parse_result.chart.search_entries(4, { before: 'IF' })
398
+ expect(result8.entries.size).to eq(11)
399
+ compare_entry_texts(result8, expected)
400
+ expected_terminals(result8, %w[FALSE IF INTEGER TRUE])
401
+
402
+ # How does it work?
403
+ # ELSE was just read at position 7
404
+ # We look backwards to nearest IF; there is one at position 3
405
+ # In chart[8], we should exclude the dotted item:
406
+ # 'stmt => IF boolean THEN stmt ELSE . stmt | 0'
407
+ # Reasoning?
408
+ # On chart[4], we find two entries for the IF .:
409
+ # 'stmt => IF . boolean THEN stmt | 3',
410
+ # 'stmt => IF . boolean THEN stmt ELSE stmt | 3'
411
+ # Only these productions that still applies at 8 must be retained
412
+ # 'stmt => IF boolean THEN stmt ELSE . stmt | 3',
413
+ # 'stmt => IF boolean THEN stmt ELSE . stmt | 0', # To exclude
414
+ # Where to place the check?
415
+ # At the dotted item?
416
+ # call, return scan nodes
417
+ # So if one has an annotated production rule:
418
+ # stmt => IF boolean THEN stmt ELSE{ closest: IF } stmt
419
+ # then the dotted item:
420
+ # stmt => IF boolean THEN stmt ELSE . stmt
421
+ # should bear the constraint
422
+
423
+ ######################
424
+ # Expectation chart[9]:
425
+ expected = [
426
+ 'literal => INTEGER . | 8',
427
+ 'literal. | 8',
428
+ 'stmt => literal . | 8',
429
+ 'stmt. | 8',
430
+ 'stmt => IF boolean THEN stmt ELSE stmt . | 3',
431
+ # 'stmt => IF boolean THEN stmt ELSE stmt . | 0', # Excluded
432
+ 'stmt. | 3',
433
+ 'stmt => IF boolean THEN stmt . | 0',
434
+ 'stmt => IF boolean THEN stmt . ELSE stmt | 0',
435
+ 'stmt. | 0',
436
+ 'program => stmt . | 0',
437
+ 'program. | 0'
438
+ ]
439
+ result9 = parse_result.chart[9]
440
+ expect(result9.entries.size).to eq(11)
441
+ compare_entry_texts(result9, expected)
442
+ expected_terminals(result9, ['ELSE'])
443
+ end
444
+ end # context
445
+ end # describe
446
+ end # module
447
+ end # module