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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/dendroid/formatters/ascii_tree.rb +142 -0
- data/lib/dendroid/formatters/base_formatter.rb +25 -0
- data/lib/dendroid/formatters/bracket_notation.rb +50 -0
- data/lib/dendroid/grm_analysis/dotted_item.rb +46 -30
- data/lib/dendroid/grm_analysis/grm_analyzer.rb +24 -61
- data/lib/dendroid/grm_analysis/{choice_items.rb → rule_items.rb} +10 -10
- data/lib/dendroid/grm_dsl/base_grm_builder.rb +3 -4
- data/lib/dendroid/parsing/and_node.rb +56 -0
- data/lib/dendroid/parsing/chart_walker.rb +293 -0
- data/lib/dendroid/parsing/composite_parse_node.rb +21 -0
- data/lib/dendroid/parsing/empty_rule_node.rb +28 -0
- data/lib/dendroid/parsing/or_node.rb +51 -0
- data/lib/dendroid/parsing/parse_node.rb +26 -0
- data/lib/dendroid/parsing/parse_tree_visitor.rb +127 -0
- data/lib/dendroid/parsing/parser.rb +185 -0
- data/lib/dendroid/parsing/terminal_node.rb +32 -0
- data/lib/dendroid/parsing/walk_progress.rb +117 -0
- data/lib/dendroid/recognizer/chart.rb +18 -2
- data/lib/dendroid/recognizer/e_item.rb +21 -2
- data/lib/dendroid/recognizer/item_set.rb +7 -2
- data/lib/dendroid/recognizer/recognizer.rb +69 -69
- data/lib/dendroid/syntax/grammar.rb +72 -60
- data/lib/dendroid/syntax/rule.rb +71 -13
- data/spec/dendroid/grm_analysis/dotted_item_spec.rb +59 -47
- data/spec/dendroid/grm_analysis/{choice_items_spec.rb → rule_items_spec.rb} +5 -6
- data/spec/dendroid/parsing/chart_walker_spec.rb +223 -0
- data/spec/dendroid/parsing/terminal_node_spec.rb +36 -0
- data/spec/dendroid/recognizer/e_item_spec.rb +5 -5
- data/spec/dendroid/recognizer/item_set_spec.rb +16 -8
- data/spec/dendroid/recognizer/recognizer_spec.rb +57 -5
- data/spec/dendroid/support/sample_grammars.rb +2 -0
- data/spec/dendroid/syntax/grammar_spec.rb +44 -34
- data/spec/dendroid/syntax/rule_spec.rb +56 -7
- data/version.txt +1 -1
- metadata +20 -13
- data/lib/dendroid/grm_analysis/alternative_item.rb +0 -70
- data/lib/dendroid/grm_analysis/production_items.rb +0 -55
- data/lib/dendroid/syntax/choice.rb +0 -95
- data/lib/dendroid/syntax/production.rb +0 -82
- data/spec/dendroid/grm_analysis/alternative_item_spec.rb +0 -12
- data/spec/dendroid/grm_analysis/production_items_spec.rb +0 -68
- data/spec/dendroid/syntax/choice_spec.rb +0 -68
- 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/
|
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::
|
18
|
-
let(:empty_prod) { Dendroid::Syntax::
|
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/
|
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::
|
19
|
-
let(:empty_prod) { Dendroid::Syntax::
|
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
|
-
|
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\
|
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::
|
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 =
|
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 =
|
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 '
|
62
|
-
expect(subject.rules).to
|
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 =
|
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
|
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
|
-
|
119
|
-
|
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(
|
144
|
-
instance.add_rule(
|
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(
|
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 =
|
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(
|
194
|
-
instance.add_rule(
|
195
|
-
instance.add_rule(
|
196
|
-
instance.add_rule(
|
197
|
-
instance.add_rule(
|
198
|
-
instance.add_rule(
|
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'
|
293
|
+
declare_terminals('a')
|
288
294
|
|
289
295
|
rule 'S' => 'A'
|
290
|
-
rule 'A' => [
|
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 =
|
343
|
+
err_msg = 'Duplicate alternatives: A => a'
|
334
344
|
expect { duplicate_production }.to raise_error(StandardError, err_msg)
|
335
345
|
end
|
336
346
|
|