rley 0.7.08 → 0.8.03

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