dendroid 0.0.12 → 0.2.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/lib/dendroid/formatters/ascii_tree.rb +142 -0
  4. data/lib/dendroid/formatters/base_formatter.rb +25 -0
  5. data/lib/dendroid/formatters/bracket_notation.rb +50 -0
  6. data/lib/dendroid/grm_analysis/dotted_item.rb +46 -30
  7. data/lib/dendroid/grm_analysis/grm_analyzer.rb +24 -61
  8. data/lib/dendroid/grm_analysis/{choice_items.rb → rule_items.rb} +10 -10
  9. data/lib/dendroid/grm_dsl/base_grm_builder.rb +3 -4
  10. data/lib/dendroid/parsing/and_node.rb +56 -0
  11. data/lib/dendroid/parsing/chart_walker.rb +293 -0
  12. data/lib/dendroid/parsing/composite_parse_node.rb +21 -0
  13. data/lib/dendroid/parsing/empty_rule_node.rb +28 -0
  14. data/lib/dendroid/parsing/or_node.rb +51 -0
  15. data/lib/dendroid/parsing/parse_node.rb +26 -0
  16. data/lib/dendroid/parsing/parse_tree_visitor.rb +127 -0
  17. data/lib/dendroid/parsing/parser.rb +185 -0
  18. data/lib/dendroid/parsing/terminal_node.rb +32 -0
  19. data/lib/dendroid/parsing/walk_progress.rb +117 -0
  20. data/lib/dendroid/recognizer/chart.rb +18 -2
  21. data/lib/dendroid/recognizer/e_item.rb +21 -2
  22. data/lib/dendroid/recognizer/item_set.rb +7 -2
  23. data/lib/dendroid/recognizer/recognizer.rb +69 -69
  24. data/lib/dendroid/syntax/grammar.rb +72 -60
  25. data/lib/dendroid/syntax/rule.rb +71 -13
  26. data/spec/dendroid/grm_analysis/dotted_item_spec.rb +59 -47
  27. data/spec/dendroid/grm_analysis/{choice_items_spec.rb → rule_items_spec.rb} +5 -6
  28. data/spec/dendroid/parsing/chart_walker_spec.rb +223 -0
  29. data/spec/dendroid/parsing/terminal_node_spec.rb +36 -0
  30. data/spec/dendroid/recognizer/e_item_spec.rb +5 -5
  31. data/spec/dendroid/recognizer/item_set_spec.rb +16 -8
  32. data/spec/dendroid/recognizer/recognizer_spec.rb +57 -5
  33. data/spec/dendroid/support/sample_grammars.rb +2 -0
  34. data/spec/dendroid/syntax/grammar_spec.rb +44 -34
  35. data/spec/dendroid/syntax/rule_spec.rb +56 -7
  36. data/version.txt +1 -1
  37. metadata +20 -13
  38. data/lib/dendroid/grm_analysis/alternative_item.rb +0 -70
  39. data/lib/dendroid/grm_analysis/production_items.rb +0 -55
  40. data/lib/dendroid/syntax/choice.rb +0 -95
  41. data/lib/dendroid/syntax/production.rb +0 -82
  42. data/spec/dendroid/grm_analysis/alternative_item_spec.rb +0 -12
  43. data/spec/dendroid/grm_analysis/production_items_spec.rb +0 -68
  44. data/spec/dendroid/syntax/choice_spec.rb +0 -68
  45. data/spec/dendroid/syntax/production_spec.rb +0 -92
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../spec_helper'
4
+ require_relative '../support/sample_grammars'
5
+ require_relative '../../../lib/dendroid/recognizer/recognizer'
6
+ require_relative '../../../lib/dendroid/parsing/chart_walker'
7
+
8
+ # require_relative '../grm_dsl/base_grm_builder'
9
+ # require_relative '../utils/base_tokenizer'
10
+ # require_relative '../recognizer/recognizer'
11
+ # require_relative 'chart_walker'
12
+ # require_relative 'parse_tree_visitor'
13
+ # require_relative '../formatters/bracket_notation'
14
+ # require_relative '../formatters/ascii_tree'
15
+
16
+ RSpec.describe Dendroid::Parsing::ChartWalker do
17
+ include SampleGrammars
18
+
19
+ def retrieve_success_item(chart, grammar)
20
+ last_item_set = chart.item_sets.last
21
+ result = nil
22
+ last_item_set.items.reverse_each do |itm|
23
+ if itm.origin.zero? && itm.dotted_item.completed? && itm.dotted_item.rule.lhs == grammar.start_symbol
24
+ result = itm
25
+ break
26
+ end
27
+ end
28
+
29
+ result
30
+ end
31
+
32
+ def recognizer_for(grammar, tokenizer)
33
+ Dendroid::Recognizer::Recognizer.new(grammar, tokenizer)
34
+ end
35
+
36
+ def success_entry(chart, recognizer)
37
+ retrieve_success_item(chart, recognizer.grm_analysis.grammar)
38
+ end
39
+
40
+ context 'Parsing non-ambiguous grammars' do
41
+ it 'generates a parse tree for the example from Wikipedia' do
42
+ recognizer = recognizer_for(grammar_l1, tokenizer_l1)
43
+ chart = recognizer.run('2 + 3 * 4')
44
+ walker = described_class.new(chart)
45
+ root = walker.walk(success_entry(chart, recognizer))
46
+
47
+ expect(root.to_s).to eq('p => s [0, 5]')
48
+ expect(root.children.size). to eq(1)
49
+ expect(root.children[-1].to_s).to eq('s => s PLUS m [0, 5]')
50
+ plus_expr = root.children[-1]
51
+ expect(plus_expr.children.size).to eq(3)
52
+ expect(plus_expr.children[0].to_s).to eq('s => m [0, 1]')
53
+ expect(plus_expr.children[1].to_s).to eq('PLUS [1, 2]')
54
+ expect(plus_expr.children[2].to_s).to eq('m => m STAR t [2, 5]')
55
+
56
+ operand_plus = plus_expr.children[0]
57
+ expect(operand_plus.children.size).to eq(1)
58
+ expect(operand_plus.children[0].to_s).to eq('m => t [0, 1]')
59
+ expect(operand_plus.children[0].children.size).to eq(1)
60
+ expect(operand_plus.children[0].children[0].to_s).to eq('t => INTEGER [0, 1]')
61
+ expect(operand_plus.children[0].children[0].children[0].to_s).to eq('INTEGER: 2 [0, 1]')
62
+
63
+ expect(plus_expr.children[1].to_s).to eq('PLUS [1, 2]')
64
+
65
+ star_expr = plus_expr.children[2]
66
+ expect(star_expr.children.size).to eq(3)
67
+ expect(star_expr.children[0].to_s).to eq('m => t [2, 3]')
68
+ expect(star_expr.children[1].to_s).to eq('STAR [3, 4]')
69
+ expect(star_expr.children[2].to_s).to eq('t => INTEGER [4, 5]')
70
+
71
+ operand_star = star_expr.children[0]
72
+ expect(operand_star.children.size).to eq(1)
73
+ expect(operand_star.children[0].to_s).to eq('t => INTEGER [2, 3]')
74
+ expect(operand_star.children[0].children[0].to_s).to eq('INTEGER: 3 [2, 3]')
75
+
76
+ expect(star_expr.children[2].children.size).to eq(1)
77
+ expect(star_expr.children[2].children[0].to_s).to eq('INTEGER: 4 [4, 5]')
78
+ end
79
+
80
+ it 'generates a parse tree for grammar l10 (with left recursive rule)' do
81
+ recognizer = recognizer_for(grammar_l10, tokenizer_l10)
82
+ chart = recognizer.run('a a a a a')
83
+ walker = described_class.new(chart)
84
+ root = walker.walk(success_entry(chart, recognizer))
85
+
86
+ expect(root.to_s).to eq('A => A a [0, 5]')
87
+ expect(root.children.size). to eq(2)
88
+ expect(root.children[0].to_s).to eq('A => A a [0, 4]')
89
+ expect(root.children[1].to_s).to eq('a [4, 5]')
90
+
91
+ expect(root.children[0].children.size).to eq(2)
92
+ expect(root.children[0].children[0].to_s).to eq('A => A a [0, 3]')
93
+ expect(root.children[0].children[1].to_s).to eq('a [3, 4]')
94
+
95
+ grand_child = root.children[0].children[0]
96
+ expect(grand_child.children.size).to eq(2)
97
+ expect(grand_child.children[0].to_s).to eq('A => A a [0, 2]')
98
+ expect(grand_child.children[1].to_s).to eq('a [2, 3]')
99
+
100
+ expect(grand_child.children[0].children.size).to eq(2)
101
+ expect(grand_child.children[0].children[0].to_s).to eq('A => A a [0, 1]')
102
+ expect(grand_child.children[0].children[1].to_s).to eq('a [1, 2]')
103
+
104
+ expect(grand_child.children[0].children[0].children.size).to eq(2)
105
+ expect(grand_child.children[0].children[0].children[0].to_s).to eq('_ [0, 0]')
106
+ expect(grand_child.children[0].children[0].children[1].to_s).to eq('a [0, 1]')
107
+ end
108
+
109
+ it 'generates a parse tree for grammar l11 (with right recursive rule)' do
110
+ recognizer = recognizer_for(grammar_l11, tokenizer_l11)
111
+ chart = recognizer.run('a a a a a')
112
+ walker = described_class.new(chart)
113
+ root = walker.walk(success_entry(chart, recognizer))
114
+
115
+ expect(root.to_s).to eq('A => a A [0, 5]')
116
+ expect(root.children.size). to eq(2)
117
+ expect(root.children[0].to_s).to eq('a [0, 1]')
118
+ expect(root.children[1].to_s).to eq('A => a A [1, 5]')
119
+
120
+ expect(root.children[1].children.size).to eq(2)
121
+ expect(root.children[1].children[0].to_s).to eq('a [1, 2]')
122
+ expect(root.children[1].children[1].to_s).to eq('A => a A [2, 5]')
123
+
124
+ grand_child = root.children[1].children[1]
125
+ expect(grand_child.children.size).to eq(2)
126
+ expect(grand_child.children[0].to_s).to eq('a [2, 3]')
127
+ expect(grand_child.children[1].to_s).to eq('A => a A [3, 5]')
128
+
129
+ expect(grand_child.children[1].children.size).to eq(2)
130
+ expect(grand_child.children[1].children[0].to_s).to eq('a [3, 4]')
131
+ expect(grand_child.children[1].children[1].to_s).to eq('A => a A [4, 5]')
132
+
133
+ expect(grand_child.children[1].children[1].children.size).to eq(2)
134
+ expect(grand_child.children[1].children[1].children[0].to_s).to eq('a [4, 5]')
135
+ expect(grand_child.children[1].children[1].children[1].to_s).to eq('_ [5, 5]')
136
+ end
137
+ end # context
138
+
139
+ context 'Parsing ambiguous grammars' do
140
+ it "generates a parse forest for the G2 grammar that choked Earley's parsing algorithm" do
141
+ recognizer = recognizer_for(grammar_l8, tokenizer_l8)
142
+ chart = recognizer.run('x x x x')
143
+ walker = described_class.new(chart)
144
+ root = walker.walk(success_entry(chart, recognizer))
145
+
146
+ expect(root.to_s).to eq('OR: S [0, 4]')
147
+ expect(root.children.size). to eq(3)
148
+ root.children.each do |child|
149
+ expect(child.children.size).to eq(2)
150
+ expect(child.to_s).to eq('S => S S [0, 4]')
151
+ end
152
+ (a, b, c) = root.children
153
+
154
+ # Test structure of tree a
155
+ (child_a_0, child_a_1) = a.children
156
+ expect(child_a_0.to_s).to eq('S => S S [0, 2]')
157
+ expect(child_a_1.to_s).to eq('S => S S [2, 4]')
158
+ expect(child_a_0.children.size).to eq(2)
159
+ (child_a_0_0, child_a_0_1) = child_a_0.children
160
+ expect(child_a_0_0.to_s).to eq('S => x [0, 1]')
161
+ expect(child_a_0_1.to_s).to eq('S => x [1, 2]')
162
+ expect(child_a_0_0.children[0].to_s).to eq('x [0, 1]')
163
+ expect(child_a_0_1.children[0].to_s).to eq('x [1, 2]')
164
+
165
+ expect(child_a_1.children.size).to eq(2)
166
+ (child_a_1_0, child_a_1_1) = child_a_1.children
167
+ expect(child_a_1_0.to_s).to eq('S => x [2, 3]')
168
+ expect(child_a_1_1.to_s).to eq('S => x [3, 4]')
169
+ expect(child_a_1_0.children[0].to_s).to eq('x [2, 3]')
170
+ expect(child_a_1_1.children[0].to_s).to eq('x [3, 4]')
171
+
172
+ # Test structure of forest b
173
+ (child_b_0, child_b_1) = b.children
174
+ expect(child_b_0.to_s).to eq('OR: S [0, 3]')
175
+ expect(child_b_1.to_s).to eq('S => x [3, 4]')
176
+ expect(child_b_1.equal?(child_a_1_1)).to be_truthy # Sharing
177
+ expect(child_b_0.children.size).to eq(2)
178
+ (child_b_0_0, child_b_0_1) = child_b_0.children
179
+ expect(child_b_0_0.to_s).to eq('S => S S [0, 3]')
180
+ expect(child_b_0_1.to_s).to eq('S => S S [0, 3]')
181
+ expect(child_b_0_0.children.size).to eq(2)
182
+ (child_b_0_0_0, child_b_0_0_1) = child_b_0_0.children
183
+ expect(child_b_0_0_0.to_s).to eq('S => x [0, 1]')
184
+ expect(child_b_0_0_0.equal?(child_a_0_0)).to be_truthy # Sharing
185
+ expect(child_b_0_0_1.to_s).to eq('S => S S [1, 3]')
186
+ expect(child_b_0_0_1.children.size).to eq(2)
187
+ expect(child_b_0_0_1.children[0].to_s).to eq('S => x [1, 2]')
188
+ expect(child_b_0_0_1.children[0].equal?(child_a_0_1)).to be_truthy # Sharing
189
+ expect(child_b_0_0_1.children[1].to_s).to eq('S => x [2, 3]')
190
+ expect(child_b_0_0_1.children[1].equal?(child_a_1_0)).to be_truthy # Sharing
191
+
192
+ expect(child_b_0_1.children.size).to eq(2)
193
+ (child_b_0_1_0, child_b_0_1_1) = child_b_0_1.children
194
+ expect(child_b_0_1_0.to_s).to eq('S => S S [0, 2]')
195
+ expect(child_b_0_1_0.equal?(child_a_0)).to be_truthy # Sharing
196
+ expect(child_b_0_1_1.to_s).to eq('S => x [2, 3]')
197
+ expect(child_b_0_1_1.equal?(child_a_1_0)).to be_truthy # Sharing
198
+
199
+ # Test structure of forest c
200
+ (child_c_0, child_c_1) = c.children
201
+ expect(child_c_0.to_s).to eq('S => x [0, 1]')
202
+ expect(child_c_0.equal?(child_a_0_0)).to be_truthy # Sharing
203
+ expect(child_c_1.to_s).to eq('OR: S [1, 4]')
204
+ expect(child_c_1.children.size).to eq(2)
205
+ (child_c_1_0, child_c_1_1) = child_c_1.children
206
+ expect(child_c_1_0.to_s).to eq('S => S S [1, 4]')
207
+ expect(child_c_1_1.to_s).to eq('S => S S [1, 4]')
208
+ expect(child_c_1_0.children.size).to eq(2)
209
+ (child_c_1_0_0, child_c_1_0_1) = child_c_1_0.children
210
+ expect(child_c_1_0_0.to_s).to eq('S => x [1, 2]')
211
+ expect(child_c_1_0_0.equal?(child_a_0_1)).to be_truthy # Sharing
212
+ expect(child_c_1_0_1.to_s).to eq('S => S S [2, 4]')
213
+ expect(child_c_1_0_1.equal?(child_a_1)).to be_truthy # Sharing
214
+ (child_c_1_1_0, child_c_1_1_1) = child_c_1_1.children
215
+ expect(child_c_1_1_0.to_s).to eq('S => S S [1, 3]')
216
+ expect(child_c_1_1_0.equal?(child_b_0_0_1)).to be_truthy # Sharing
217
+ expect(child_c_1_1_1.to_s).to eq('S => x [3, 4]')
218
+ expect(child_c_1_1_1.equal?(child_b_1)).to be_truthy # Sharing
219
+ end
220
+ end # context
221
+ end # describe
222
+
223
+
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../spec_helper'
4
+ require_relative '../../../lib\dendroid/syntax/terminal'
5
+ require_relative '../../../lib/dendroid/lexical/token_position'
6
+ require_relative '../../../lib/dendroid/lexical/literal'
7
+ require_relative '../../../lib/dendroid/parsing/terminal_node'
8
+
9
+ RSpec.describe Dendroid::Parsing::TerminalNode do
10
+ let(:ex_source) { '+' }
11
+ let(:ex_pos) { Dendroid::Lexical::TokenPosition.new(2, 5) }
12
+ let(:ex_terminal) { Dendroid::Syntax::Terminal.new('PLUS') }
13
+ let(:plus_token) { Dendroid::Lexical::Token.new(ex_source, ex_pos, ex_terminal) }
14
+ let(:plus_node) { described_class.new(ex_terminal, plus_token, 3) }
15
+
16
+ let(:int_source) { '2' }
17
+ let(:int_symbol) { Dendroid::Syntax::Terminal.new('INTEGER') }
18
+ let(:int_token) { Dendroid::Lexical::Literal.new(int_source, ex_pos, int_symbol, 2) }
19
+ let(:int_node) { described_class.new(int_symbol, int_token, 5) }
20
+
21
+ context 'Initialization:' do
22
+ it 'should be initialized with a symbol, terminal and a rank' do
23
+ expect { described_class.new(ex_terminal, plus_token, 3) }.not_to raise_error
24
+ end
25
+ end
26
+
27
+ context 'provided services' do
28
+ it 'renders a String representation of itself' do
29
+ expect(plus_node.to_s).to eq('PLUS [3, 4]')
30
+ end
31
+
32
+ it 'renders also the token value (if any)' do
33
+ expect(int_node.to_s).to eq('INTEGER: 2 [5, 6]')
34
+ end
35
+ end
36
+ end
@@ -4,7 +4,7 @@ require_relative '../../spec_helper'
4
4
  require_relative '../../../lib/dendroid/syntax/terminal'
5
5
  require_relative '../../../lib/dendroid/syntax/non_terminal'
6
6
  require_relative '../../../lib/dendroid/syntax/symbol_seq'
7
- require_relative '../../../lib/dendroid/syntax/production'
7
+ require_relative '../../../lib/dendroid/syntax/rule'
8
8
  require_relative '../../../lib/dendroid/grm_analysis/dotted_item'
9
9
  require_relative '../../../lib/dendroid/recognizer/e_item'
10
10
 
@@ -14,10 +14,10 @@ describe Dendroid::Recognizer::EItem do
14
14
  let(:expr_symb) { Dendroid::Syntax::NonTerminal.new('expression') }
15
15
  let(:rhs) { Dendroid::Syntax::SymbolSeq.new([num_symb, plus_symb, num_symb]) }
16
16
  let(:empty_body) { Dendroid::Syntax::SymbolSeq.new([]) }
17
- let(:prod) { Dendroid::Syntax::Production.new(expr_symb, rhs) }
18
- let(:empty_prod) { Dendroid::Syntax::Production.new(expr_symb, empty_body) }
19
- let(:sample_dotted) { Dendroid::GrmAnalysis::DottedItem.new(prod, 1) }
20
- let(:other_dotted) { Dendroid::GrmAnalysis::DottedItem.new(empty_prod, 0) }
17
+ let(:prod) { Dendroid::Syntax::Rule.new(expr_symb, [rhs]) }
18
+ let(:empty_prod) { Dendroid::Syntax::Rule.new(expr_symb, [empty_body]) }
19
+ let(:sample_dotted) { Dendroid::GrmAnalysis::DottedItem.new(prod, 1, 0) }
20
+ let(:other_dotted) { Dendroid::GrmAnalysis::DottedItem.new(empty_prod, 0, 0) }
21
21
  let(:sample_origin) { 3 }
22
22
 
23
23
  subject { described_class.new(sample_dotted, sample_origin) }
@@ -4,7 +4,7 @@ require_relative '../../spec_helper'
4
4
  require_relative '../../../lib/dendroid/syntax/terminal'
5
5
  require_relative '../../../lib/dendroid/syntax/non_terminal'
6
6
  require_relative '../../../lib/dendroid/syntax/symbol_seq'
7
- require_relative '../../../lib/dendroid/syntax/production'
7
+ require_relative '../../../lib/dendroid/syntax/rule'
8
8
  require_relative '../../../lib/dendroid/grm_analysis/dotted_item'
9
9
  require_relative '../../../lib/dendroid/recognizer/e_item'
10
10
  require_relative '../../../lib/dendroid/recognizer/item_set'
@@ -15,11 +15,11 @@ describe Dendroid::Recognizer::ItemSet do
15
15
  let(:expr_symb) { Dendroid::Syntax::NonTerminal.new('expression') }
16
16
  let(:rhs) { Dendroid::Syntax::SymbolSeq.new([num_symb, plus_symb, num_symb]) }
17
17
  let(:empty_body) { Dendroid::Syntax::SymbolSeq.new([]) }
18
- let(:prod) { Dendroid::Syntax::Production.new(expr_symb, rhs) }
19
- let(:empty_prod) { Dendroid::Syntax::Production.new(expr_symb, empty_body) }
20
- let(:sample_dotted) { Dendroid::GrmAnalysis::DottedItem.new(prod, 1) }
18
+ let(:prod) { Dendroid::Syntax::Rule.new(expr_symb, [rhs]) }
19
+ let(:empty_prod) { Dendroid::Syntax::Rule.new(expr_symb, [empty_body]) }
20
+ let(:sample_dotted) { Dendroid::GrmAnalysis::DottedItem.new(prod, 1, 0) }
21
21
  let(:sample_origin) { 3 }
22
- let(:other_dotted) { Dendroid::GrmAnalysis::DottedItem.new(empty_prod, 0) }
22
+ let(:other_dotted) { Dendroid::GrmAnalysis::DottedItem.new(empty_prod, 0, 0) }
23
23
  let(:first_element) { Dendroid::Recognizer::EItem.new(sample_dotted, sample_origin) }
24
24
  let(:second_element) { Dendroid::Recognizer::EItem.new(other_dotted, 5) }
25
25
 
@@ -37,15 +37,23 @@ describe Dendroid::Recognizer::ItemSet do
37
37
 
38
38
  context 'Provided services:' do
39
39
  it 'adds a new element' do
40
- subject.add_item(first_element)
40
+ added = subject.add_item(first_element)
41
41
  expect(subject.size).to eq(1)
42
+ expect(added).to eq(first_element)
42
43
 
43
- # Trying a second time, doesn't change the set
44
+ # Trying a second time with itentical item, doesn't change the set
44
45
  subject.add_item(first_element)
45
46
  expect(subject.size).to eq(1)
46
47
 
47
- subject.add_item(second_element)
48
+ # Trying a third time with equal item, doesn't change the set
49
+ similar = first_element.dup
50
+ added = subject.add_item(similar)
51
+ expect(subject.size).to eq(1)
52
+ expect(added).to eq(first_element)
53
+
54
+ added = subject.add_item(second_element)
48
55
  expect(subject.size).to eq(2)
56
+ expect(added).to eq(second_element)
49
57
  end
50
58
 
51
59
  it 'can render a String representation of itself' do
@@ -51,7 +51,7 @@ describe Dendroid::Recognizer::Recognizer do
51
51
  'm => t . @ 0',
52
52
  's => m . @ 0',
53
53
  # 'm => m . STAR t @ 0',
54
- 'p => s . @ 0',
54
+ 'p => s . @ 0', # Can be ruled out (next token != eos)
55
55
  's => s . PLUS m @ 0'
56
56
  ]
57
57
  set2 = [ # 2 + . 3 * 4'
@@ -65,7 +65,7 @@ describe Dendroid::Recognizer::Recognizer do
65
65
  'm => t . @ 2',
66
66
  's => s PLUS m . @ 0',
67
67
  'm => m . STAR t @ 2',
68
- 'p => s . @ 0'
68
+ 'p => s . @ 0' # Can be ruled out (next token != eos)
69
69
  # 's => s . PLUS m @ 0'
70
70
  ]
71
71
  set4 = [ # 2 + 3 * . 4'
@@ -422,6 +422,55 @@ describe Dendroid::Recognizer::Recognizer do
422
422
  comp_expected_actuals(chart, expectations)
423
423
  end
424
424
 
425
+
426
+ it 'accepts an input with multiple levels of ambiguity' do
427
+ recognizer = described_class.new(grammar_l8, tokenizer_l8)
428
+ chart = recognizer.run('x x x x')
429
+ expect(chart).to be_successful
430
+
431
+ set0 = [ # . x x x x
432
+ 'S => . S S @ 0',
433
+ 'S => . x @ 0'
434
+ ]
435
+ set1 = [ # x . x x x
436
+ 'S => x . @ 0',
437
+ 'S => S . S @ 0',
438
+ 'S => . S S @ 1',
439
+ 'S => . x @ 1'
440
+ ]
441
+ set2 = [ # x x . x x
442
+ 'S => x . @ 1',
443
+ 'S => S S . @ 0',
444
+ 'S => S . S @ 1',
445
+ 'S => S . S @ 0',
446
+ 'S => . S S @ 2',
447
+ 'S => . x @ 2'
448
+ ]
449
+ set3 = [ # x x x . x
450
+ 'S => x . @ 2',
451
+ 'S => S S . @ 1',
452
+ 'S => S S . @ 0',
453
+ 'S => S . S @ 2',
454
+ 'S => S . S @ 1',
455
+ 'S => S . S @ 0',
456
+ 'S => . S S @ 3',
457
+ 'S => . x @ 3'
458
+ ]
459
+ set4 = [ # x x x x .
460
+ 'S => x . @ 3',
461
+ 'S => S S . @ 2',
462
+ 'S => S S . @ 1',
463
+ 'S => S S . @ 0', # Success entry
464
+ 'S => S . S @ 3',
465
+ 'S => S . S @ 2',
466
+ 'S => S . S @ 1',
467
+ 'S => S . S @ 0',
468
+ 'S => . S S @ 4'
469
+ ]
470
+ expectations = [set0, set1, set2, set3, set4]
471
+ comp_expected_actuals(chart, expectations)
472
+ end
473
+
425
474
  it 'swallows the input from an infinite ambiguity grammar' do
426
475
  recognizer = described_class.new(grammar_l9, tokenizer_l9)
427
476
  chart = recognizer.run('x x x')
@@ -441,7 +490,8 @@ describe Dendroid::Recognizer::Recognizer do
441
490
  'S => . S S @ 1',
442
491
  'S => . @ 1',
443
492
  'S => . x @ 1',
444
- 'S => S . S @ 1'
493
+ 'S => S . S @ 1',
494
+ 'S => S S . @ 1'
445
495
  ]
446
496
  set2 = [ # x x . x
447
497
  'S => x . @ 1',
@@ -452,7 +502,8 @@ describe Dendroid::Recognizer::Recognizer do
452
502
  'S => . S S @ 2',
453
503
  'S => . @ 2',
454
504
  'S => . x @ 2',
455
- 'S => S . S @ 2'
505
+ 'S => S . S @ 2',
506
+ 'S => S S . @ 2'
456
507
  ]
457
508
  set3 = [ # x x x .
458
509
  'S => x . @ 2',
@@ -465,7 +516,8 @@ describe Dendroid::Recognizer::Recognizer do
465
516
  'S => . S S @ 3',
466
517
  'S => . @ 3',
467
518
  # 'S => . x @ 3',
468
- 'S => S . S @ 3'
519
+ 'S => S . S @ 3',
520
+ 'S => S S . @ 3'
469
521
  ]
470
522
  expectations = [set0, set1, set2, set3]
471
523
  comp_expected_actuals(chart, expectations)
@@ -211,6 +211,7 @@ module SampleGrammars
211
211
  end
212
212
 
213
213
  def grammar_l10
214
+ # Grammar with left recursive rule
214
215
  builder = Dendroid::GrmDSL::BaseGrmBuilder.new do
215
216
  declare_terminals('a')
216
217
 
@@ -230,6 +231,7 @@ module SampleGrammars
230
231
 
231
232
  def grammar_l11
232
233
  builder = Dendroid::GrmDSL::BaseGrmBuilder.new do
234
+ # Grammar with right-recursive rule
233
235
  declare_terminals('a')
234
236
 
235
237
  rule 'A' => ['a A', '']
@@ -4,8 +4,7 @@ require_relative '..\..\spec_helper'
4
4
  require_relative '..\..\..\lib\dendroid\syntax\terminal'
5
5
  require_relative '..\..\..\lib\dendroid\syntax\non_terminal'
6
6
  require_relative '..\..\..\lib\dendroid\syntax\symbol_seq'
7
- require_relative '..\..\..\lib\dendroid\syntax\production'
8
- require_relative '..\..\..\lib\dendroid\syntax\choice'
7
+ require_relative '..\..\..\lib\dendroid\syntax\rule'
9
8
  require_relative '..\..\..\lib\dendroid\syntax\grammar'
10
9
  require_relative '..\..\..\lib\dendroid\grm_dsl\base_grm_builder'
11
10
 
@@ -33,19 +32,15 @@ describe Dendroid::Syntax::Grammar do
33
32
  Dendroid::Syntax::SymbolSeq.new(symbols)
34
33
  end
35
34
 
36
- def build_production(lhs, symbols)
37
- Dendroid::Syntax::Production.new(lhs, build_symbol_seq(symbols))
38
- end
39
-
40
35
  def build_choice(lhs, sequences)
41
- Dendroid::Syntax::Choice.new(lhs, sequences.map { |arr| build_symbol_seq(arr) })
36
+ Dendroid::Syntax::Rule.new(lhs, sequences.map { |arr| build_symbol_seq(arr) })
42
37
  end
43
38
 
44
39
  def build_all_rules
45
- rule1 = build_production(p_symb, [s_symb]) # p => s
40
+ rule1 = build_choice(p_symb, [[s_symb]]) # p => s
46
41
  rule2 = build_choice(s_symb, [[s_symb, plus_symb, m_symb], [m_symb]]) # s => s + m | m
47
42
  rule3 = build_choice(m_symb, [[m_symb, star_symb, t_symb], [t_symb]]) # m => m * t
48
- rule4 = build_production(t_symb, [int_symb]) # t => INTEGER
43
+ rule4 = build_choice(t_symb, [[int_symb]]) # t => INTEGER
49
44
  [rule1, rule2, rule3, rule4]
50
45
  end
51
46
 
@@ -58,8 +53,8 @@ describe Dendroid::Syntax::Grammar do
58
53
  expect(subject.symbols).to eq(all_terminals)
59
54
  end
60
55
 
61
- it 'ignores about productions after initialization' do
62
- expect(subject.rules).to be_nil
56
+ it 'does not have rules after initialization' do
57
+ expect(subject.rules).to be_empty
63
58
  end
64
59
 
65
60
  it 'maps a terminal name to one GrmSymbol object' do
@@ -72,7 +67,7 @@ describe Dendroid::Syntax::Grammar do
72
67
 
73
68
  context 'Adding productions:' do
74
69
  it 'allows the addition of one production rule' do
75
- rule = build_production(p_symb, [s_symb])
70
+ rule = build_choice(p_symb, [[s_symb]])
76
71
  expect { subject.add_rule(rule) }.not_to raise_error
77
72
  expect(subject.rules.size).to eq(1)
78
73
  expect(subject.rules.first).to eq(rule)
@@ -109,14 +104,15 @@ describe Dendroid::Syntax::Grammar do
109
104
  end
110
105
  end
111
106
 
112
- it 'maps every non-terminal to its defining productions' do
107
+ it 'maps every non-terminal to its defining production' do
113
108
  rules = build_all_rules
114
109
  rules.each { |rl| subject.add_rule(rl) }
115
110
  %i[p s m t].each do |symb_name|
116
111
  symb = subject.name2symbol[symb_name]
117
112
  expected_prods = subject.rules.select { |prd| prd.head == symb }
118
- related_prods = subject.nonterm2productions[symb]
119
- expect(related_prods).to eq(expected_prods)
113
+ expect(expected_prods.size).to eq(1)
114
+ related_prod = subject.nonterm2production[symb]
115
+ expect(related_prod).to eq(expected_prods[0])
120
116
  end
121
117
  end
122
118
  end # context
@@ -140,10 +136,10 @@ describe Dendroid::Syntax::Grammar do
140
136
  nterm_e = build_nonterminal('E')
141
137
 
142
138
  instance = described_class.new([terminal_a])
143
- instance.add_rule(build_production(nterm_s_prime, [nterm_s]))
144
- instance.add_rule(build_production(nterm_s, [nterm_a, nterm_a, nterm_a, nterm_a]))
139
+ instance.add_rule(build_choice(nterm_s_prime, [[nterm_s]]))
140
+ instance.add_rule(build_choice(nterm_s, [[nterm_a, nterm_a, nterm_a, nterm_a]]))
145
141
  instance.add_rule(build_choice(nterm_a, [[terminal_a], [nterm_e]]))
146
- instance.add_rule(build_production(nterm_e, []))
142
+ instance.add_rule(build_choice(nterm_e, [[]]))
147
143
 
148
144
  instance.complete!
149
145
  all_nonterminals = subject.symbols.reject(&:terminal?)
@@ -159,7 +155,7 @@ describe Dendroid::Syntax::Grammar do
159
155
  # Let add's unreachable symbols
160
156
  zed_symb = build_nonterminal('Z')
161
157
  question_symb = build_nonterminal('?')
162
- bad_rule = build_production(zed_symb, [zed_symb, question_symb, int_symb]) # Z => Z ? INTEGER
158
+ bad_rule = build_choice(zed_symb, [[zed_symb, question_symb, int_symb]]) # Z => Z ? INTEGER
163
159
  subject.add_rule(bad_rule)
164
160
  unreachable = subject.send(:unreachable_symbols)
165
161
  expect(unreachable).not_to be_empty
@@ -174,7 +170,7 @@ describe Dendroid::Syntax::Grammar do
174
170
  expect(t_symb).to be_productive
175
171
  expect(p_symb).to be_productive
176
172
 
177
- # Grammar with non-productive symbols
173
+ # # Grammar with non-productive symbols
178
174
  term_a = build_terminal('a')
179
175
  term_b = build_terminal('b')
180
176
  term_c = build_terminal('c')
@@ -190,12 +186,12 @@ describe Dendroid::Syntax::Grammar do
190
186
  nterm_S = build_nonterminal('S')
191
187
  instance = described_class.new([term_a, term_b, term_c, term_d, term_e, term_f])
192
188
  instance.add_rule(build_choice(nterm_S, [[nterm_A, nterm_B], [nterm_D, nterm_E]]))
193
- instance.add_rule(build_production(nterm_A, [term_a]))
194
- instance.add_rule(build_production(nterm_B, [term_b, nterm_C]))
195
- instance.add_rule(build_production(nterm_C, [term_c]))
196
- instance.add_rule(build_production(nterm_D, [term_d, nterm_F]))
197
- instance.add_rule(build_production(nterm_E, [term_e]))
198
- instance.add_rule(build_production(nterm_F, [term_f, nterm_D]))
189
+ instance.add_rule(build_choice(nterm_A, [[term_a]]))
190
+ instance.add_rule(build_choice(nterm_B, [[term_b, nterm_C]]))
191
+ instance.add_rule(build_choice(nterm_C, [[term_c]]))
192
+ instance.add_rule(build_choice(nterm_D, [[term_d, nterm_F]]))
193
+ instance.add_rule(build_choice(nterm_E, [[term_e]]))
194
+ instance.add_rule(build_choice(nterm_F, [[term_f, nterm_D]]))
199
195
  nonproductive = instance.send(:mark_non_productive_symbols)
200
196
  expect(nonproductive).not_to be_empty
201
197
  expect(nonproductive).to eq([nterm_D, nterm_F])
@@ -230,8 +226,7 @@ describe Dendroid::Syntax::Grammar do
230
226
  # No terminal symbol explicitly declared => all symbols are non-terminals
231
227
 
232
228
  rule 'S' => 'A'
233
- rule 'A' => 'a A c'
234
- rule 'A' => 'b'
229
+ rule 'A' => ['a A c', 'b']
235
230
  end
236
231
 
237
232
  builder.grammar
@@ -243,8 +238,7 @@ describe Dendroid::Syntax::Grammar do
243
238
 
244
239
  # # Wrong: terminals 'd' and 'e' never appear in rules
245
240
  rule 'S' => 'A'
246
- rule 'A' => 'a A c'
247
- rule 'A' => 'b'
241
+ rule 'A' => ['a A c', 'b']
248
242
  end
249
243
 
250
244
  builder.grammar
@@ -282,13 +276,24 @@ describe Dendroid::Syntax::Grammar do
282
276
  builder.grammar
283
277
  end
284
278
 
279
+ def grm_multiple_defs
280
+ builder = Dendroid::GrmDSL::BaseGrmBuilder.new do
281
+ declare_terminals('a b c')
282
+
283
+ rule 'A' => %w[a B]
284
+ rule 'B' => ['b', '']
285
+ rule 'A' => 'c'
286
+ end
287
+
288
+ builder.grammar
289
+ end
290
+
285
291
  def duplicate_production
286
292
  builder = Dendroid::GrmDSL::BaseGrmBuilder.new do
287
- declare_terminals('a', 'b', 'c')
293
+ declare_terminals('a')
288
294
 
289
295
  rule 'S' => 'A'
290
- rule 'A' => ['a A c', 'b']
291
- rule 'S' => 'A' # Duplicate rule
296
+ rule 'A' => %w[a a] # Duplicate alternatives
292
297
  end
293
298
 
294
299
  builder.grammar
@@ -329,8 +334,13 @@ describe Dendroid::Syntax::Grammar do
329
334
  expect { grm_undefined_nterm }.to raise_error(StandardError, err_msg)
330
335
  end
331
336
 
337
+ it 'raises an error when a non-terminal is defined multiple times' do
338
+ err_msg = "Non-terminal 'A' is on left-hand side of more than one rule."
339
+ expect { grm_multiple_defs }.to raise_error(StandardError, err_msg)
340
+ end
341
+
332
342
  it 'raises an error when a production is duplicated' do
333
- err_msg = "Production rule 'S => A' appears more than once in the grammar."
343
+ err_msg = 'Duplicate alternatives: A => a'
334
344
  expect { duplicate_production }.to raise_error(StandardError, err_msg)
335
345
  end
336
346