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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/lib/rley/constants.rb +1 -1
  4. data/lib/rley/gfg/call_edge.rb +30 -0
  5. data/lib/rley/gfg/edge.rb +4 -0
  6. data/lib/rley/gfg/end_vertex.rb +1 -1
  7. data/lib/rley/gfg/epsilon_edge.rb +0 -4
  8. data/lib/rley/gfg/grm_flow_graph.rb +32 -7
  9. data/lib/rley/gfg/item_vertex.rb +71 -25
  10. data/lib/rley/gfg/non_terminal_vertex.rb +10 -1
  11. data/lib/rley/gfg/return_edge.rb +31 -0
  12. data/lib/rley/gfg/scan_edge.rb +2 -1
  13. data/lib/rley/gfg/shortcut_edge.rb +26 -0
  14. data/lib/rley/gfg/start_vertex.rb +2 -2
  15. data/lib/rley/gfg/vertex.rb +27 -1
  16. data/lib/rley/parse_forest_visitor.rb +115 -0
  17. data/lib/rley/parser/base_parser.rb +27 -0
  18. data/lib/rley/parser/dotted_item.rb +11 -0
  19. data/lib/rley/parser/earley_parser.rb +3 -15
  20. data/lib/rley/parser/gfg_chart.rb +106 -0
  21. data/lib/rley/parser/gfg_earley_parser.rb +139 -0
  22. data/lib/rley/parser/gfg_parsing.rb +384 -0
  23. data/lib/rley/parser/parse_entry.rb +148 -0
  24. data/lib/rley/parser/parse_entry_set.rb +104 -0
  25. data/lib/rley/parser/parse_entry_tracker.rb +56 -0
  26. data/lib/rley/parser/parse_forest_builder.rb +229 -0
  27. data/lib/rley/parser/parse_forest_factory.rb +54 -0
  28. data/lib/rley/parser/parse_walker_factory.rb +237 -0
  29. data/lib/rley/ptree/token_range.rb +14 -1
  30. data/lib/rley/sppf/alternative_node.rb +34 -0
  31. data/lib/rley/sppf/composite_node.rb +27 -0
  32. data/lib/rley/sppf/epsilon_node.rb +27 -0
  33. data/lib/rley/sppf/leaf_node.rb +12 -0
  34. data/lib/rley/sppf/non_terminal_node.rb +38 -0
  35. data/lib/rley/sppf/parse_forest.rb +48 -0
  36. data/lib/rley/sppf/sppf_node.rb +24 -0
  37. data/lib/rley/sppf/token_node.rb +29 -0
  38. data/lib/rley/syntax/grammar_builder.rb +16 -12
  39. data/lib/rley/syntax/grm_symbol.rb +6 -0
  40. data/lib/rley/syntax/terminal.rb +5 -0
  41. data/spec/rley/gfg/call_edge_spec.rb +51 -0
  42. data/spec/rley/gfg/end_vertex_spec.rb +1 -0
  43. data/spec/rley/gfg/grm_flow_graph_spec.rb +24 -2
  44. data/spec/rley/gfg/item_vertex_spec.rb +75 -6
  45. data/spec/rley/gfg/non_terminal_vertex_spec.rb +14 -0
  46. data/spec/rley/gfg/return_edge_spec.rb +51 -0
  47. data/spec/rley/gfg/shortcut_edge_spec.rb +43 -0
  48. data/spec/rley/gfg/vertex_spec.rb +52 -37
  49. data/spec/rley/parse_forest_visitor_spec.rb +238 -0
  50. data/spec/rley/parser/dotted_item_spec.rb +29 -8
  51. data/spec/rley/parser/gfg_chart_spec.rb +138 -0
  52. data/spec/rley/parser/gfg_earley_parser_spec.rb +918 -0
  53. data/spec/rley/parser/gfg_parsing_spec.rb +565 -0
  54. data/spec/rley/parser/parse_entry_set_spec.rb +179 -0
  55. data/spec/rley/parser/parse_entry_spec.rb +208 -0
  56. data/spec/rley/parser/parse_forest_builder_spec.rb +382 -0
  57. data/spec/rley/parser/parse_forest_factory_spec.rb +81 -0
  58. data/spec/rley/parser/parse_walker_factory_spec.rb +235 -0
  59. data/spec/rley/parser/state_set_spec.rb +4 -0
  60. data/spec/rley/sppf/alternative_node_spec.rb +72 -0
  61. data/spec/rley/sppf/antecedence_graph.rb +87 -0
  62. data/spec/rley/sppf/forest_representation.rb +136 -0
  63. data/spec/rley/sppf/gfg_representation.rb +111 -0
  64. data/spec/rley/sppf/non_terminal_node_spec.rb +64 -0
  65. data/spec/rley/support/ambiguous_grammar_helper.rb +36 -36
  66. data/spec/rley/support/expectation_helper.rb +36 -0
  67. data/spec/rley/support/grammar_helper.rb +28 -0
  68. data/spec/rley/support/grammar_sppf_helper.rb +25 -0
  69. data/spec/rley/syntax/grammar_builder_spec.rb +5 -0
  70. data/spec/rley/syntax/non_terminal_spec.rb +4 -0
  71. data/spec/rley/syntax/terminal_spec.rb +4 -0
  72. metadata +58 -2
@@ -0,0 +1,179 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ require_relative '../../../lib/rley/syntax/production'
4
+ require_relative '../../../lib/rley/parser/dotted_item'
5
+ require_relative '../../../lib/rley/gfg/item_vertex'
6
+ require_relative '../../../lib/rley/parser/parse_entry'
7
+
8
+ # Load the class under test
9
+ require_relative '../../../lib/rley/parser/parse_entry_set'
10
+
11
+ module Rley # Open this namespace to avoid module qualifier prefixes
12
+ module Parser # Open this namespace to avoid module qualifier prefixes
13
+ describe ParseEntrySet do
14
+ # Factory method. Builds a production with given left-hand side (LHS)
15
+ # and given RHS (right-hand side)
16
+ def build_prod(theLHS, *theRHSSymbols)
17
+ return Syntax::Production.new(theLHS, theRHSSymbols)
18
+ end
19
+
20
+ let(:t_a) { Rley::Syntax::Terminal.new('a') }
21
+ let(:t_b) { Rley::Syntax::Terminal.new('b') }
22
+ let(:t_c) { Rley::Syntax::Terminal.new('c') }
23
+ let(:nt_repeated_c) { Rley::Syntax::NonTerminal.new('Repetition') }
24
+ let(:repeated_prod) { build_prod(nt_repeated_c, t_c, nt_repeated_c) }
25
+ let(:nt_sentence) { Rley::Syntax::NonTerminal.new('Sentence') }
26
+ let(:sample_prod) { build_prod(nt_sentence, t_a, t_b, t_b, nt_repeated_c) }
27
+
28
+ let(:sample_item1) { Parser::DottedItem.new(sample_prod, 1) }
29
+ let(:sample_item2) { Parser::DottedItem.new(sample_prod, 2) }
30
+ let(:sample_item3) { Parser::DottedItem.new(sample_prod, 3) }
31
+
32
+ let(:vertex1) { GFG::ItemVertex.new(sample_item1) }
33
+ let(:entry1) { ParseEntry.new(vertex1, 2) }
34
+ let(:vertex2) { GFG::ItemVertex.new(sample_item2) }
35
+ let(:entry2) { ParseEntry.new(vertex2, 3) }
36
+ let(:vertex3) { GFG::ItemVertex.new(sample_item3) }
37
+ let(:entry3) { ParseEntry.new(vertex3, 4) }
38
+
39
+ context 'Initialization:' do
40
+ it 'should be created without argument' do
41
+ expect { ParseEntrySet.new }.not_to raise_error
42
+ end
43
+
44
+ it 'should be empty after creation' do
45
+ expect(subject.entries).to be_empty
46
+ end
47
+ end # context
48
+
49
+ context 'Provided services:' do
50
+ it 'should accept the addition of an entry' do
51
+ # Case: first time e,try addition
52
+ expect(subject.push_entry(entry1)).to eq(entry1)
53
+ expect(subject).not_to be_empty
54
+
55
+ # Case: duplicate entry
56
+ expect(subject.push_entry(entry1)).to eq(entry1)
57
+
58
+ # Yet another entry
59
+ expect(subject.push_entry(entry2)).to eq(entry2)
60
+ expect(subject.entries).to eq([entry1, entry2])
61
+ end
62
+
63
+ it 'should retrieve the entry at given position' do
64
+ subject.push_entry(entry1)
65
+ subject.push_entry(entry2)
66
+ expect(subject[0]).to eq(entry1)
67
+ expect(subject[1]).to eq(entry2)
68
+ end
69
+
70
+ it 'should list the entries expecting a given terminal' do
71
+ # Case: an entry expecting a terminal
72
+ subject.push_entry(entry1)
73
+ expect(subject.entries4term(t_b)).to eq([entry1])
74
+
75
+ # Case: a second entry expecting same terminal
76
+ subject.push_entry(entry2)
77
+ expect(subject.entries4term(t_b)).to eq([entry1, entry2])
78
+ end
79
+
80
+ it 'should list the expected terminals' do
81
+ subject.push_entry(entry1)
82
+ subject.push_entry(entry2)
83
+ subject.push_entry(entry3)
84
+
85
+ expect(subject.expected_terminals).to eq([t_b])
86
+ end
87
+
88
+ it 'should list the entries expecting a given non-terminal' do
89
+ # Case: an entry expecting a non-terminal
90
+ subject.push_entry(entry3)
91
+ expect(subject.entries4n_term(nt_repeated_c)).to eq([entry3])
92
+ end
93
+
94
+ =begin
95
+ it 'should list of ambiguous states' do
96
+ prod1 = double('fake-production1')
97
+ prod2 = double('fake-production2')
98
+ expect(subject.ambiguities.size).to eq(0)
99
+
100
+ # Adding states
101
+ subject.push_entry(entry1)
102
+ allow(vertex1).to receive(:production).and_return(prod1)
103
+ allow(vertex1).to receive(:"reduce_item?").and_return(true)
104
+ allow(vertex1).to receive(:lhs).and_return(:something)
105
+ expect(subject.ambiguities.size).to eq(0)
106
+ allow(vertex2).to receive(:production).and_return(prod2)
107
+ allow(vertex2).to receive(:"reduce_item?").and_return(true)
108
+ allow(vertex2).to receive(:lhs).and_return(:something_else)
109
+ subject.push_entry(entry2)
110
+ expect(subject.ambiguities.size).to eq(0)
111
+ # dotted_rule3 = double('fake_dotted_rule3')
112
+ # allow(dotted_rule3).to receive(:production).and_return(prod2)
113
+ # allow(dotted_rule3).to receive(:"reduce_item?").and_return(true)
114
+ # allow(dotted_rule3).to receive(:lhs).and_return(:something_else)
115
+ # entry3 = ParseEntry.new(dotted_rule3, 5)
116
+ subject.push_entry(entry3)
117
+ expect(subject.ambiguities[0]).to eq([entry2, entry3])
118
+ end
119
+ =end
120
+ =begin
121
+ it 'should list the states expecting a given terminal' do
122
+ # Case of no state
123
+ expect(subject.states_expecting(:a)).to be_empty
124
+
125
+ # Adding states
126
+ subject.push_entry(entry1)
127
+ subject.push_entry(entry2)
128
+ allow(vertex1).to receive(:next_symbol).and_return(:b)
129
+ allow(vertex2).to receive(:next_symbol).and_return(:a)
130
+ expect(subject.states_expecting(:a)).to eq([entry2])
131
+ expect(subject.states_expecting(:b)).to eq([entry1])
132
+ end
133
+
134
+ it 'should list the states related to a production' do
135
+ a_prod = double('fake-production')
136
+
137
+ # Case of no state
138
+ expect(subject.states_for(a_prod)).to be_empty
139
+
140
+ # Adding states
141
+ subject.push_entry(entry1)
142
+ subject.push_entry(entry2)
143
+ allow(vertex1).to receive(:production).and_return(:dummy)
144
+ allow(vertex2).to receive(:production).and_return(a_prod)
145
+ expect(subject.states_for(a_prod)).to eq([entry2])
146
+ end
147
+
148
+ it 'should list the states that rewrite a given non-terminal' do
149
+ non_term = double('fake-non-terminal')
150
+ prod1 = double('fake-production1')
151
+ prod2 = double('fake-production2')
152
+
153
+ # Adding states
154
+ subject.push_entry(entry1)
155
+ subject.push_entry(entry2)
156
+ allow(vertex1).to receive(:production).and_return(prod1)
157
+ allow(prod1).to receive(:lhs).and_return(:dummy)
158
+ allow(vertex2).to receive(:production).and_return(prod2)
159
+ allow(vertex2).to receive(:reduce_item?).and_return(true)
160
+ allow(prod2).to receive(:lhs).and_return(non_term)
161
+ expect(subject.states_rewriting(non_term)).to eq([entry2])
162
+ end
163
+
164
+
165
+
166
+ it 'should complain when impossible predecessor of parse state' do
167
+ subject.push_entry(entry1)
168
+ subject.push_entry(entry2)
169
+ allow(vertex1).to receive(:prev_position).and_return(nil)
170
+ err = StandardError
171
+ expect { subject.predecessor_state(entry1) }.to raise_error(err)
172
+ end
173
+ =end
174
+ end # context
175
+ end # describe
176
+ end # module
177
+ end # module
178
+
179
+ # End of file
@@ -0,0 +1,208 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ require_relative '../../../lib/rley/syntax/terminal'
4
+ require_relative '../../../lib/rley/syntax/non_terminal'
5
+ require_relative '../../../lib/rley/syntax/production'
6
+ require_relative '../../../lib/rley/parser/dotted_item'
7
+
8
+ # Load the class under test
9
+ require_relative '../../../lib/rley/parser/parse_entry'
10
+
11
+ module Rley # Open this namespace to avoid module qualifier prefixes
12
+ module Parser # Open this namespace to avoid module qualifier prefixes
13
+ describe ParseEntry do
14
+
15
+ let(:t_a) { Syntax::Terminal.new('A') }
16
+ let(:t_b) { Syntax::Terminal.new('B') }
17
+ let(:t_c) { Syntax::Terminal.new('C') }
18
+ let(:nt_sentence) { Syntax::NonTerminal.new('sentence') }
19
+
20
+ let(:sample_prod) do
21
+ Syntax::Production.new(nt_sentence, [t_a, t_b, t_c])
22
+ end
23
+
24
+ let(:empty_prod) do
25
+ Syntax::Production.new(nt_sentence, [])
26
+ end
27
+
28
+
29
+ let(:dotted_rule) { DottedItem.new(sample_prod, 2) }
30
+ let(:origin_val) { 3 }
31
+ let(:vertex_faked) { double('fake-vertex') }
32
+ let(:vertex2) { double('vertex-mock') }
33
+ # Default instantiation rule
34
+ subject { ParseEntry.new(vertex_faked, origin_val) }
35
+
36
+ context 'Initialization:' do
37
+ it 'should be created with a vertex and an origin position' do
38
+ args = [vertex_faked, origin_val]
39
+ expect { ParseEntry.new(*args) }.not_to raise_error
40
+ end
41
+
42
+ it 'should complain when the vertex is nil' do
43
+ err = StandardError
44
+ msg = 'GFG vertex cannot be nil'
45
+ expect { ParseEntry.new(nil, 2) }.to raise_error(err, msg)
46
+ end
47
+
48
+ it 'should know the vertex' do
49
+ expect(subject.vertex).to eq(vertex_faked)
50
+ end
51
+
52
+ it 'should know the origin value' do
53
+ expect(subject.origin).to eq(origin_val)
54
+ end
55
+
56
+ it 'should have not antecedent at creation' do
57
+ expect(subject.antecedents).to be_empty
58
+ expect(subject).to be_orphan
59
+ end
60
+ end # context
61
+
62
+ context 'Provided services:' do
63
+ it 'should compare with itself' do
64
+ synonym = subject # Fool Rubocop
65
+ expect(subject == synonym).to eq(true)
66
+ end
67
+
68
+ it 'should compare with another' do
69
+ equal = ParseEntry.new(vertex_faked, origin_val)
70
+ expect(subject == equal).to eq(true)
71
+
72
+ # Same vertex, different origin
73
+ diff_origin = ParseEntry.new(vertex_faked, 2)
74
+ expect(subject == diff_origin).to eq(false)
75
+
76
+ # Different vertices, same origin
77
+ diff_vertex = ParseEntry.new(double('other_vertex_faked'), 3)
78
+ expect(subject == diff_vertex).to eq(false)
79
+ end
80
+
81
+ it 'should know if the vertex is a start vertex' do
82
+ expect(subject).not_to be_start_entry
83
+
84
+ instance = ParseEntry.new(GFG::StartVertex.new('.NT'), 3)
85
+ expect(instance).to be_start_entry
86
+ end
87
+
88
+ it 'should know if the vertex is an end vertex' do
89
+ expect(subject).not_to be_end_entry
90
+
91
+ instance = ParseEntry.new(GFG::EndVertex.new('NT.'), 3)
92
+ expect(instance).to be_end_entry
93
+ end
94
+
95
+ it 'should know if the entry is a dotted item vertex' do
96
+ expect(subject).not_to be_dotted_entry
97
+
98
+ instance = ParseEntry.new(GFG::ItemVertex.new('P => S.'), 3)
99
+ expect(instance).to be_dotted_entry
100
+ end
101
+
102
+ it 'should know if the vertex is at end of production (if any)' do
103
+ # Case: start vertex
104
+ instance1 = ParseEntry.new(GFG::StartVertex.new('.NT'), 3)
105
+ expect(instance1).not_to be_exit_entry
106
+
107
+ # Case: end vertex
108
+ instance2 = ParseEntry.new(GFG::EndVertex.new('NT.'), 3)
109
+ expect(instance2).not_to be_exit_entry
110
+
111
+ # Case: item vertex not at end of rhs
112
+ v1 = double('vertex-not-at-end')
113
+ allow(v1).to receive(:complete?).and_return(false)
114
+ instance3 = ParseEntry.new(v1, 3)
115
+ expect(instance3).not_to be_exit_entry
116
+
117
+ # Case: item vertex at end of rhs
118
+ v2 = double('vertex-at-end')
119
+ allow(v2).to receive(:complete?).and_return(true)
120
+ instance4 = ParseEntry.new(v2, 3)
121
+ expect(instance4).to be_exit_entry
122
+ end
123
+
124
+ it 'should know if the vertex is at begin of production (if any)' do
125
+ # Case: start vertex
126
+ instance1 = ParseEntry.new(GFG::StartVertex.new('.NT'), 3)
127
+ expect(instance1).not_to be_entry_entry
128
+
129
+ # Case: end vertex
130
+ instance2 = ParseEntry.new(GFG::EndVertex.new('NT.'), 3)
131
+ expect(instance2).not_to be_entry_entry
132
+
133
+ # Case: item vertex not at begin of rhs
134
+ d1 = DottedItem.new(sample_prod, 1)
135
+ v1 = GFG::ItemVertex.new(d1)
136
+ instance3 = ParseEntry.new(v1, 3)
137
+ expect(instance3).not_to be_entry_entry
138
+
139
+ # Case: item vertex at end of rhs
140
+ d2 = DottedItem.new(sample_prod, 0)
141
+ v2 = GFG::ItemVertex.new(d2)
142
+ instance4 = ParseEntry.new(v2, 3)
143
+ expect(instance4).to be_entry_entry
144
+ end
145
+
146
+ it 'should know the symbol before the dot (if any)' do
147
+ # Case: start vertex
148
+ instance1 = ParseEntry.new(GFG::StartVertex.new('.NT'), 3)
149
+ expect(instance1.prev_symbol).to be_nil
150
+
151
+ # Case: end vertex
152
+ instance2 = ParseEntry.new(GFG::EndVertex.new('NT.'), 3)
153
+ expect(instance2.prev_symbol).to be_nil # Really correct?
154
+
155
+ # Case: item vertex not at start of rhs
156
+ v1 = double('vertex-not-at-start')
157
+ allow(v1).to receive(:prev_symbol).and_return('symbol')
158
+ instance3 = ParseEntry.new(v1, 3)
159
+ expect(instance3.prev_symbol).to eq('symbol')
160
+
161
+ # Case: item vertex at start of rhs
162
+ v2 = double('vertex-at-start')
163
+ allow(v2).to receive(:prev_symbol).and_return(nil)
164
+ instance4 = ParseEntry.new(v2, 0)
165
+ expect(instance4.prev_symbol).to be_nil
166
+ end
167
+
168
+ it 'should know the next expected symbol (if any)' do
169
+ # Case: start vertex
170
+ instance1 = ParseEntry.new(GFG::StartVertex.new('.NT'), 3)
171
+ expect(instance1.next_symbol).to be_nil
172
+
173
+ # Case: end vertex
174
+ instance2 = ParseEntry.new(GFG::EndVertex.new('NT.'), 3)
175
+ expect(instance2.next_symbol).to be_nil
176
+
177
+ # Case: item vertex not at end of rhs
178
+ v1 = double('vertex-not-at-end')
179
+ allow(v1).to receive(:next_symbol).and_return('symbol')
180
+ instance3 = ParseEntry.new(v1, 3)
181
+ expect(instance3.next_symbol).to eq('symbol')
182
+
183
+ # Case: item vertex at end of rhs
184
+ v2 = double('vertex-at-end')
185
+ allow(v2).to receive(:next_symbol).and_return(nil)
186
+ instance4 = ParseEntry.new(v2, 3)
187
+ expect(instance4.next_symbol).to be_nil
188
+ end
189
+
190
+ it 'should accept antecedents' do
191
+ antecedent = ParseEntry.new(vertex2, origin_val)
192
+ subject.add_antecedent(antecedent)
193
+ expect(subject.antecedents).to eql([antecedent])
194
+ expect(subject).not_to be_orphan
195
+ end
196
+ =begin
197
+
198
+ it 'should know its text representation' do
199
+ expected = 'sentence => A B . C | 3'
200
+ expect(subject.to_s).to eq(expected)
201
+ end
202
+ =end
203
+ end # context
204
+ end # describe
205
+ end # module
206
+ end # module
207
+
208
+ # End of file
@@ -0,0 +1,382 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ require_relative '../../../lib/rley/parser/gfg_earley_parser'
4
+
5
+ require_relative '../../../lib/rley/syntax/grammar_builder'
6
+ require_relative '../support/grammar_helper'
7
+ require_relative '../support/expectation_helper'
8
+
9
+ require_relative '../../../lib/rley/parser/parse_walker_factory'
10
+
11
+ # Load the class under test
12
+ require_relative '../../../lib/rley/parser/parse_forest_builder'
13
+
14
+ module Rley # Open this namespace to avoid module qualifier prefixes
15
+ module Parser
16
+ describe ParseForestBuilder do
17
+ include GrammarHelper # Mix-in with token factory method
18
+ include ExpectationHelper # Mix-in with expectation on parse entry sets
19
+
20
+ let(:sample_grammar) do
21
+ # Grammar based on paper from Elisabeth Scott
22
+ # "SPPF=Style Parsing From Earley Recognizers" in
23
+ # Notes in Theoretical Computer Science 203, (2008), pp. 53-67
24
+ # contains a hidden left recursion and a cycle
25
+ builder = Syntax::GrammarBuilder.new
26
+ builder.add_terminals('a', 'b')
27
+ builder.add_production('Phi' => %'S')
28
+ builder.add_production('S' => %w[A T])
29
+ builder.add_production('S' => %w[a T])
30
+ builder.add_production('A' => 'a')
31
+ builder.add_production('A' => %w[B A])
32
+ builder.add_production('B' => [])
33
+ builder.add_production('T' => %w( b b b))
34
+ builder.grammar
35
+ end
36
+
37
+ let(:sample_tokens) do
38
+ build_token_sequence(%w(a b b b), sample_grammar)
39
+ end
40
+
41
+ let(:sample_result) do
42
+ parser = Parser::GFGEarleyParser.new(sample_grammar)
43
+ parser.parse(sample_tokens)
44
+ end
45
+
46
+ let(:walker) do
47
+ factory = ParseWalkerFactory.new
48
+ factory.build_walker(sample_result)
49
+ end
50
+
51
+ subject do
52
+ ParseForestBuilder.new(sample_result)
53
+ end
54
+
55
+ # Emit a text representation of the current path.
56
+ def path_to_s()
57
+ text_parts = subject.curr_path.map { |path_element| path_element.to_string(0) }
58
+ return text_parts.join('/')
59
+ end
60
+
61
+
62
+ context 'Initialization:' do
63
+ it 'should be created with a GFGParsing' do
64
+ expect { ParseForestBuilder.new(sample_result) }.not_to raise_error
65
+ end
66
+
67
+ it 'should know the parse result' do
68
+ expect(subject.parsing).to eq(sample_result)
69
+ end
70
+
71
+ it 'should have an empty path' do
72
+ expect(subject.curr_path).to be_empty
73
+ end
74
+ end
75
+
76
+ context 'Parse forest construction' do
77
+
78
+ it 'should initialize the root node' do
79
+ first_event = walker.next
80
+ subject.receive_event(*first_event)
81
+ forest = subject.forest
82
+
83
+ expect(forest.root.to_string(0)).to eq('Phi[0, 4]')
84
+ expect(subject.curr_path).to eq([forest.root])
85
+ expect(subject.entry2node[first_event[1]]).to eq(forest.root)
86
+ end
87
+
88
+ it 'should initialize the first child of the root node' do
89
+ event1 = walker.next
90
+ subject.receive_event(*event1)
91
+
92
+ event2 = walker.next
93
+ subject.receive_event(*event2)
94
+
95
+ expect(subject.curr_parent.to_string(0)).to eq('S[?, 4]')
96
+ expected_path2 = 'Phi[0, 4]/S[?, 4]'
97
+ expect(path_to_s).to eq(expected_path2)
98
+ end
99
+
100
+ it 'should build alternative node when detecting backtrack point' do
101
+ 2.times do
102
+ event = walker.next
103
+ subject.receive_event(*event)
104
+ end
105
+
106
+ event3 = walker.next
107
+ subject.receive_event(*event3)
108
+
109
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(S => a T .)[0, 4]')
110
+ expected_path3 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]'
111
+ expect(path_to_s).to eq(expected_path3)
112
+ expect(subject.curr_path[-2].refinement).to eq(:or)
113
+ end
114
+
115
+ it 'should build token node when scan edge was detected' do
116
+ 3.times do
117
+ event = walker.next
118
+ subject.receive_event(*event)
119
+ end
120
+
121
+ event4 = walker.next
122
+ subject.receive_event(*event4)
123
+ expect(event4[1].to_s).to eq('S => a T . | 0')
124
+ expect(subject.curr_parent.to_string(0)).to eq('T[?, 4]')
125
+ expected_path4 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]/T[?, 4]'
126
+ expect(path_to_s).to eq(expected_path4)
127
+ expect(subject.curr_parent.subnodes).to be_empty
128
+
129
+ event5 = walker.next
130
+ subject.receive_event(*event5)
131
+ expect(event5[1].to_s).to eq('T. | 1')
132
+ expect(subject.curr_parent.to_string(0)).to eq('T[1, 4]')
133
+ expected_path5 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]/T[1, 4]'
134
+ expect(path_to_s).to eq(expected_path5)
135
+ expect(subject.curr_parent.subnodes).to be_empty
136
+
137
+ event6 = walker.next
138
+ subject.receive_event(*event6)
139
+ expect(event6[1].to_s).to eq('T => b b b . | 1')
140
+ expect(subject.curr_parent.to_string(0)).to eq('T[1, 4]')
141
+ expected_path6 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]/T[1, 4]'
142
+ expect(path_to_s).to eq(expected_path6)
143
+ expect(subject.curr_parent.subnodes.size).to eq(1)
144
+ token_event6 = 'b[3, 4]'
145
+ expect(subject.curr_parent.subnodes.first.to_string(0)).to eq(token_event6)
146
+
147
+ event7 = walker.next
148
+ subject.receive_event(*event7)
149
+ expect(event7[1].to_s).to eq('T => b b . b | 1')
150
+ expect(subject.curr_parent.to_string(0)).to eq('T[1, 4]')
151
+ expected_path7 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]/T[1, 4]'
152
+ expect(path_to_s).to eq(expected_path7)
153
+ expect(subject.curr_parent.subnodes.size).to eq(2)
154
+ token_event7 = 'b[2, 3]'
155
+ expect(subject.curr_parent.subnodes.first.to_string(0)).to eq(token_event7)
156
+
157
+ event8 = walker.next
158
+ subject.receive_event(*event8)
159
+ expect(event8[1].to_s).to eq('T => b . b b | 1')
160
+ expect(subject.curr_parent.to_string(0)).to eq('T[1, 4]')
161
+ expected_path8 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]/T[1, 4]'
162
+ expect(path_to_s).to eq(expected_path8)
163
+ expect(subject.curr_parent.subnodes.size).to eq(3)
164
+ token_event8 = 'b[1, 2]'
165
+ expect(subject.curr_parent.subnodes.first.to_string(0)).to eq(token_event8)
166
+
167
+ event9 = walker.next
168
+ subject.receive_event(*event9)
169
+ expect(event9[1].to_s).to eq('T => . b b b | 1')
170
+ expect(subject.curr_parent.to_string(0)).to eq('T[1, 4]')
171
+ expected_path9 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]/T[1, 4]'
172
+ expect(path_to_s).to eq(expected_path9)
173
+
174
+ event10 = walker.next
175
+ subject.receive_event(*event10)
176
+ expect(event10[1].to_s).to eq('.T | 1')
177
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(S => a T .)[0, 4]')
178
+ expected_path10 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]'
179
+ expect(path_to_s).to eq(expected_path10)
180
+
181
+ event11 = walker.next
182
+ subject.receive_event(*event11)
183
+ expect(event11[1].to_s).to eq('S => a . T | 0')
184
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(S => a T .)[0, 4]')
185
+ expected_path11 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]'
186
+ expect(path_to_s).to eq(expected_path11)
187
+ expect(subject.curr_parent.subnodes.size).to eq(2)
188
+ token_event11 = 'a[0, 1]'
189
+ expect(subject.curr_parent.subnodes.first.to_string(0)).to eq(token_event11)
190
+
191
+ event12 = walker.next
192
+ subject.receive_event(*event12)
193
+ expect(event12[1].to_s).to eq('S => . a T | 0')
194
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(S => a T .)[0, 4]')
195
+ expected_path12 = 'Phi[0, 4]/S[0, 4]/Alt(S => a T .)[0, 4]'
196
+ expect(path_to_s).to eq(expected_path12)
197
+ expect(subject.curr_parent.subnodes.size).to eq(2) # Is this OK?
198
+
199
+ # Pop all Alternative nodes until no Alternative found, pop this last too
200
+ event13 = walker.next
201
+ subject.receive_event(*event13)
202
+ expect(event13[1].to_s).to eq('.S | 0')
203
+ expect(subject.curr_parent.to_string(0)).to eq('Phi[0, 4]')
204
+ expected_path13 = 'Phi[0, 4]'
205
+ expect(path_to_s).to eq(expected_path13)
206
+
207
+ event14 = walker.next
208
+ subject.receive_event(*event14)
209
+ expect(event14[1].to_s).to eq('Phi => . S | 0')
210
+ expect(subject.curr_parent.to_string(0)).to eq('Phi[0, 4]')
211
+ expected_path14 = 'Phi[0, 4]'
212
+ expect(path_to_s).to eq(expected_path14)
213
+
214
+ event15 = walker.next
215
+ subject.receive_event(*event15)
216
+ expect(event15[1].to_s).to eq('.Phi | 0')
217
+ expect(path_to_s).to be_empty
218
+ end
219
+
220
+ it 'should handle backtracking' do
221
+ 15.times do
222
+ event = walker.next
223
+ subject.receive_event(*event)
224
+ end
225
+
226
+ event16 = walker.next
227
+ subject.receive_event(*event16)
228
+ expect(event16[0]).to eq(:backtrack) # Backtrack event!
229
+ expect(event16[1].to_s).to eq('S. | 0')
230
+ # A new alternative node must be created
231
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(S => A T .)[0, 4]')
232
+ expected_path16 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]'
233
+ expect(path_to_s).to eq(expected_path16)
234
+
235
+
236
+ event17 = walker.next
237
+ subject.receive_event(*event17)
238
+ expect(event17[1].to_s).to eq('S => A T . | 0')
239
+ expect(subject.curr_parent.to_string(0)).to eq('T[?, 4]')
240
+ expected_path17 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/T[?, 4]'
241
+ expect(path_to_s).to eq(expected_path17)
242
+ end
243
+
244
+ it 'should detect second time visit of an entry' do
245
+ 17.times do
246
+ event = walker.next
247
+ subject.receive_event(*event)
248
+ end
249
+
250
+ event18 = walker.next
251
+ subject.receive_event(*event18)
252
+ expect(event18[0]).to eq(:revisit) # Revisit event!
253
+ expect(event18[1].to_s).to eq('T. | 1')
254
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(S => A T .)[0, 4]')
255
+ expected_path18 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]'
256
+ expect(path_to_s).to eq(expected_path18)
257
+
258
+ event19 = walker.next
259
+ subject.receive_event(*event19)
260
+ expect(event19[1].to_s).to eq('S => A . T | 0')
261
+ expect(subject.curr_parent.to_string(0)).to eq('A[?, 1]')
262
+ expected_path19 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[?, 1]'
263
+ expect(path_to_s).to eq(expected_path19)
264
+
265
+ event20 = walker.next
266
+ subject.receive_event(*event20)
267
+ # Next entry is an end entry...
268
+ expect(event20[1].to_s).to eq('A. | 0')
269
+ # ... with multiple antecedents => alternative nodes required
270
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(A => a .)[0, 1]')
271
+ expected_path20 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[0, 1]/Alt(A => a .)[0, 1]'
272
+ expect(path_to_s).to eq(expected_path20)
273
+ expect(subject.curr_path[-2].refinement).to eq(:or)
274
+
275
+ event21 = walker.next
276
+ subject.receive_event(*event21)
277
+ expect(event21[1].to_s).to eq('A => a . | 0')
278
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(A => a .)[0, 1]')
279
+ expected_path21 = expected_path20
280
+ expect(path_to_s).to eq(expected_path21)
281
+
282
+ event22 = walker.next
283
+ subject.receive_event(*event22)
284
+ expect(event22[1].to_s).to eq('A => . a | 0')
285
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(A => a .)[0, 1]')
286
+ expected_path22 = expected_path20
287
+ expect(path_to_s).to eq(expected_path22)
288
+
289
+ event23 = walker.next
290
+ subject.receive_event(*event23)
291
+ # Next entry is an start entry...
292
+ expect(event23[1].to_s).to eq('.A | 0')
293
+ # ... with multiple antecedents => alternative nodes required
294
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(S => A T .)[0, 4]')
295
+ expected_path23 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]'
296
+ expect(path_to_s).to eq(expected_path23)
297
+
298
+ event24 = walker.next
299
+ subject.receive_event(*event24)
300
+ expect(event24[1].to_s).to eq('S => . A T | 0')
301
+ # ... with multiple antecedents => alternative nodes required
302
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(S => A T .)[0, 4]')
303
+ expected_path24 = expected_path23
304
+ expect(path_to_s).to eq(expected_path24)
305
+ end
306
+
307
+ it 'should handle remaining events' do
308
+ 24.times do
309
+ event = walker.next
310
+ subject.receive_event(*event)
311
+ end
312
+
313
+ event25 = walker.next
314
+ subject.receive_event(*event25)
315
+ expect(event25[0]).to eq(:backtrack) # Backtrack event!
316
+ expect(event25[1].to_s).to eq('A. | 0')
317
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(A => B A .)[0, 1]')
318
+ expected_path25 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[0, 1]/Alt(A => B A .)[0, 1]'
319
+ expect(path_to_s).to eq(expected_path25)
320
+
321
+ event26 = walker.next
322
+ subject.receive_event(*event26)
323
+ expect(event26[1].to_s).to eq('A => B A . | 0')
324
+ expect(subject.curr_parent.to_string(0)).to eq('A[?, 1]')
325
+ expected_path26 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[0, 1]/Alt(A => B A .)[0, 1]/A[?, 1]'
326
+ expect(path_to_s).to eq(expected_path26)
327
+
328
+ event27 = walker.next
329
+ subject.receive_event(*event27)
330
+ expect(event27[0]).to eq(:revisit) # Revisit event!
331
+ expect(event27[1].to_s).to eq('A. | 0')
332
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(A => B A .)[0, 1]')
333
+ expected_path27 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[0, 1]/Alt(A => B A .)[0, 1]'
334
+ expect(path_to_s).to eq(expected_path27)
335
+
336
+ event28 = walker.next
337
+ subject.receive_event(*event28)
338
+ expect(event28[0]).to eq(:visit)
339
+ expect(event28[1].to_s).to eq('A => B . A | 0')
340
+ expect(subject.curr_parent.to_string(0)).to eq('B[?, 0]')
341
+ expected_path28 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[0, 1]/Alt(A => B A .)[0, 1]/B[?, 0]'
342
+ expect(path_to_s).to eq(expected_path28)
343
+
344
+ event29 = walker.next
345
+ subject.receive_event(*event29)
346
+ expect(event29[0]).to eq(:visit)
347
+ expect(event29[1].to_s).to eq('B. | 0')
348
+ expect(subject.curr_parent.to_string(0)).to eq('B[0, 0]')
349
+ expected_path29 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[0, 1]/Alt(A => B A .)[0, 1]/B[0, 0]'
350
+ expect(path_to_s).to eq(expected_path29)
351
+
352
+ event30 = walker.next
353
+ subject.receive_event(*event30)
354
+ expect(event30[0]).to eq(:visit)
355
+ # Empty production!
356
+ expect(event30[1].to_s).to eq('B => . | 0')
357
+ expect(subject.curr_parent.to_string(0)).to eq('B[0, 0]')
358
+ expected_path30 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[0, 1]/Alt(A => B A .)[0, 1]/B[0, 0]'
359
+ expect(path_to_s).to eq(expected_path30)
360
+
361
+ event31 = walker.next
362
+ subject.receive_event(*event31)
363
+ expect(event31[0]).to eq(:visit)
364
+ expect(event31[1].to_s).to eq('.B | 0')
365
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(A => B A .)[0, 1]')
366
+ expected_path31 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[0, 1]/Alt(A => B A .)[0, 1]'
367
+ expect(path_to_s).to eq(expected_path31)
368
+
369
+ event32 = walker.next
370
+ subject.receive_event(*event32)
371
+ expect(event32[0]).to eq(:visit)
372
+ expect(event32[1].to_s).to eq('A => . B A | 0')
373
+ expect(subject.curr_parent.to_string(0)).to eq('Alt(A => B A .)[0, 1]')
374
+ expected_path32 = 'Phi[0, 4]/S[0, 4]/Alt(S => A T .)[0, 4]/A[0, 1]/Alt(A => B A .)[0, 1]'
375
+ expect(path_to_s).to eq(expected_path32)
376
+ end
377
+ end # context
378
+
379
+ end # describe
380
+ end # module
381
+ end # module
382
+ # End of file