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
@@ -17,47 +17,48 @@ module Dendroid
|
|
17
17
|
# @return [Array<Dendroid::Syntax::GrmSymbol>] The terminal and non-terminal symbols.
|
18
18
|
attr_reader :symbols
|
19
19
|
|
20
|
+
# A Hash that maps symbol names to their grammar symbols
|
21
|
+
# @return [Hash{String|Symbol => Dendroid::Syntax::GrmSymbol}]
|
22
|
+
attr_reader :name2symbol
|
23
|
+
|
20
24
|
# The list of production rules for the language.
|
21
25
|
# @return [Array<Dendroid::Syntax::Rule>] Array of rules for the grammar.
|
22
26
|
attr_reader :rules
|
23
27
|
|
24
|
-
# A Hash that maps symbol names to their grammar symbols
|
25
|
-
# @return [Hash{String => Dendroid::Syntax::GrmSymbol}]
|
26
|
-
attr_reader :name2symbol
|
27
|
-
|
28
|
-
# TODO: make nonterminal - rules one-to-one
|
29
28
|
# A Hash that maps symbol names to their grammar symbols
|
30
29
|
# @return [Hash{Dendroid::Syntax::GrmSymbol => Dendroid::Syntax::Rule}]
|
31
|
-
attr_reader :
|
30
|
+
attr_reader :nonterm2production
|
32
31
|
|
33
32
|
# Constructor.
|
34
33
|
# @param terminals [Array<Dendroid::Syntax::Terminal>]
|
35
34
|
def initialize(terminals)
|
36
35
|
@symbols = []
|
37
36
|
@name2symbol = {}
|
37
|
+
@rules = []
|
38
|
+
@nonterm2production = {}
|
38
39
|
add_terminals(terminals)
|
39
40
|
end
|
40
41
|
|
41
|
-
# Add a rule to the grammar
|
42
|
+
# Add a rule to the grammar.
|
42
43
|
# @param rule [Dendroid::Syntax::Rule]
|
43
44
|
def add_rule(rule)
|
44
|
-
if
|
45
|
-
|
46
|
-
|
45
|
+
if lhs_already_defined?(rule)
|
46
|
+
msg = "Non-terminal '#{rule.head}' is on left-hand side of more than one rule."
|
47
|
+
raise StandardError, msg
|
47
48
|
end
|
48
|
-
|
49
|
-
|
50
|
-
raise StandardError, "Production rule '#{rule}' appears more than once in the grammar."
|
49
|
+
if duplicate_rule?(rule)
|
50
|
+
raise StandardError, "Duplicate production rule '#{rule}'."
|
51
51
|
end
|
52
52
|
|
53
53
|
add_symbol(rule.head)
|
54
54
|
rule.nonterminals.each { |nonterm| add_symbol(nonterm) }
|
55
55
|
rules << rule
|
56
|
-
|
57
|
-
nonterm2productions[rule.head] << rule
|
56
|
+
nonterm2production[rule.head] = rule
|
58
57
|
end
|
59
58
|
|
60
|
-
# Return the start symbol for the language
|
59
|
+
# Return the start symbol for the language, that is,
|
60
|
+
# the non-terminal symbol used to denote the top-level
|
61
|
+
# construct of the language being defined.
|
61
62
|
# @return [Dendroid::Syntax::NonTerminal]
|
62
63
|
def start_symbol
|
63
64
|
rules.first.lhs
|
@@ -73,10 +74,14 @@ module Dendroid
|
|
73
74
|
|
74
75
|
private
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
def lhs_already_defined?(rule)
|
78
|
+
nonterm2production.include? rule.head
|
79
|
+
end
|
80
|
+
|
81
|
+
def duplicate_rule?(rule)
|
82
|
+
nonterm2production[rule.head]&.include? rule
|
83
|
+
end
|
84
|
+
|
80
85
|
def add_terminals(terminals)
|
81
86
|
terminals.each { |term| add_symbol(term) }
|
82
87
|
end
|
@@ -89,6 +94,15 @@ module Dendroid
|
|
89
94
|
name2symbol[symb.name.to_s] = symb
|
90
95
|
end
|
91
96
|
|
97
|
+
def all_terminals
|
98
|
+
Set.new(symbols.select(&:terminal?))
|
99
|
+
end
|
100
|
+
|
101
|
+
def all_nonterminals
|
102
|
+
Set.new(symbols.reject(&:terminal?))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Perform correctness checks of the grammar.
|
92
106
|
def validate
|
93
107
|
at_least_one_terminal
|
94
108
|
are_terminals_referenced?
|
@@ -104,7 +118,6 @@ module Dendroid
|
|
104
118
|
# Does the grammar contain at least one terminal symbol?
|
105
119
|
def at_least_one_terminal
|
106
120
|
found = symbols.any?(&:terminal?)
|
107
|
-
|
108
121
|
return true if found
|
109
122
|
|
110
123
|
err_msg = "Grammar doesn't contain any terminal symbol."
|
@@ -114,37 +127,28 @@ module Dendroid
|
|
114
127
|
# Does every terminal symbol appear at least once
|
115
128
|
# in a rhs of a production rule?
|
116
129
|
def are_terminals_referenced?
|
117
|
-
all_terminals = Set.new(symbols.select(&:terminal?))
|
118
130
|
terms_in_rhs = rules.reduce(Set.new) do |collected, prd|
|
119
131
|
found = prd.terminals
|
120
132
|
collected.merge(found)
|
121
133
|
end
|
122
|
-
|
123
|
-
unless check_ok
|
124
|
-
unused_terms = all_terminals.difference(terms_in_rhs)
|
125
|
-
text = unused_terms.map(&:name).join("', '")
|
126
|
-
err_msg = "Terminal symbols '#{text}' never appear in production rules."
|
127
|
-
raise StandardError, err_msg
|
128
|
-
end
|
134
|
+
return true if all_terminals == terms_in_rhs
|
129
135
|
|
130
|
-
|
136
|
+
unused_terms = all_terminals.difference(terms_in_rhs)
|
137
|
+
text = unused_terms.map(&:name).join("', '")
|
138
|
+
err_msg = "Terminal symbols '#{text}' never appear in production rules."
|
139
|
+
raise StandardError, err_msg
|
131
140
|
end
|
132
141
|
|
133
142
|
def are_nonterminals_rewritten?
|
134
|
-
all_nonterminals = Set.new(symbols.reject(&:terminal?))
|
135
|
-
|
136
143
|
symbs_in_lhs = rules.reduce(Set.new) do |collected, prd|
|
137
144
|
collected.add(prd.head)
|
138
145
|
end
|
139
|
-
|
140
|
-
unless check_ok
|
141
|
-
undefined_nterms = all_nonterminals.difference(symbs_in_lhs)
|
142
|
-
text = undefined_nterms.map(&:name).join("', '")
|
143
|
-
err_msg = "Non-terminal symbols '#{text}' never appear in head of any production rule."
|
144
|
-
raise StandardError, err_msg
|
145
|
-
end
|
146
|
+
return true if all_nonterminals == symbs_in_lhs
|
146
147
|
|
147
|
-
|
148
|
+
undefined_nterms = all_nonterminals.difference(symbs_in_lhs)
|
149
|
+
text = undefined_nterms.map(&:name).join("', '")
|
150
|
+
err_msg = "Non-terminal symbols '#{text}' never appear in head of any production rule."
|
151
|
+
raise StandardError, err_msg
|
148
152
|
end
|
149
153
|
|
150
154
|
def are_symbols_reachable?
|
@@ -165,28 +169,31 @@ module Dendroid
|
|
165
169
|
raise StandardError, err_msg
|
166
170
|
end
|
167
171
|
|
172
|
+
# rubocop: disable Metrics/AbcSize
|
173
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
174
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
175
|
+
|
168
176
|
# Are all symbols reachable from start symbol?
|
177
|
+
# @return [Set<NonTerminal>] Set of unreachable symbols
|
169
178
|
def unreachable_symbols
|
170
179
|
backlog = [start_symbol]
|
171
180
|
set_reachable = Set.new(backlog.dup)
|
172
181
|
|
173
|
-
|
182
|
+
loop do
|
174
183
|
reachable_sym = backlog.pop
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
backlog.push(member)
|
180
|
-
end
|
181
|
-
set_reachable.add(member)
|
182
|
-
end
|
184
|
+
prd = nonterm2production[reachable_sym]
|
185
|
+
prd.rhs_symbols.each do |member|
|
186
|
+
backlog.push(member) unless member.terminal? || set_reachable.include?(member)
|
187
|
+
set_reachable.add(member)
|
183
188
|
end
|
184
|
-
|
189
|
+
break if backlog.empty?
|
190
|
+
end
|
185
191
|
|
186
192
|
all_symbols = Set.new(symbols)
|
187
193
|
all_symbols - set_reachable
|
188
194
|
end
|
189
195
|
|
196
|
+
# @return [Array<Dendroid::Syntax::NonTerminal>]
|
190
197
|
def mark_non_productive_symbols
|
191
198
|
prod_count = rules.size
|
192
199
|
backlog = Set.new(0...prod_count)
|
@@ -204,7 +211,7 @@ module Dendroid
|
|
204
211
|
backlog.subtract(to_remove)
|
205
212
|
end
|
206
213
|
|
207
|
-
backlog.each { |i| rules[i].non_productive }
|
214
|
+
# backlog.each { |i| rules[i].non_productive }
|
208
215
|
non_productive = symbols.reject(&:productive?)
|
209
216
|
non_productive.each { |symb| symb.productive = false }
|
210
217
|
non_productive
|
@@ -214,11 +221,11 @@ module Dendroid
|
|
214
221
|
nullable_found = false
|
215
222
|
sym2seqs = {}
|
216
223
|
|
217
|
-
|
218
|
-
if
|
224
|
+
nonterm2production.each_pair do |sym, prod|
|
225
|
+
if prod.empty?
|
219
226
|
sym.nullable = nullable_found = true
|
220
227
|
else
|
221
|
-
sym2seqs[sym] =
|
228
|
+
sym2seqs[sym] = prod.rhs
|
222
229
|
end
|
223
230
|
end
|
224
231
|
|
@@ -228,7 +235,7 @@ module Dendroid
|
|
228
235
|
seqs.each { |sq| backlog[sq] = [0, sym] }
|
229
236
|
end
|
230
237
|
|
231
|
-
|
238
|
+
loop do
|
232
239
|
seqs_done = []
|
233
240
|
backlog.each_pair do |sq, (elem_index, lhs)|
|
234
241
|
member = sq[elem_index]
|
@@ -256,18 +263,23 @@ module Dendroid
|
|
256
263
|
backlog.delete(sq)
|
257
264
|
end
|
258
265
|
end
|
259
|
-
|
266
|
+
break if backlog.empty? || seqs_done.empty?
|
267
|
+
end
|
260
268
|
end
|
261
269
|
|
270
|
+
# symbols.each do |sym|
|
271
|
+
# next if sym.terminal?
|
272
|
+
#
|
273
|
+
# sym.nullable = false if sym.nullable.nil?
|
274
|
+
# end
|
262
275
|
symbols.each do |sym|
|
263
|
-
next if sym.terminal?
|
276
|
+
next if sym.terminal? || sym.nullable?
|
264
277
|
|
265
|
-
sym.nullable = false
|
278
|
+
sym.nullable = false
|
266
279
|
end
|
267
280
|
end
|
268
281
|
# rubocop: enable Metrics/AbcSize
|
269
|
-
# rubocop: enable Metrics/
|
270
|
-
# rubocop: enable Metrics/MethodLength
|
282
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
271
283
|
# rubocop: enable Metrics/PerceivedComplexity
|
272
284
|
end # class
|
273
285
|
end # module
|
data/lib/dendroid/syntax/rule.rb
CHANGED
@@ -2,28 +2,60 @@
|
|
2
2
|
|
3
3
|
module Dendroid
|
4
4
|
module Syntax
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# and the right-hand side (RHS) consists of one or more sequence of symbols.
|
8
|
-
# The symbols in RHS can be either terminal or non-terminal symbols.
|
9
|
-
# The rule stipulates that the LHS is equivalent to the RHS,
|
10
|
-
# in other words every occurrence of the LHS can be substituted to
|
11
|
-
# corresponding RHS.
|
5
|
+
# A specialization of the Rule class.
|
6
|
+
# A choice is a rule with multiple rhs
|
12
7
|
class Rule
|
13
8
|
# @return [Dendroid::Syntax::NonTerminal] The left-hand side of the rule.
|
14
9
|
attr_reader :head
|
15
10
|
alias lhs head
|
16
11
|
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
# @return [Array<Dendroid::Syntax::SymbolSeq>]
|
13
|
+
attr_reader :alternatives
|
14
|
+
|
15
|
+
# Create a Choice instance.
|
16
|
+
# @param theLhs [Dendroid::Syntax::NonTerminal] The left-hand side of the rule.
|
17
|
+
# @param alt [Array<Dendroid::Syntax::SymbolSeq>] the alternatives (each as a sequence of symbols).
|
18
|
+
def initialize(theLhs, alt)
|
19
|
+
@head = valid_head(theLhs)
|
20
|
+
@alternatives = valid_alternatives(alt)
|
21
21
|
end
|
22
22
|
|
23
|
-
# Return the text representation of the
|
23
|
+
# Return the text representation of the choice
|
24
24
|
# @return [String]
|
25
25
|
def to_s
|
26
|
-
head.
|
26
|
+
"#{head} => #{alternatives.join(' | ')}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Predicate method to check whether the choice rule body is productive.
|
30
|
+
# It is productive when at least one of its alternative is productive.
|
31
|
+
# @return [Boolean]
|
32
|
+
def productive?
|
33
|
+
productive_alts = alternatives.select(&:productive?)
|
34
|
+
return false if productive_alts.empty?
|
35
|
+
|
36
|
+
@productive = Set.new(productive_alts)
|
37
|
+
head.productive = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# Predicate method to check whether the rule has at least one empty alternative.
|
41
|
+
# @return [Boolean]
|
42
|
+
def empty?
|
43
|
+
alternatives.any?(&:empty?)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns an array with the symbol sequence of its alternatives
|
47
|
+
# @return [Array<Dendroid::Syntax::SymbolSeq>]
|
48
|
+
def rhs
|
49
|
+
alternatives
|
50
|
+
end
|
51
|
+
|
52
|
+
# Equality operator
|
53
|
+
# Two production rules are equal when their head and alternatives are equal.
|
54
|
+
# @return [Boolean]
|
55
|
+
def ==(other)
|
56
|
+
return true if equal?(other)
|
57
|
+
|
58
|
+
(head == other.head) && (alternatives == other.alternatives)
|
27
59
|
end
|
28
60
|
|
29
61
|
# The set of all grammar symbols that occur in the rhs.
|
@@ -70,6 +102,32 @@ module Dendroid
|
|
70
102
|
|
71
103
|
lhs
|
72
104
|
end
|
105
|
+
|
106
|
+
def valid_alternatives(alt)
|
107
|
+
raise StandardError, "Expecting an Array, found a #{rhs.class} instead." unless alt.is_a?(Array)
|
108
|
+
|
109
|
+
if alt.size.zero?
|
110
|
+
# A choice must have at least two alternatives
|
111
|
+
raise StandardError, "The choice for `#{head}` must have at least one alternative."
|
112
|
+
end
|
113
|
+
|
114
|
+
# Verify that each array element is a valid symbol sequence
|
115
|
+
alt.each { |elem| valid_sequence(elem) }
|
116
|
+
|
117
|
+
# Fail when duplicate rhs found
|
118
|
+
alt_texts = alt.map(&:to_s)
|
119
|
+
no_duplicate = alt_texts.uniq
|
120
|
+
if alt_texts.size > no_duplicate.size
|
121
|
+
alt_texts.each_with_index do |str, i|
|
122
|
+
next if str == no_duplicate[i]
|
123
|
+
|
124
|
+
err_msg = "Duplicate alternatives: #{head} => #{alt_texts[i]}"
|
125
|
+
raise StandardError, err_msg
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
alt
|
130
|
+
end
|
73
131
|
end # class
|
74
132
|
end # module
|
75
133
|
end # module
|
@@ -4,28 +4,29 @@ 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
|
|
10
10
|
describe Dendroid::GrmAnalysis::DottedItem do
|
11
11
|
let(:num_symb) { Dendroid::Syntax::Terminal.new('NUMBER') }
|
12
12
|
let(:plus_symb) { Dendroid::Syntax::Terminal.new('PLUS') }
|
13
|
+
let(:minus_symb) { Dendroid::Syntax::Terminal.new('MINUS') }
|
13
14
|
let(:expr_symb) { Dendroid::Syntax::NonTerminal.new('expression') }
|
14
|
-
let(:
|
15
|
+
let(:rhs1) { Dendroid::Syntax::SymbolSeq.new([num_symb, plus_symb, num_symb]) }
|
16
|
+
let(:rhs2) { Dendroid::Syntax::SymbolSeq.new([num_symb, minus_symb, num_symb]) }
|
15
17
|
let(:empty_body) { Dendroid::Syntax::SymbolSeq.new([]) }
|
16
|
-
let(:
|
17
|
-
let(:empty_prod) { Dendroid::Syntax::Production.new(expr_symb, empty_body) }
|
18
|
+
let(:choice) { Dendroid::Syntax::Rule.new(expr_symb, [rhs1, rhs2, empty_body]) }
|
18
19
|
|
19
|
-
# Implements a dotted item: expression => NUMBER .
|
20
|
-
subject { described_class.new(
|
20
|
+
# Implements a dotted item: expression => NUMBER . MINUS NUMBER
|
21
|
+
subject { described_class.new(choice, 1, 1) }
|
21
22
|
|
22
23
|
context 'Initialization:' do
|
23
24
|
it 'is initialized with a production and a dot position' do
|
24
|
-
expect { described_class.new(
|
25
|
+
expect { described_class.new(choice, 1, 1) }.not_to raise_error
|
25
26
|
end
|
26
27
|
|
27
28
|
it 'knows its related production' do
|
28
|
-
expect(subject.rule).to eq(
|
29
|
+
expect(subject.rule).to eq(choice)
|
29
30
|
end
|
30
31
|
|
31
32
|
it 'knows its position' do
|
@@ -35,67 +36,78 @@ describe Dendroid::GrmAnalysis::DottedItem do
|
|
35
36
|
|
36
37
|
context 'Provided services:' do
|
37
38
|
it 'renders a String representation of itself' do
|
38
|
-
expect(subject.to_s).to eq('expression => NUMBER .
|
39
|
+
expect(subject.to_s).to eq('expression => NUMBER . MINUS NUMBER')
|
39
40
|
end
|
40
41
|
|
41
42
|
it 'knows its state' do
|
42
|
-
expect(described_class.new(
|
43
|
-
expect(described_class.new(
|
44
|
-
expect(described_class.new(
|
43
|
+
expect(described_class.new(choice, 0, 1).state).to eq(:initial)
|
44
|
+
expect(described_class.new(choice, 1, 1).state).to eq(:partial)
|
45
|
+
expect(described_class.new(choice, 3, 1).state).to eq(:completed)
|
45
46
|
|
46
|
-
# Case of an empty
|
47
|
-
expect(described_class.new(
|
47
|
+
# Case of an empty alternative
|
48
|
+
expect(described_class.new(choice, 0, 2).state).to eq(:initial_and_completed)
|
48
49
|
end
|
49
50
|
|
50
51
|
it 'knows whether it is in the initial position' do
|
51
|
-
expect(described_class.new(
|
52
|
-
expect(described_class.new(
|
53
|
-
expect(described_class.new(
|
52
|
+
expect(described_class.new(choice, 0, 0)).to be_initial_pos
|
53
|
+
expect(described_class.new(choice, 2, 0)).not_to be_initial_pos
|
54
|
+
expect(described_class.new(choice, 3, 0)).not_to be_initial_pos
|
54
55
|
|
55
|
-
# Case of an empty
|
56
|
-
expect(described_class.new(
|
56
|
+
# Case of an empty alternative
|
57
|
+
expect(described_class.new(choice, 0, 2)).to be_initial_pos
|
57
58
|
end
|
58
59
|
|
59
60
|
it 'knows whether it is in the final position' do
|
60
|
-
expect(described_class.new(
|
61
|
-
expect(described_class.new(
|
62
|
-
expect(described_class.new(
|
63
|
-
expect(described_class.new(
|
61
|
+
expect(described_class.new(choice, 0, 1)).not_to be_final_pos
|
62
|
+
expect(described_class.new(choice, 2, 1)).not_to be_final_pos
|
63
|
+
expect(described_class.new(choice, 3, 1)).to be_final_pos
|
64
|
+
expect(described_class.new(choice, 3,1)).to be_completed
|
64
65
|
|
65
|
-
# Case of an empty
|
66
|
-
expect(described_class.new(
|
66
|
+
# Case of an empty alternative
|
67
|
+
expect(described_class.new(choice, 0, 2)).to be_final_pos
|
67
68
|
end
|
68
69
|
|
69
70
|
it 'knows whether it is in an intermediate position' do
|
70
|
-
expect(described_class.new(
|
71
|
-
expect(described_class.new(
|
72
|
-
expect(described_class.new(
|
71
|
+
expect(described_class.new(choice, 0, 0)).not_to be_intermediate_pos
|
72
|
+
expect(described_class.new(choice, 2, 0)).to be_intermediate_pos
|
73
|
+
expect(described_class.new(choice, 3, 0)).not_to be_intermediate_pos
|
73
74
|
|
74
|
-
# Case of an empty
|
75
|
-
expect(described_class.new(
|
75
|
+
# Case of an empty alternative
|
76
|
+
expect(described_class.new(choice, 0, 2)).not_to be_intermediate_pos
|
76
77
|
end
|
77
78
|
|
78
79
|
it 'knows the symbol after the dot (if any)' do
|
79
|
-
expect(described_class.new(
|
80
|
-
expect(described_class.new(
|
81
|
-
expect(described_class.new(
|
82
|
-
expect(described_class.new(
|
80
|
+
expect(described_class.new(choice, 0, 1).next_symbol.name).to eq(:NUMBER)
|
81
|
+
expect(described_class.new(choice, 1, 1).next_symbol.name).to eq(:MINUS)
|
82
|
+
expect(described_class.new(choice, 2, 1).next_symbol.name).to eq(:NUMBER)
|
83
|
+
expect(described_class.new(choice, 3, 1).next_symbol).to be_nil
|
83
84
|
|
84
|
-
# Case of an empty
|
85
|
-
expect(described_class.new(
|
85
|
+
# Case of an empty alternative
|
86
|
+
expect(described_class.new(choice, 0, 2).next_symbol).to be_nil
|
86
87
|
end
|
87
88
|
|
88
|
-
it '
|
89
|
-
expect(described_class.new(
|
90
|
-
expect(described_class.new(
|
91
|
-
expect(described_class.new(
|
92
|
-
expect(described_class.new(
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
89
|
+
it 'knows the symbol before the dot (if any)' do
|
90
|
+
expect(described_class.new(choice, 0, 1).prev_symbol).to be_nil
|
91
|
+
expect(described_class.new(choice, 1, 1).prev_symbol.name).to eq(:NUMBER)
|
92
|
+
expect(described_class.new(choice, 2, 1).prev_symbol.name).to eq(:MINUS)
|
93
|
+
expect(described_class.new(choice, 3, 1).prev_symbol.name).to eq(:NUMBER)
|
94
|
+
|
95
|
+
# Case of an empty alternative
|
96
|
+
expect(described_class.new(choice, 0, 1).prev_symbol).to be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'can compare a given symbol to the one expected' do
|
100
|
+
expect(described_class.new(choice, 0, 1)).to be_expecting(num_symb)
|
101
|
+
expect(described_class.new(choice, 0, 1)).not_to be_expecting(plus_symb)
|
102
|
+
expect(described_class.new(choice, 1, 0)).to be_expecting(plus_symb)
|
103
|
+
expect(described_class.new(choice, 1, 1)).to be_expecting(minus_symb)
|
104
|
+
expect(described_class.new(choice, 2, 0)).to be_expecting(num_symb)
|
105
|
+
expect(described_class.new(choice, 3, 1)).not_to be_expecting(num_symb)
|
106
|
+
expect(described_class.new(choice, 3, 1)).not_to be_expecting(plus_symb)
|
107
|
+
|
108
|
+
# Case of an empty alternative
|
109
|
+
expect(described_class.new(choice, 0, 2)).not_to be_expecting(num_symb)
|
110
|
+
expect(described_class.new(choice, 0, 2)).not_to be_expecting(plus_symb)
|
99
111
|
end
|
100
112
|
end # context
|
101
113
|
end # describe
|
@@ -4,11 +4,10 @@ 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
|
-
|
9
|
-
require_relative '..\..\..\lib\dendroid\grm_analysis\choice_items'
|
7
|
+
require_relative '..\..\..\lib\dendroid\syntax\rule'
|
8
|
+
require_relative '..\..\..\lib\dendroid\grm_analysis\rule_items'
|
10
9
|
|
11
|
-
describe Dendroid::GrmAnalysis::
|
10
|
+
describe Dendroid::GrmAnalysis::RuleItems do
|
12
11
|
let(:num_symb) { Dendroid::Syntax::Terminal.new('NUMBER') }
|
13
12
|
let(:plus_symb) { Dendroid::Syntax::Terminal.new('PLUS') }
|
14
13
|
let(:star_symb) { Dendroid::Syntax::Terminal.new('STAR') }
|
@@ -17,8 +16,8 @@ describe Dendroid::GrmAnalysis::ChoiceItems do
|
|
17
16
|
let(:alt2) { Dendroid::Syntax::SymbolSeq.new([num_symb, star_symb, num_symb]) }
|
18
17
|
let(:alt3) { Dendroid::Syntax::SymbolSeq.new([]) }
|
19
18
|
subject do
|
20
|
-
choice = Dendroid::Syntax::
|
21
|
-
choice.extend(Dendroid::GrmAnalysis::
|
19
|
+
choice = Dendroid::Syntax::Rule.new(expr_symb, [alt1, alt2, alt3])
|
20
|
+
choice.extend(Dendroid::GrmAnalysis::RuleItems)
|
22
21
|
choice.build_items
|
23
22
|
choice
|
24
23
|
end
|