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
@@ -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.0.12
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.0.12
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-02 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
@@ -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\choice'
8
-
9
- describe Dendroid::Syntax::Choice do
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') }
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([]) }
18
-
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]) }
24
-
25
- context 'Initialization:' do
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
28
- end
29
-
30
- it 'knows its alternatives' do
31
- expect(subject.alternatives).to eq([alt1, alt2, empty_body])
32
- end
33
-
34
- it 'renders a String representation of itself' do
35
- expectation = 'expression => NUMBER PLUS NUMBER | NUMBER MINUS NUMBER | '
36
- expect(subject.to_s).to eq(expectation)
37
- end
38
- end # context
39
-
40
- context 'Provided services:' do
41
- it 'knows its terminal members' do
42
- expect(subject.terminals).to eq([num_symb, plus_symb, minus_symb])
43
- end
44
-
45
- it 'knows its non-terminal members' do
46
- expect(subject.nonterminals).to be_empty
47
-
48
- my_alt1 = Dendroid::Syntax::SymbolSeq.new([expr_symb, plus_symb, expr_symb])
49
- my_alt2 = Dendroid::Syntax::SymbolSeq.new([foo_symb, minus_symb, expr_symb])
50
- instance = described_class.new(foo_symb, [my_alt1, my_alt2])
51
- expect(instance.nonterminals).to eq([expr_symb, foo_symb])
52
- end
53
- end # context
54
-
55
- context 'Errors:' do
56
- it 'fails when initialized with one alternative only' do
57
- err = StandardError
58
- err_msg = 'The choice for `expression` must have at least two alternatives.'
59
- expect { described_class.new(expr_symb, [alt1]) }.to raise_error(err, err_msg)
60
- end
61
-
62
- it 'fails in presence of duplicate rhs' do
63
- err = StandardError
64
- err_msg = 'Duplicate alternatives: expression => NUMBER PLUS NUMBER'
65
- expect { described_class.new(expr_symb, [alt1, alt2, alt1]) }.to raise_error(err, err_msg)
66
- end
67
- end # context
68
- end # describe