rley 0.2.15 → 0.3.00

Sign up to get free protection for your applications and to get access to all the features.
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