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.
- checksums.yaml +4 -4
- data/.rubocop.yml +29 -5
- data/CHANGELOG.md +28 -4
- data/README.md +4 -5
- data/examples/NLP/nano_eng/nano_en_demo.rb +7 -11
- data/examples/NLP/nano_eng/nano_grammar.rb +18 -18
- data/examples/data_formats/JSON/json_ast_builder.rb +9 -18
- data/examples/data_formats/JSON/json_demo.rb +1 -2
- data/examples/data_formats/JSON/json_grammar.rb +11 -11
- data/examples/general/calc_iter1/calc_grammar.rb +5 -4
- data/examples/general/calc_iter2/calc_grammar.rb +9 -9
- data/examples/general/left.rb +1 -1
- data/examples/general/right.rb +1 -1
- data/lib/rley/base/dotted_item.rb +5 -0
- data/lib/rley/base/grm_items_builder.rb +6 -0
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/engine.rb +2 -2
- data/lib/rley/interface.rb +16 -0
- data/lib/rley/notation/all_notation_nodes.rb +4 -0
- data/lib/rley/notation/ast_builder.rb +185 -0
- data/lib/rley/notation/ast_node.rb +44 -0
- data/lib/rley/notation/ast_visitor.rb +115 -0
- data/lib/rley/notation/grammar.rb +49 -0
- data/lib/rley/notation/grammar_builder.rb +505 -0
- data/lib/rley/notation/grouping_node.rb +23 -0
- data/lib/rley/notation/parser.rb +56 -0
- data/lib/rley/notation/sequence_node.rb +35 -0
- data/lib/rley/notation/symbol_node.rb +29 -0
- data/lib/rley/notation/tokenizer.rb +180 -0
- data/lib/rley/parse_rep/ast_base_builder.rb +44 -0
- data/lib/rley/parser/gfg_chart.rb +101 -6
- data/lib/rley/parser/gfg_earley_parser.rb +1 -1
- data/lib/rley/parser/gfg_parsing.rb +5 -3
- data/lib/rley/parser/parse_entry_set.rb +1 -1
- data/lib/rley/syntax/{grammar_builder.rb → base_grammar_builder.rb} +53 -15
- data/lib/rley/syntax/grm_symbol.rb +1 -1
- data/lib/rley/syntax/match_closest.rb +43 -0
- data/lib/rley/syntax/production.rb +6 -0
- data/lib/rley.rb +1 -1
- data/spec/rley/engine_spec.rb +6 -6
- data/spec/rley/gfg/grm_flow_graph_spec.rb +2 -2
- data/spec/rley/notation/grammar_builder_spec.rb +302 -0
- data/spec/rley/notation/parser_spec.rb +183 -0
- data/spec/rley/notation/tokenizer_spec.rb +364 -0
- data/spec/rley/parse_rep/ast_builder_spec.rb +0 -1
- data/spec/rley/parse_rep/groucho_spec.rb +1 -1
- data/spec/rley/parse_rep/parse_forest_builder_spec.rb +1 -1
- data/spec/rley/parse_rep/parse_forest_factory_spec.rb +2 -2
- data/spec/rley/parse_rep/parse_tree_factory_spec.rb +1 -1
- data/spec/rley/parser/dangling_else_spec.rb +447 -0
- data/spec/rley/parser/gfg_earley_parser_spec.rb +118 -10
- data/spec/rley/parser/gfg_parsing_spec.rb +2 -1
- data/spec/rley/parser/parse_walker_factory_spec.rb +2 -2
- data/spec/rley/support/ambiguous_grammar_helper.rb +2 -2
- data/spec/rley/support/grammar_abc_helper.rb +2 -2
- data/spec/rley/support/grammar_ambig01_helper.rb +2 -2
- data/spec/rley/support/grammar_arr_int_helper.rb +2 -2
- data/spec/rley/support/grammar_b_expr_helper.rb +2 -2
- data/spec/rley/support/grammar_int_seq_helper.rb +51 -0
- data/spec/rley/support/grammar_l0_helper.rb +2 -2
- data/spec/rley/support/grammar_pb_helper.rb +2 -2
- data/spec/rley/support/grammar_sppf_helper.rb +2 -2
- data/spec/rley/syntax/{grammar_builder_spec.rb → base_grammar_builder_spec.rb} +29 -11
- data/spec/rley/syntax/match_closest_spec.rb +46 -0
- data/spec/rley/syntax/production_spec.rb +4 -0
- metadata +29 -14
- data/lib/rley/parser/parse_state.rb +0 -78
- data/lib/rley/parser/parse_state_tracker.rb +0 -59
- data/lib/rley/parser/state_set.rb +0 -100
- data/spec/rley/parser/parse_state_spec.rb +0 -125
- data/spec/rley/parser/parse_tracer_spec.rb +0 -200
- data/spec/rley/parser/state_set_spec.rb +0 -130
@@ -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::
|
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::
|
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/
|
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::
|
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/
|
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
|