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,81 @@
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
+ # Load the class under test
10
+ require_relative '../../../lib/rley/parser/parse_forest_factory'
11
+
12
+ module Rley # Open this namespace to avoid module qualifier prefixes
13
+ module Parser
14
+ describe ParseForestFactory do
15
+ include GrammarHelper # Mix-in with token factory method
16
+ include ExpectationHelper # Mix-in with expectation on parse entry sets
17
+
18
+ let(:sample_grammar) do
19
+ # Grammar based on paper from Elisabeth Scott
20
+ # "SPPF=Style Parsing From Earley Recognizers" in
21
+ # Notes in Theoretical Computer Science 203, (2008), pp. 53-67
22
+ # contains a hidden left recursion and a cycle
23
+ builder = Syntax::GrammarBuilder.new
24
+ builder.add_terminals('a', 'b')
25
+ builder.add_production('Phi' => %'S')
26
+ builder.add_production('S' => %w[A T])
27
+ builder.add_production('S' => %w[a T])
28
+ builder.add_production('A' => 'a')
29
+ builder.add_production('A' => %w[B A])
30
+ builder.add_production('B' => [])
31
+ builder.add_production('T' => %w( b b b))
32
+ builder.grammar
33
+ end
34
+
35
+ let(:sample_tokens) do
36
+ build_token_sequence(%w(a b b b), sample_grammar)
37
+ end
38
+
39
+ let(:sample_result) do
40
+ parser = Parser::GFGEarleyParser.new(sample_grammar)
41
+ parser.parse(sample_tokens)
42
+ end
43
+
44
+
45
+ subject do
46
+ ParseForestFactory.new(sample_result)
47
+ end
48
+
49
+ # Emit a text representation of the current path.
50
+ def path_to_s()
51
+ text_parts = subject.curr_path.map { |path_element| path_element.to_string(0) }
52
+ return text_parts.join('/')
53
+ end
54
+
55
+
56
+ context 'Initialization:' do
57
+ it 'should be created with a GFGParsing' do
58
+ expect { ParseForestFactory.new(sample_result) }.not_to raise_error
59
+ end
60
+
61
+ it 'should know the parse result' do
62
+ expect(subject.parsing).to eq(sample_result)
63
+ end
64
+ end
65
+ =begin
66
+ context 'Parse forest construction' do
67
+ it 'should build a parse forest' do
68
+ forest = subject.build_parse_forest
69
+
70
+ require 'yaml'
71
+ require_relative '../sppf/forest_representation'
72
+ File.open("forest.yml", "w") { |f| YAML.dump(forest, f) }
73
+ pen = ForestRepresentation.new
74
+ pen.generate_graph(forest, File.open("forest.dot", "w"))
75
+ end
76
+ end # context
77
+ =end
78
+ end # describe
79
+ end # module
80
+ end # module
81
+ # End of file
@@ -0,0 +1,235 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ require_relative '../../../lib/rley/syntax/grammar_builder'
4
+ require_relative '../support/grammar_helper'
5
+ require_relative '../support/expectation_helper'
6
+
7
+ require_relative '../../../lib/rley/parser/gfg_earley_parser'
8
+
9
+ # Load the class under test
10
+ require_relative '../../../lib/rley/parser/parse_walker_factory'
11
+
12
+
13
+ module Rley # Open this namespace to avoid module qualifier prefixes
14
+ module Parser
15
+ describe ParseWalkerFactory do
16
+ include GrammarHelper # Mix-in with token factory method
17
+ include ExpectationHelper # Mix-in with expectation on parse entry sets
18
+
19
+ # Helper method use to check wether a visit event
20
+ # matches the given expectations
21
+ # The expectations form an array with the given elements:
22
+ # [0] => the event symbol (:visit)
23
+ # [1] => the parse entry being visited
24
+ # [2] => the index of the parse entry set to which the visitee belongs to
25
+ def event_expectations(anEvent, expectations)
26
+ visit_event, entry, index = anEvent
27
+ expect(visit_event).to eq(expectations[0])
28
+ if expectations[1].is_a?(String)
29
+ case entry
30
+ when Parser::ParseEntry
31
+ expect(entry.to_s).to eq(expectations[1])
32
+ when Parser::Token
33
+ expect(entry.lexeme).to eq(expectations[1])
34
+ end
35
+ else
36
+ expect(entry).to eq(expectations[1])
37
+ end
38
+ expect(index).to eq(expectations[2])
39
+ end
40
+
41
+ let(:sample_grammar) do
42
+ # Grammar based on paper from Elisabeth Scott
43
+ # "SPPF=Style Parsing From Earley Recognizers" in
44
+ # Notes in Theoretical Computer Science 203, (2008), pp. 53-67
45
+ # contains a hidden left recursion and a cycle
46
+ builder = Syntax::GrammarBuilder.new
47
+ builder.add_terminals('a', 'b')
48
+ builder.add_production('Phi' => %'S')
49
+ builder.add_production('S' => %w[A T])
50
+ builder.add_production('S' => %w[a T])
51
+ builder.add_production('A' => 'a')
52
+ builder.add_production('A' => %w[B A])
53
+ builder.add_production('B' => [])
54
+ builder.add_production('T' => %w( b b b))
55
+ builder.grammar
56
+ end
57
+
58
+ let(:sample_tokens) do
59
+ build_token_sequence(%w(a b b b), sample_grammar)
60
+ end
61
+
62
+ let(:sample_result) do
63
+ parser = Parser::GFGEarleyParser.new(sample_grammar)
64
+ parser.parse(sample_tokens)
65
+ end
66
+
67
+ let(:subject) { ParseWalkerFactory.new }
68
+
69
+
70
+ context 'Initialization:' do
71
+ it 'should be created without argument' do
72
+ expect { ParseWalkerFactory.new }.not_to raise_error
73
+ end
74
+ end # context
75
+
76
+ context 'Parse graph traversal:' do
77
+ it 'should create an Enumerator as a walker' do
78
+ expect(subject.build_walker(sample_result)).to be_kind_of(Enumerator)
79
+ end
80
+
81
+ it 'should return the accepting parse entry in the first place' do
82
+ walker = subject.build_walker(sample_result)
83
+ first_event = walker.next
84
+ expectations = [:visit, sample_result.accepting_entry, 4]
85
+ event_expectations(first_event, expectations)
86
+ end
87
+
88
+ it 'should traverse the parse graph backwards' do
89
+ walker = subject.build_walker(sample_result)
90
+ event1 = walker.next
91
+ expectations = [:visit, 'Phi. | 0', 4]
92
+ event_expectations(event1, expectations)
93
+
94
+ event2 = walker.next
95
+ expectations = [:visit, 'Phi => S . | 0', 4]
96
+ event_expectations(event2, expectations)
97
+
98
+ event3 = walker.next
99
+ expectations = [:visit, 'S. | 0', 4]
100
+ event_expectations(event3, expectations)
101
+
102
+ # Backtrack created: first alternative selected
103
+ event4 = walker.next
104
+ expectations = [:visit, 'S => a T . | 0', 4]
105
+ event_expectations(event4, expectations)
106
+
107
+ event5 = walker.next
108
+ expectations = [:visit, 'T. | 1', 4]
109
+ event_expectations(event5, expectations)
110
+
111
+ event6 = walker.next
112
+ expectations = [:visit, 'T => b b b . | 1', 4]
113
+ event_expectations(event6, expectations)
114
+
115
+ event7 = walker.next
116
+ expectations = [:visit, 'T => b b . b | 1', 3]
117
+ event_expectations(event7, expectations)
118
+
119
+ event8 = walker.next
120
+ expectations = [:visit, 'T => b . b b | 1', 2]
121
+ event_expectations(event8, expectations)
122
+
123
+ event9 = walker.next
124
+ expectations = [:visit, 'T => . b b b | 1', 1]
125
+ event_expectations(event9, expectations)
126
+
127
+ event10 = walker.next
128
+ expectations = [:visit, '.T | 1', 1]
129
+ event_expectations(event10, expectations)
130
+
131
+ event11 = walker.next
132
+ expectations = [:visit, 'S => a . T | 0', 1]
133
+ event_expectations(event11, expectations)
134
+
135
+ event12 = walker.next
136
+ expectations = [:visit, 'S => . a T | 0', 0]
137
+ event_expectations(event12, expectations)
138
+
139
+ event13 = walker.next
140
+ expectations = [:visit, '.S | 0', 0]
141
+ event_expectations(event13, expectations)
142
+
143
+ event14 = walker.next
144
+ expectations = [:visit, 'Phi => . S | 0', 0]
145
+ event_expectations(event14, expectations)
146
+
147
+ event15 = walker.next
148
+ expectations = [:visit, '.Phi | 0', 0]
149
+ event_expectations(event15, expectations)
150
+
151
+ # Backtracking is occurring
152
+ event16 = walker.next
153
+ expectations = [:backtrack, 'S. | 0', 4]
154
+ event_expectations(event16, expectations)
155
+
156
+ event18 = walker.next
157
+ expectations = [:visit, 'S => A T . | 0', 4]
158
+ event_expectations(event18, expectations)
159
+
160
+ event19 = walker.next
161
+ expectations = [:revisit, 'T. | 1', 4]
162
+ event_expectations(event19, expectations)
163
+
164
+ # Multiple visit occurred: jump to antecedent of start entry
165
+ event20 = walker.next
166
+ expectations = [:visit, 'S => A . T | 0', 1]
167
+ event_expectations(event20, expectations)
168
+
169
+ event21 = walker.next
170
+ expectations = [:visit, 'A. | 0', 1]
171
+ event_expectations(event21, expectations)
172
+
173
+ # Backtrack created: first alternative selected
174
+ event22 = walker.next
175
+ expectations = [:visit, 'A => a . | 0', 1]
176
+ event_expectations(event22, expectations)
177
+
178
+ event23 = walker.next
179
+ expectations = [:visit, 'A => . a | 0', 0]
180
+ event_expectations(event23, expectations)
181
+
182
+ event24 = walker.next
183
+ expectations = [:visit, '.A | 0', 0]
184
+ event_expectations(event24, expectations)
185
+
186
+ event25 = walker.next
187
+ expectations = [:visit, 'S => . A T | 0', 0]
188
+ event_expectations(event25, expectations)
189
+
190
+ # Backtracking is occurring
191
+ event26 = walker.next
192
+ expectations = [:backtrack, 'A. | 0', 1]
193
+ event_expectations(event26, expectations)
194
+
195
+ event27 = walker.next
196
+ expectations = [:visit, 'A => B A . | 0', 1]
197
+ event_expectations(event27, expectations)
198
+
199
+ event28 = walker.next
200
+ expectations = [:revisit, 'A. | 0', 1]
201
+ event_expectations(event28, expectations)
202
+
203
+ event29 = walker.next
204
+ expectations = [:visit, 'A => B . A | 0', 0]
205
+ event_expectations(event29, expectations)
206
+
207
+ event30 = walker.next
208
+ expectations = [:visit, 'B. | 0', 0]
209
+ event_expectations(event30, expectations)
210
+
211
+ event31 = walker.next
212
+ expectations = [:visit, 'B => . | 0', 0]
213
+ event_expectations(event31, expectations)
214
+
215
+ event32 = walker.next
216
+ expectations = [:visit, '.B | 0', 0]
217
+ event_expectations(event32, expectations)
218
+
219
+ event33 = walker.next
220
+ expectations = [:visit, 'A => . B A | 0', 0]
221
+ event_expectations(event33, expectations)
222
+ end
223
+
224
+ it 'should raise an exception at end of visit' do
225
+ walker = subject.build_walker(sample_result)
226
+ 32.times { walker.next }
227
+
228
+ expect{ walker.next }.to raise_error(StopIteration)
229
+ end
230
+
231
+ end # context
232
+ end # describe
233
+ end # module
234
+ end # module
235
+ # End of file
@@ -17,6 +17,10 @@ module Rley # Open this namespace to avoid module qualifier prefixes
17
17
  it 'should be created without argument' do
18
18
  expect { StateSet.new }.not_to raise_error
19
19
  end
20
+
21
+ it 'should be empty at creation' do
22
+ expect(subject.states).to be_empty
23
+ end
20
24
  end # context
21
25
 
22
26
  context 'Provided services:' do
@@ -0,0 +1,72 @@
1
+ require 'ostruct'
2
+ require_relative '../../spec_helper'
3
+
4
+ require_relative '../../../lib/rley/gfg/item_vertex'
5
+ require_relative '../../../lib/rley/syntax/terminal'
6
+ require_relative '../../../lib/rley/syntax/production'
7
+ require_relative '../../../lib/rley/ptree/token_range'
8
+ require_relative '../../../lib/rley/parser/dotted_item'
9
+
10
+ # Load the class under test
11
+ require_relative '../../../lib/rley/sppf/alternative_node'
12
+
13
+ module Rley # Open this namespace to avoid module qualifier prefixes
14
+ module SPPF # Open this namespace to avoid module qualifier prefixes
15
+ describe AlternativeNode do
16
+ # Factory method. Generate a range from its boundary values.
17
+ def range(low, high)
18
+ return PTree::TokenRange.new(low: low, high: high)
19
+ end
20
+
21
+ let(:t_a) { Syntax::Terminal.new('A') }
22
+ let(:t_b) { Syntax::Terminal.new('B') }
23
+ let(:t_c) { Syntax::Terminal.new('C') }
24
+ let(:nt_sentence) { Syntax::NonTerminal.new('sentence') }
25
+ let(:sample_prod) do
26
+ Syntax::Production.new(nt_sentence, [t_a, t_b, t_c])
27
+ end
28
+ let(:sample_item) { Parser::DottedItem.new(sample_prod, 3) }
29
+ let(:sample_vertex) { GFG::ItemVertex.new(sample_item) }
30
+ let(:sample_range) { range(0, 3) }
31
+
32
+ subject { AlternativeNode.new(sample_vertex, sample_range) }
33
+
34
+ context 'Construction:' do
35
+ it 'should be created with a item vertex and a token range' do
36
+ expect { AlternativeNode.new(sample_vertex, sample_range) }.not_to raise_error
37
+ end
38
+ end
39
+
40
+ context 'Initialization:' do
41
+ it 'should know its token range' do
42
+ expect(subject.range).to eq(sample_range)
43
+ expect(subject.origin).to eq(sample_range.low)
44
+ end
45
+
46
+ it "shouldn't have children yet" do
47
+ expect(subject.subnodes).to be_empty
48
+ end
49
+ end # context
50
+
51
+ context 'Provided services:' do
52
+ it 'should accept the addition of subnodes' do
53
+ subnode1 = double('first_subnode')
54
+ subnode2 = double('second_subnode')
55
+ subnode3 = double('third_subnode')
56
+ expect { subject.add_subnode(subnode1) }.not_to raise_error
57
+ subject.add_subnode(subnode2)
58
+ subject.add_subnode(subnode3)
59
+ expect(subject.subnodes).to eq([subnode3, subnode2, subnode1])
60
+ end
61
+
62
+
63
+ it 'should have a string representation' do
64
+ expect(subject.to_string(0)).to eq('Alt(sentence => A B C .)[0, 3]')
65
+ end
66
+ end # context
67
+
68
+ end # describe
69
+ end # module
70
+ end # module
71
+
72
+ # End of file
@@ -0,0 +1,87 @@
1
+ # Mix-in module that generates a Graphviz's DOT file
2
+ # that represents the precedence graph of parse entries.
3
+ module AntecedenceGraph
4
+
5
+ def generate_graph(aParsing, aFile)
6
+ heading = build_heading()
7
+ aFile.puts(heading)
8
+
9
+ fill_graph(aParsing, aFile)
10
+
11
+ trailing = build_trailing()
12
+ aFile.puts(trailing)
13
+ end
14
+
15
+ private
16
+
17
+ def build_heading()
18
+ text = <<-END_STRING
19
+ digraph entries {
20
+ size = "7,9.5";
21
+ page = "8.5,11";
22
+ ratio = fill;
23
+ rankdir = "BT"; // Draw arrows from bottom to top
24
+ END_STRING
25
+
26
+ return text
27
+ end
28
+
29
+ def build_trailing()
30
+ return '}'
31
+ end
32
+
33
+ def fill_graph(aParsing, aFile)
34
+ # Associate to each parse entry a node id
35
+ oid2node_id = build_nodes_id(aParsing)
36
+ aParsing.chart.sets.each_with_index do |entry_set, chart_index|
37
+ # Create the graph nodes
38
+ aFile.puts ''
39
+ aFile.puts(cluster_heading(chart_index))
40
+
41
+ entry_set.entries.each do |entry|
42
+ aFile.puts %Q( #{oid2node_id[entry]}[label="#{entry}"];)
43
+ end
44
+ aFile.puts ' }' # Close cluster
45
+
46
+ # Create the edges
47
+ aFile.puts ''
48
+ entry_set.entries.each do |entry|
49
+ antecedents = aParsing.antecedence[entry]
50
+ antecedents.each do |antec|
51
+ aFile.puts " #{oid2node_id[antec]} -> #{oid2node_id[entry]};"
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # For each parse entry, associate a graph node id
58
+ def build_nodes_id(aParsing)
59
+ # Create a Hash with pairs of the form: object id => node id
60
+ oid2node_id = {}
61
+
62
+ aParsing.chart.sets.each_with_index do |entry_set, chart_index|
63
+ entry_set.entries.each_with_index do |entry, entry_index|
64
+ oid2node_id[entry] = "node_#{chart_index}_#{entry_index}"
65
+ end
66
+ end
67
+
68
+ return oid2node_id
69
+ end
70
+
71
+
72
+ def cluster_heading(anIndex)
73
+ text = <<-END_STRING
74
+ subgraph cluster_chart_#{anIndex} {
75
+ style = rounded;
76
+ color = blue;
77
+ fontsize = 24.0;
78
+ labeljust = "r";
79
+ label="chart[#{anIndex}]";
80
+ END_STRING
81
+
82
+ return text
83
+ end
84
+
85
+
86
+
87
+ end # module