dendroid 0.1.00 → 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 +3 -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 +2 -4
  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 +8 -0
  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 +33 -20
  24. data/lib/dendroid/syntax/grammar.rb +1 -1
  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 +16 -21
  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
@@ -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
 
@@ -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)
@@ -141,10 +136,10 @@ describe Dendroid::Syntax::Grammar do
141
136
  nterm_e = build_nonterminal('E')
142
137
 
143
138
  instance = described_class.new([terminal_a])
144
- instance.add_rule(build_production(nterm_s_prime, [nterm_s]))
145
- 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]]))
146
141
  instance.add_rule(build_choice(nterm_a, [[terminal_a], [nterm_e]]))
147
- instance.add_rule(build_production(nterm_e, []))
142
+ instance.add_rule(build_choice(nterm_e, [[]]))
148
143
 
149
144
  instance.complete!
150
145
  all_nonterminals = subject.symbols.reject(&:terminal?)
@@ -160,7 +155,7 @@ describe Dendroid::Syntax::Grammar do
160
155
  # Let add's unreachable symbols
161
156
  zed_symb = build_nonterminal('Z')
162
157
  question_symb = build_nonterminal('?')
163
- 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
164
159
  subject.add_rule(bad_rule)
165
160
  unreachable = subject.send(:unreachable_symbols)
166
161
  expect(unreachable).not_to be_empty
@@ -175,7 +170,7 @@ describe Dendroid::Syntax::Grammar do
175
170
  expect(t_symb).to be_productive
176
171
  expect(p_symb).to be_productive
177
172
 
178
- # Grammar with non-productive symbols
173
+ # # Grammar with non-productive symbols
179
174
  term_a = build_terminal('a')
180
175
  term_b = build_terminal('b')
181
176
  term_c = build_terminal('c')
@@ -191,12 +186,12 @@ describe Dendroid::Syntax::Grammar do
191
186
  nterm_S = build_nonterminal('S')
192
187
  instance = described_class.new([term_a, term_b, term_c, term_d, term_e, term_f])
193
188
  instance.add_rule(build_choice(nterm_S, [[nterm_A, nterm_B], [nterm_D, nterm_E]]))
194
- instance.add_rule(build_production(nterm_A, [term_a]))
195
- instance.add_rule(build_production(nterm_B, [term_b, nterm_C]))
196
- instance.add_rule(build_production(nterm_C, [term_c]))
197
- instance.add_rule(build_production(nterm_D, [term_d, nterm_F]))
198
- instance.add_rule(build_production(nterm_E, [term_e]))
199
- 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]]))
200
195
  nonproductive = instance.send(:mark_non_productive_symbols)
201
196
  expect(nonproductive).not_to be_empty
202
197
  expect(nonproductive).to eq([nterm_D, nterm_F])
@@ -3,28 +3,77 @@
3
3
  require_relative '..\..\spec_helper'
4
4
  require_relative '..\..\..\lib\dendroid\syntax\terminal'
5
5
  require_relative '..\..\..\lib\dendroid\syntax\non_terminal'
6
+ require_relative '..\..\..\lib\dendroid\syntax\symbol_seq'
6
7
  require_relative '..\..\..\lib\dendroid\syntax\rule'
7
8
 
8
9
  describe Dendroid::Syntax::Rule do
9
10
  let(:num_symb) { Dendroid::Syntax::Terminal.new('NUMBER') }
11
+ let(:plus_symb) { Dendroid::Syntax::Terminal.new('PLUS') }
12
+ let(:minus_symb) { Dendroid::Syntax::Terminal.new('MINUS') }
10
13
  let(:expr_symb) { Dendroid::Syntax::NonTerminal.new('expression') }
14
+ let(:foo_symb) { Dendroid::Syntax::NonTerminal.new('foo') }
15
+ let(:alt1) { Dendroid::Syntax::SymbolSeq.new([num_symb, plus_symb, num_symb]) }
16
+ let(:alt2) { Dendroid::Syntax::SymbolSeq.new([num_symb, minus_symb, num_symb]) }
17
+ let(:empty_body) { Dendroid::Syntax::SymbolSeq.new([]) }
11
18
 
12
- subject { described_class.new(expr_symb) }
19
+ # Implements a choice rule:
20
+ # expression => NUMBER PLUS NUMBER
21
+ # | NUMBER MINUS NUMBER
22
+ # | epsilon
23
+ subject { described_class.new(expr_symb, [alt1, alt2, empty_body]) }
13
24
 
14
25
  context 'Initialization:' do
15
- it 'is initialized with a non-terminal' do
16
- expect { described_class.new(expr_symb) }.not_to raise_error
26
+ it 'is initialized with a head and alternatives' do
27
+ expect { described_class.new(expr_symb, [alt1, alt2, empty_body]) }.not_to raise_error
17
28
  end
18
29
 
19
30
  it 'knows its head (aka lhs)' do
20
31
  expect(subject.head).to eq(expr_symb)
21
32
  end
33
+
34
+ it 'knows its alternatives' do
35
+ expect(subject.alternatives).to eq([alt1, alt2, empty_body])
36
+ end
37
+
38
+ it 'renders a String representation of itself' do
39
+ expectation = 'expression => NUMBER PLUS NUMBER | NUMBER MINUS NUMBER | '
40
+ expect(subject.to_s).to eq(expectation)
41
+ end
42
+ end # context
43
+
44
+ context 'Provided services:' do
45
+ it 'knows its terminal members' do
46
+ expect(subject.terminals).to eq([num_symb, plus_symb, minus_symb])
47
+ end
48
+
49
+ it 'knows its non-terminal members' do
50
+ expect(subject.nonterminals).to be_empty
51
+
52
+ my_alt1 = Dendroid::Syntax::SymbolSeq.new([expr_symb, plus_symb, expr_symb])
53
+ my_alt2 = Dendroid::Syntax::SymbolSeq.new([foo_symb, minus_symb, expr_symb])
54
+ instance = described_class.new(foo_symb, [my_alt1, my_alt2])
55
+ expect(instance.nonterminals).to eq([expr_symb, foo_symb])
56
+ end
22
57
  end # context
23
58
 
24
59
  context 'Errors:' do
25
- it 'fails when initialized with a terminal' do
26
- msg = "Terminal symbol 'NUMBER' may not be on left-side of a rule."
27
- expect { described_class.new(num_symb) }.to raise_error(StandardError, msg)
60
+ context 'Errors:' do
61
+ it 'fails when initialized with a terminal' do
62
+ msg = "Terminal symbol 'NUMBER' may not be on left-side of a rule."
63
+ expect { described_class.new(num_symb, []) }.to raise_error(StandardError, msg)
64
+ end
65
+ end
66
+
67
+ it 'fails when initialized with one alternative only' do
68
+ err = StandardError
69
+ err_msg = 'The choice for `expression` must have at least one alternative.'
70
+ expect { described_class.new(expr_symb, []) }.to raise_error(err, err_msg)
28
71
  end
29
- end
72
+
73
+ it 'fails in presence of duplicate rhs' do
74
+ err = StandardError
75
+ err_msg = 'Duplicate alternatives: expression => NUMBER PLUS NUMBER'
76
+ expect { described_class.new(expr_symb, [alt1, alt2, alt1]) }.to raise_error(err, err_msg)
77
+ end
78
+ end # context
30
79
  end # describe
data/version.txt CHANGED
@@ -1 +1 @@
1
- 0.1.00
1
+ 0.2.00
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dendroid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.00
4
+ version: 0.2.00
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-03 00:00:00.000000000 Z
11
+ date: 2023-12-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: WIP. A Ruby implementation of an Earley parser
14
14
  email: famished.tiger@yahoo.com
@@ -24,47 +24,54 @@ files:
24
24
  - bin/dendroid
25
25
  - dendroid.gemspec
26
26
  - lib/dendroid.rb
27
- - lib/dendroid/grm_analysis/alternative_item.rb
28
- - lib/dendroid/grm_analysis/choice_items.rb
27
+ - lib/dendroid/formatters/ascii_tree.rb
28
+ - lib/dendroid/formatters/base_formatter.rb
29
+ - lib/dendroid/formatters/bracket_notation.rb
29
30
  - lib/dendroid/grm_analysis/dotted_item.rb
30
31
  - lib/dendroid/grm_analysis/grm_analyzer.rb
31
- - lib/dendroid/grm_analysis/production_items.rb
32
+ - lib/dendroid/grm_analysis/rule_items.rb
32
33
  - lib/dendroid/grm_dsl/base_grm_builder.rb
33
34
  - lib/dendroid/lexical/literal.rb
34
35
  - lib/dendroid/lexical/token.rb
35
36
  - lib/dendroid/lexical/token_position.rb
37
+ - lib/dendroid/parsing/and_node.rb
38
+ - lib/dendroid/parsing/chart_walker.rb
39
+ - lib/dendroid/parsing/composite_parse_node.rb
40
+ - lib/dendroid/parsing/empty_rule_node.rb
41
+ - lib/dendroid/parsing/or_node.rb
42
+ - lib/dendroid/parsing/parse_node.rb
43
+ - lib/dendroid/parsing/parse_tree_visitor.rb
44
+ - lib/dendroid/parsing/parser.rb
45
+ - lib/dendroid/parsing/terminal_node.rb
46
+ - lib/dendroid/parsing/walk_progress.rb
36
47
  - lib/dendroid/recognizer/chart.rb
37
48
  - lib/dendroid/recognizer/e_item.rb
38
49
  - lib/dendroid/recognizer/item_set.rb
39
50
  - lib/dendroid/recognizer/recognizer.rb
40
- - lib/dendroid/syntax/choice.rb
41
51
  - lib/dendroid/syntax/grammar.rb
42
52
  - lib/dendroid/syntax/grm_symbol.rb
43
53
  - lib/dendroid/syntax/non_terminal.rb
44
- - lib/dendroid/syntax/production.rb
45
54
  - lib/dendroid/syntax/rule.rb
46
55
  - lib/dendroid/syntax/symbol_seq.rb
47
56
  - lib/dendroid/syntax/terminal.rb
48
57
  - lib/dendroid/utils/base_tokenizer.rb
49
- - spec/dendroid/grm_analysis/alternative_item_spec.rb
50
- - spec/dendroid/grm_analysis/choice_items_spec.rb
51
58
  - spec/dendroid/grm_analysis/dotted_item_spec.rb
52
59
  - spec/dendroid/grm_analysis/grm_analyzer_spec.rb
53
- - spec/dendroid/grm_analysis/production_items_spec.rb
60
+ - spec/dendroid/grm_analysis/rule_items_spec.rb
54
61
  - spec/dendroid/grm_dsl/base_grm_builder_spec.rb
55
62
  - spec/dendroid/lexical/literal_spec.rb
56
63
  - spec/dendroid/lexical/token_position_spec.rb
57
64
  - spec/dendroid/lexical/token_spec.rb
65
+ - spec/dendroid/parsing/chart_walker_spec.rb
66
+ - spec/dendroid/parsing/terminal_node_spec.rb
58
67
  - spec/dendroid/recognizer/chart_spec.rb
59
68
  - spec/dendroid/recognizer/e_item_spec.rb
60
69
  - spec/dendroid/recognizer/item_set_spec.rb
61
70
  - spec/dendroid/recognizer/recognizer_spec.rb
62
71
  - spec/dendroid/support/sample_grammars.rb
63
- - spec/dendroid/syntax/choice_spec.rb
64
72
  - spec/dendroid/syntax/grammar_spec.rb
65
73
  - spec/dendroid/syntax/grm_symbol_spec.rb
66
74
  - spec/dendroid/syntax/non_terminal_spec.rb
67
- - spec/dendroid/syntax/production_spec.rb
68
75
  - spec/dendroid/syntax/rule_spec.rb
69
76
  - spec/dendroid/syntax/symbol_seq_spec.rb
70
77
  - spec/dendroid/syntax/terminal_spec.rb
@@ -90,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
97
  - !ruby/object:Gem::Version
91
98
  version: '0'
92
99
  requirements: []
93
- rubygems_version: 3.3.7
100
+ rubygems_version: 3.4.10
94
101
  signing_key:
95
102
  specification_version: 4
96
103
  summary: WIP. A Ruby implementation of an Earley parser
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'dotted_item'
4
-
5
- module Dendroid
6
- module GrmAnalysis
7
- # A specialization of DottedItem specific for Choice (rule)
8
- class AlternativeItem < DottedItem
9
- # @return [Integer] the alternative number
10
- attr_reader :alt_index
11
-
12
- # Constructor.
13
- # @param aChoice [Dendroid::Syntax::Choice]
14
- # @param aPosition [Integer] Position of the dot in rhs of production.
15
- # @param index [Integer] the rank of the alternative at hand
16
- def initialize(aChoice, aPosition, index)
17
- @alt_index = index
18
- super(aChoice, aPosition)
19
- end
20
-
21
- # Return a String representation of the alternative item.
22
- # @return [String]
23
- def to_s
24
- rhs_names = rule.alternatives[alt_index].members.map(&:to_s)
25
- dotted_rhs = rhs_names.insert(position, '.')
26
- "#{rule.head} => #{dotted_rhs.join(' ')}"
27
- end
28
-
29
- # Indicate whether the rhs of the alternative is empty
30
- # @return [Boolean]
31
- def empty?
32
- rule.alternatives[alt_index].empty?
33
- end
34
-
35
- # Indicate whether the dot is at the start of rhs
36
- # @return [Boolean]
37
- def final_pos?
38
- empty? || position == rule.alternatives[alt_index].size
39
- end
40
-
41
- alias completed? final_pos?
42
-
43
- # Return the symbol right after the dot (if any)
44
- # @return [Dendroid::Syntax::GrmSymbol, NilClass]
45
- def next_symbol
46
- return nil if empty? || completed?
47
-
48
- rule.alternatives[alt_index].members[position]
49
- end
50
-
51
- # Test for equality with another dotted item.
52
- # Two dotted items are equal if they refer to the same rule and
53
- # have both the same rhs and dot positions.
54
- # @return [Boolean]
55
- def ==(other)
56
- return true if eql?(other)
57
-
58
- (position == other.position) && rule.eql?(other.rule) && (alt_index == other.alt_index)
59
- end
60
-
61
- private
62
-
63
- def valid_position(aPosition)
64
- raise StandardError if aPosition.negative? || aPosition > rule.alternatives[alt_index].size
65
-
66
- aPosition
67
- end
68
- end # class
69
- end # module
70
- end # module
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'dotted_item'
4
-
5
- module Dendroid
6
- module GrmAnalysis
7
- # Mix-in module for extending the Dendroid::Syntax::Production class
8
- # with dotted items manipulation methods and an attribute named `items`.
9
- module ProductionItems
10
- # Build the dotted items for this production and assign them
11
- # to the `items` attributes
12
- # @return [Array<GrmAnalysis::DottedItem>]
13
- def build_items
14
- @items = if empty?
15
- [DottedItem.new(self, 0)]
16
- else
17
- (0..body.size).reduce([]) do |result, pos|
18
- result << GrmAnalysis::DottedItem.new(self, pos)
19
- end
20
- end
21
- end
22
-
23
- # Read accessor for the `items` attribute.
24
- # Return the dotted items for this production
25
- # @return [Array<GrmAnalysis::DottedItem>]
26
- def items
27
- @items
28
- end
29
-
30
- # Return the predicted item (i.e. the dotted item with the dot at start)
31
- # for this production.
32
- # @return [Array<GrmAnalysis::DottedItem>]
33
- def predicted_items
34
- [@items.first]
35
- end
36
-
37
- # Return the reduce item (i.e. the dotted item with the dot at end)
38
- # for this production.
39
- # @return [Array<GrmAnalysis::DottedItem>]
40
- def reduce_items
41
- [@items.last]
42
- end
43
-
44
- # Return the next item given the provided item.
45
- # In other words, advance the dot by one position.
46
- # @param anItem [GrmAnalysis::DottedItem]
47
- # @return [GrmAnalysis::DottedItem|NilClass]
48
- def next_item(anItem)
49
- return nil if anItem == @items.last
50
-
51
- @items[anItem.position + 1]
52
- end
53
- end # module
54
- end # module
55
- end # module
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'rule'
4
-
5
- module Dendroid
6
- module Syntax
7
- # A specialization of the Rule class.
8
- # A choice is a rule with multiple rhs
9
- class Choice < Rule
10
- # @return [Array<Dendroid::Syntax::SymbolSeq>]
11
- attr_reader :alternatives
12
-
13
- # Create a Choice instance.
14
- # @param lhs [Dendroid::Syntax::NonTerminal] The left-hand side of the rule.
15
- # @param alt [Array<Dendroid::Syntax::SymbolSeq>] the alternatives (each as a sequence of symbols).
16
- def initialize(lhs, alt)
17
- super(lhs)
18
- @alternatives = valid_alternatives(alt)
19
- end
20
-
21
- # Predicate method to check whether the rule has alternatives
22
- # @return [TrueClass]
23
- def choice?
24
- true
25
- end
26
-
27
- # Return the text representation of the choice
28
- # @return [String]
29
- def to_s
30
- "#{head} => #{alternatives.join(' | ')}"
31
- end
32
-
33
- # Predicate method to check whether the choice rule body is productive.
34
- # It is productive when at least of its alternative is productive.
35
- # @return [Boolean]
36
- def productive?
37
- productive_alts = alternatives.select(&:productive?)
38
- return false if productive_alts.empty?
39
-
40
- @productive = Set.new(productive_alts)
41
- head.productive = true
42
- end
43
-
44
- # Predicate method to check whether the rule has at least one empty alternative.
45
- # @return [Boolean]
46
- def empty?
47
- alternatives.any?(&:empty?)
48
- end
49
-
50
- # Returns an array with the symbol sequence of its alternatives
51
- # @return [Array<Dendroid::Syntax::SymbolSeq>]
52
- def rhs
53
- alternatives
54
- end
55
-
56
- # Equality operator
57
- # Two production rules are equal when their head and alternatives are equal.
58
- # @return [Boolean]
59
- def ==(other)
60
- return true if equal?(other)
61
- return false if other.is_a?(Production)
62
-
63
- (head == other.head) && (alternatives == other.alternatives)
64
- end
65
-
66
- private
67
-
68
- def valid_alternatives(alt)
69
- raise StandardError, "Expecting an Array, found a #{rhs.class} instead." unless alt.is_a?(Array)
70
-
71
- if alt.size < 2
72
- # A choice must have at least two alternatives
73
- raise StandardError, "The choice for `#{head}` must have at least two alternatives."
74
- end
75
-
76
- # Verify that each array element is a valid symbol sequence
77
- alt.each { |elem| valid_sequence(elem) }
78
-
79
- # Fail when duplicate rhs found
80
- alt_texts = alt.map(&:to_s)
81
- no_duplicate = alt_texts.uniq
82
- if alt_texts.size > no_duplicate.size
83
- alt_texts.each_with_index do |str, i|
84
- next if str == no_duplicate[i]
85
-
86
- err_msg = "Duplicate alternatives: #{head} => #{alt_texts[i]}"
87
- raise StandardError, err_msg
88
- end
89
- end
90
-
91
- alt
92
- end
93
- end # class
94
- end # module
95
- end # module
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'rule'
4
-
5
- module Dendroid
6
- module Syntax
7
- # A specialization of the Rule class.
8
- # A production is a rule with a single rhs
9
- class Production < Rule
10
- # @return [Dendroid::Syntax::SymbolSeq]
11
- attr_reader :body
12
-
13
- # Create a Production instance.
14
- # @param lhs [Dendroid::Syntax::NonTerminal] The left-hand side of the rule.
15
- # @param rhs [Dendroid::Syntax::SymbolSeq] the sequence of symbols on rhs.
16
- def initialize(lhs, rhs)
17
- super(lhs)
18
- @body = valid_sequence(rhs)
19
- end
20
-
21
- # Predicate method to check whether the rule body (its rhs) is empty.
22
- # @return [Boolean]
23
- def empty?
24
- body.empty?
25
- end
26
-
27
- # Predicate method to check whether the rule has alternatives
28
- # @return [FalseClass]
29
- def choice?
30
- false
31
- end
32
-
33
- # Predicate method to check whether the production rule body is productive.
34
- # It is productive when it is empty or all of its rhs members are productive too.
35
- # @return [Boolean, NilClass]
36
- def productive?
37
- if @productive.nil?
38
- if body.productive?
39
- self.productive = true
40
- else
41
- nil
42
- end
43
- else
44
- @productive
45
- end
46
- end
47
-
48
- # Mark the production rule as non-productive.
49
- def non_productive
50
- self.productive = false
51
- end
52
-
53
- # Return the text representation of the production rule
54
- # @return [String]
55
- def to_s
56
- "#{head} => #{body}"
57
- end
58
-
59
- # Equality operator
60
- # Two production rules are equal when their head and rhs are equal.
61
- # @return [Boolean]
62
- def ==(other)
63
- return true if equal?(other)
64
-
65
- (head == other.head) && (body == other.body)
66
- end
67
-
68
- # Returns an array with the symbol sequence of its rhs
69
- # @return [Array<Dendroid::Syntax::SymbolSeq>]
70
- def rhs
71
- [body]
72
- end
73
-
74
- private
75
-
76
- def productive=(val)
77
- @productive = val
78
- lhs.productive = val
79
- end
80
- end # class
81
- end # module
82
- end # module
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '..\..\spec_helper'
4
- require_relative '..\..\..\lib\dendroid\syntax\terminal'
5
- require_relative '..\..\..\lib\dendroid\syntax\non_terminal'
6
- require_relative '..\..\..\lib\dendroid\syntax\symbol_seq'
7
- require_relative '..\..\..\lib\dendroid\syntax\production'
8
- require_relative '..\..\..\lib\dendroid\grm_analysis\alternative_item'
9
-
10
- describe Dendroid::GrmAnalysis::DottedItem do
11
- # TODO
12
- end # describe
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../spec_helper'
4
- require_relative '../../../lib/dendroid/syntax/terminal'
5
- require_relative '../../../lib/dendroid/syntax/non_terminal'
6
- require_relative '../../../lib/dendroid/syntax/symbol_seq'
7
- require_relative '../../../lib/dendroid/syntax/production'
8
- require_relative '../../../lib/dendroid/grm_analysis/production_items'
9
-
10
- describe Dendroid::GrmAnalysis::ProductionItems do
11
- let(:num_symb) { Dendroid::Syntax::Terminal.new('NUMBER') }
12
- let(:plus_symb) { Dendroid::Syntax::Terminal.new('PLUS') }
13
- let(:expr_symb) { Dendroid::Syntax::NonTerminal.new('expression') }
14
- let(:rhs) { Dendroid::Syntax::SymbolSeq.new([num_symb, plus_symb, num_symb]) }
15
- let(:empty_body) { Dendroid::Syntax::SymbolSeq.new([]) }
16
- let(:prod) { Dendroid::Syntax::Production.new(expr_symb, rhs) }
17
- let(:empty_prod) do
18
- e = Dendroid::Syntax::Production.new(expr_symb, empty_body)
19
- e.extend(Dendroid::GrmAnalysis::ProductionItems)
20
- e.build_items
21
- e
22
- end
23
-
24
- subject do
25
- prod.extend(Dendroid::GrmAnalysis::ProductionItems)
26
- prod.build_items
27
- prod
28
- end
29
-
30
- context 'Methods from mix-in' do
31
- it 'builds items for given non-empty production' do
32
- expect(subject.items.size).to eq(subject.body.size + 1)
33
- subject.items.each_with_index do |item, index|
34
- expect(item.rule).to eq(subject)
35
- expect(item.position).to eq(index)
36
- end
37
- end
38
-
39
- it 'builds the item for given empty production' do
40
- expect(empty_prod.items.size).to eq(1)
41
- expect(empty_prod.items[0].rule).to eq(empty_prod)
42
- expect(empty_prod.items[0].position).to eq(0)
43
- end
44
-
45
- it 'returns the first (predicted) item of the production' do
46
- expect(subject.predicted_items).to eq([subject.items.first])
47
- expect(empty_prod.predicted_items).to eq([empty_prod.items.first])
48
- end
49
-
50
- it 'returns the last (reduce) item of the production' do
51
- expect(subject.reduce_items).to eq([subject.items.last])
52
- expect(empty_prod.reduce_items).to eq([empty_prod.items.first])
53
- end
54
-
55
- # rubocop: disable Style/EachForSimpleLoop
56
- it 'returns the consecutive item to a given one' do
57
- (0..2).each do |pos|
58
- curr_item = subject.items[pos]
59
- next_one = subject.next_item(curr_item)
60
- expect(next_one).to eq(subject.items[pos + 1])
61
- end
62
- expect(subject.next_item(subject.items[-1])).to be_nil
63
-
64
- expect(empty_prod.next_item(empty_prod.items[-1])).to be_nil
65
- end
66
- # rubocop: enable Style/EachForSimpleLoop
67
- end # context
68
- end # describe