rley 0.2.15 → 0.3.00
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/gfg/call_edge.rb +30 -0
- data/lib/rley/gfg/edge.rb +4 -0
- data/lib/rley/gfg/end_vertex.rb +1 -1
- data/lib/rley/gfg/epsilon_edge.rb +0 -4
- data/lib/rley/gfg/grm_flow_graph.rb +32 -7
- data/lib/rley/gfg/item_vertex.rb +71 -25
- data/lib/rley/gfg/non_terminal_vertex.rb +10 -1
- data/lib/rley/gfg/return_edge.rb +31 -0
- data/lib/rley/gfg/scan_edge.rb +2 -1
- data/lib/rley/gfg/shortcut_edge.rb +26 -0
- data/lib/rley/gfg/start_vertex.rb +2 -2
- data/lib/rley/gfg/vertex.rb +27 -1
- data/lib/rley/parse_forest_visitor.rb +115 -0
- data/lib/rley/parser/base_parser.rb +27 -0
- data/lib/rley/parser/dotted_item.rb +11 -0
- data/lib/rley/parser/earley_parser.rb +3 -15
- data/lib/rley/parser/gfg_chart.rb +106 -0
- data/lib/rley/parser/gfg_earley_parser.rb +139 -0
- data/lib/rley/parser/gfg_parsing.rb +384 -0
- data/lib/rley/parser/parse_entry.rb +148 -0
- data/lib/rley/parser/parse_entry_set.rb +104 -0
- data/lib/rley/parser/parse_entry_tracker.rb +56 -0
- data/lib/rley/parser/parse_forest_builder.rb +229 -0
- data/lib/rley/parser/parse_forest_factory.rb +54 -0
- data/lib/rley/parser/parse_walker_factory.rb +237 -0
- data/lib/rley/ptree/token_range.rb +14 -1
- data/lib/rley/sppf/alternative_node.rb +34 -0
- data/lib/rley/sppf/composite_node.rb +27 -0
- data/lib/rley/sppf/epsilon_node.rb +27 -0
- data/lib/rley/sppf/leaf_node.rb +12 -0
- data/lib/rley/sppf/non_terminal_node.rb +38 -0
- data/lib/rley/sppf/parse_forest.rb +48 -0
- data/lib/rley/sppf/sppf_node.rb +24 -0
- data/lib/rley/sppf/token_node.rb +29 -0
- data/lib/rley/syntax/grammar_builder.rb +16 -12
- data/lib/rley/syntax/grm_symbol.rb +6 -0
- data/lib/rley/syntax/terminal.rb +5 -0
- data/spec/rley/gfg/call_edge_spec.rb +51 -0
- data/spec/rley/gfg/end_vertex_spec.rb +1 -0
- data/spec/rley/gfg/grm_flow_graph_spec.rb +24 -2
- data/spec/rley/gfg/item_vertex_spec.rb +75 -6
- data/spec/rley/gfg/non_terminal_vertex_spec.rb +14 -0
- data/spec/rley/gfg/return_edge_spec.rb +51 -0
- data/spec/rley/gfg/shortcut_edge_spec.rb +43 -0
- data/spec/rley/gfg/vertex_spec.rb +52 -37
- data/spec/rley/parse_forest_visitor_spec.rb +238 -0
- data/spec/rley/parser/dotted_item_spec.rb +29 -8
- data/spec/rley/parser/gfg_chart_spec.rb +138 -0
- data/spec/rley/parser/gfg_earley_parser_spec.rb +918 -0
- data/spec/rley/parser/gfg_parsing_spec.rb +565 -0
- data/spec/rley/parser/parse_entry_set_spec.rb +179 -0
- data/spec/rley/parser/parse_entry_spec.rb +208 -0
- data/spec/rley/parser/parse_forest_builder_spec.rb +382 -0
- data/spec/rley/parser/parse_forest_factory_spec.rb +81 -0
- data/spec/rley/parser/parse_walker_factory_spec.rb +235 -0
- data/spec/rley/parser/state_set_spec.rb +4 -0
- data/spec/rley/sppf/alternative_node_spec.rb +72 -0
- data/spec/rley/sppf/antecedence_graph.rb +87 -0
- data/spec/rley/sppf/forest_representation.rb +136 -0
- data/spec/rley/sppf/gfg_representation.rb +111 -0
- data/spec/rley/sppf/non_terminal_node_spec.rb +64 -0
- data/spec/rley/support/ambiguous_grammar_helper.rb +36 -36
- data/spec/rley/support/expectation_helper.rb +36 -0
- data/spec/rley/support/grammar_helper.rb +28 -0
- data/spec/rley/support/grammar_sppf_helper.rb +25 -0
- data/spec/rley/syntax/grammar_builder_spec.rb +5 -0
- data/spec/rley/syntax/non_terminal_spec.rb +4 -0
- data/spec/rley/syntax/terminal_spec.rb +4 -0
- 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
|