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
@@ -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 :nonterm2productions
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 @rules.nil?
45
- @rules = []
46
- @nonterm2productions = {}
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
- # TODO: add test for duplicate productions
49
- if nonterm2productions[rule.head]&.include? rule
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
- nonterm2productions[rule.head] = [] unless nonterm2productions.include? rule.head
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
- # rubocop: disable Metrics/AbcSize
77
- # rubocop: disable Metrics/BlockNesting
78
- # rubocop: disable Metrics/MethodLength
79
- # rubocop: disable Metrics/PerceivedComplexity
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
- check_ok = all_terminals == terms_in_rhs
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
- check_ok
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
- check_ok = all_nonterminals == symbs_in_lhs
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
- check_ok
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
- begin
182
+ loop do
174
183
  reachable_sym = backlog.pop
175
- prods = nonterm2productions[reachable_sym]
176
- prods.each do |prd|
177
- prd.rhs_symbols.each do |member|
178
- unless member.terminal? || set_reachable.include?(member)
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
- end until backlog.empty?
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
- nonterm2productions.each_pair do |sym, prods|
218
- if prods.any?(&:empty?)
224
+ nonterm2production.each_pair do |sym, prod|
225
+ if prod.empty?
219
226
  sym.nullable = nullable_found = true
220
227
  else
221
- sym2seqs[sym] = prods.map(&:rhs).flatten
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
- begin
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
- end until backlog.empty? || seqs_done.empty?
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 if sym.nullable.nil?
278
+ sym.nullable = false
266
279
  end
267
280
  end
268
281
  # rubocop: enable Metrics/AbcSize
269
- # rubocop: enable Metrics/BlockNesting
270
- # rubocop: enable Metrics/MethodLength
282
+ # rubocop: enable Metrics/CyclomaticComplexity
271
283
  # rubocop: enable Metrics/PerceivedComplexity
272
284
  end # class
273
285
  end # module
@@ -2,28 +2,60 @@
2
2
 
3
3
  module Dendroid
4
4
  module Syntax
5
- # In a context-free grammar, a rule has its left-hand side (LHS)
6
- # that consists solely of one non-terminal symbol.
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
- # Create a Rule instance.
18
- # @param lhs [Dendroid::Syntax::NonTerminal] The left-hand side of the rule.
19
- def initialize(lhs)
20
- @head = valid_head(lhs)
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 rule
23
+ # Return the text representation of the choice
24
24
  # @return [String]
25
25
  def to_s
26
- head.to_s
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\production'
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(:rhs) { Dendroid::Syntax::SymbolSeq.new([num_symb, plus_symb, num_symb]) }
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(:prod) { Dendroid::Syntax::Production.new(expr_symb, rhs) }
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 . PLUS NUMBER
20
- subject { described_class.new(prod, 1) }
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(prod, 1) }.not_to raise_error
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(prod)
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 . PLUS 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(prod, 0).state).to eq(:initial)
43
- expect(described_class.new(prod, 1).state).to eq(:partial)
44
- expect(described_class.new(prod, 3).state).to eq(:completed)
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 production
47
- expect(described_class.new(empty_prod, 0).state).to eq(:initial_and_completed)
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(prod, 0)).to be_initial_pos
52
- expect(described_class.new(prod, 2)).not_to be_initial_pos
53
- expect(described_class.new(prod, 3)).not_to be_initial_pos
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 production
56
- expect(described_class.new(empty_prod, 0)).to be_initial_pos
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(prod, 0)).not_to be_final_pos
61
- expect(described_class.new(prod, 2)).not_to be_final_pos
62
- expect(described_class.new(prod, 3)).to be_final_pos
63
- expect(described_class.new(prod, 3)).to be_completed
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 production
66
- expect(described_class.new(empty_prod, 0)).to be_final_pos
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(prod, 0)).not_to be_intermediate_pos
71
- expect(described_class.new(prod, 2)).to be_intermediate_pos
72
- expect(described_class.new(prod, 3)).not_to be_intermediate_pos
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 production
75
- expect(described_class.new(empty_prod, 0)).not_to be_intermediate_pos
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(prod, 0).next_symbol.name).to eq(:NUMBER)
80
- expect(described_class.new(prod, 1).next_symbol.name).to eq(:PLUS)
81
- expect(described_class.new(prod, 2).next_symbol.name).to eq(:NUMBER)
82
- expect(described_class.new(prod, 3).next_symbol).to be_nil
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 production
85
- expect(described_class.new(empty_prod, 0).next_symbol).to be_nil
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 'can compare a given symbol to the expected one' do
89
- expect(described_class.new(prod, 0)).to be_expecting(num_symb)
90
- expect(described_class.new(prod, 0)).not_to be_expecting(plus_symb)
91
- expect(described_class.new(prod, 1)).to be_expecting(plus_symb)
92
- expect(described_class.new(prod, 2)).to be_expecting(num_symb)
93
- expect(described_class.new(prod, 3)).not_to be_expecting(num_symb)
94
- expect(described_class.new(prod, 3)).not_to be_expecting(plus_symb)
95
-
96
- # Case of an empty production
97
- expect(described_class.new(empty_prod, 0)).not_to be_expecting(num_symb)
98
- expect(described_class.new(empty_prod, 0)).not_to be_expecting(plus_symb)
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\choice'
8
- # require_relative '..\..\..\lib\dendroid\grm_analysis\alternative_item'
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::ChoiceItems do
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::Choice.new(expr_symb, [alt1, alt2, alt3])
21
- choice.extend(Dendroid::GrmAnalysis::ChoiceItems)
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