rley 0.2.15 → 0.3.00
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/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
|