dendroid 0.1.00 → 0.2.01

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +6 -0
  4. data/lib/dendroid/formatters/ascii_tree.rb +142 -0
  5. data/lib/dendroid/formatters/base_formatter.rb +24 -0
  6. data/lib/dendroid/formatters/bracket_notation.rb +50 -0
  7. data/lib/dendroid/grm_analysis/dotted_item.rb +45 -30
  8. data/lib/dendroid/grm_analysis/grm_analyzer.rb +10 -4
  9. data/lib/dendroid/grm_analysis/{choice_items.rb → rule_items.rb} +10 -10
  10. data/lib/dendroid/grm_dsl/base_grm_builder.rb +3 -4
  11. data/lib/dendroid/parsing/and_node.rb +54 -0
  12. data/lib/dendroid/parsing/chart_walker.rb +301 -0
  13. data/lib/dendroid/parsing/composite_parse_node.rb +21 -0
  14. data/lib/dendroid/parsing/empty_rule_node.rb +28 -0
  15. data/lib/dendroid/parsing/or_node.rb +46 -0
  16. data/lib/dendroid/parsing/parse_node.rb +26 -0
  17. data/lib/dendroid/parsing/parse_tree_visitor.rb +127 -0
  18. data/lib/dendroid/parsing/parser.rb +185 -0
  19. data/lib/dendroid/parsing/terminal_node.rb +32 -0
  20. data/lib/dendroid/parsing/walk_progress.rb +121 -0
  21. data/lib/dendroid/recognizer/chart.rb +3 -0
  22. data/lib/dendroid/recognizer/e_item.rb +21 -2
  23. data/lib/dendroid/recognizer/item_set.rb +7 -2
  24. data/lib/dendroid/recognizer/recognizer.rb +42 -23
  25. data/lib/dendroid/syntax/grammar.rb +5 -1
  26. data/lib/dendroid/syntax/rule.rb +71 -13
  27. data/spec/dendroid/grm_analysis/dotted_item_spec.rb +59 -47
  28. data/spec/dendroid/grm_analysis/{choice_items_spec.rb → rule_items_spec.rb} +5 -6
  29. data/spec/dendroid/parsing/chart_walker_spec.rb +250 -0
  30. data/spec/dendroid/parsing/terminal_node_spec.rb +36 -0
  31. data/spec/dendroid/recognizer/e_item_spec.rb +5 -5
  32. data/spec/dendroid/recognizer/item_set_spec.rb +16 -8
  33. data/spec/dendroid/recognizer/recognizer_spec.rb +56 -5
  34. data/spec/dendroid/support/sample_grammars.rb +2 -2
  35. data/spec/dendroid/syntax/grammar_spec.rb +16 -21
  36. data/spec/dendroid/syntax/rule_spec.rb +56 -7
  37. data/version.txt +1 -1
  38. metadata +20 -13
  39. data/lib/dendroid/grm_analysis/alternative_item.rb +0 -70
  40. data/lib/dendroid/grm_analysis/production_items.rb +0 -55
  41. data/lib/dendroid/syntax/choice.rb +0 -95
  42. data/lib/dendroid/syntax/production.rb +0 -82
  43. data/spec/dendroid/grm_analysis/alternative_item_spec.rb +0 -12
  44. data/spec/dendroid/grm_analysis/production_items_spec.rb +0 -68
  45. data/spec/dendroid/syntax/choice_spec.rb +0 -68
  46. data/spec/dendroid/syntax/production_spec.rb +0 -92
@@ -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.empty?
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
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../spec_helper'
4
+ require_relative '../support/sample_grammars'
5
+ require_relative '../../../lib/dendroid/recognizer/recognizer'
6
+ require_relative '../../../lib/dendroid/parsing/chart_walker'
7
+
8
+ # require_relative '../grm_dsl/base_grm_builder'
9
+ # require_relative '../utils/base_tokenizer'
10
+ # require_relative '../recognizer/recognizer'
11
+ # require_relative 'chart_walker'
12
+ # require_relative 'parse_tree_visitor'
13
+ # require_relative '../formatters/bracket_notation'
14
+ # require_relative '../formatters/ascii_tree'
15
+
16
+ RSpec.describe Dendroid::Parsing::ChartWalker do
17
+ include SampleGrammars
18
+
19
+ def retrieve_success_item(chart, grammar)
20
+ last_item_set = chart.item_sets.last
21
+ result = nil
22
+ last_item_set.items.reverse_each do |itm|
23
+ if itm.origin.zero? && itm.dotted_item.completed? && itm.dotted_item.rule.lhs == grammar.start_symbol
24
+ result = itm
25
+ break
26
+ end
27
+ end
28
+
29
+ result
30
+ end
31
+
32
+ def recognizer_for(grammar, tokenizer)
33
+ Dendroid::Recognizer::Recognizer.new(grammar, tokenizer)
34
+ end
35
+
36
+ def success_entry(chart, recognizer)
37
+ retrieve_success_item(chart, recognizer.grm_analysis.grammar)
38
+ end
39
+
40
+ # rubocop: disable Naming/VariableNumber
41
+ context 'Parsing non-ambiguous grammars' do
42
+ it 'generates a parse tree for the example from Wikipedia' do
43
+ recognizer = recognizer_for(grammar_l1, tokenizer_l1)
44
+ chart = recognizer.run('2 + 3 * 4')
45
+ walker = described_class.new(chart)
46
+ root = walker.walk(success_entry(chart, recognizer))
47
+
48
+ expect(root.to_s).to eq('p => s [0, 5]')
49
+ expect(root.children.size).to eq(1)
50
+ expect(root.children[-1].to_s).to eq('s => s PLUS m [0, 5]')
51
+ plus_expr = root.children[-1]
52
+ expect(plus_expr.children.size).to eq(3)
53
+ expect(plus_expr.children[0].to_s).to eq('s => m [0, 1]')
54
+ expect(plus_expr.children[1].to_s).to eq('PLUS [1, 2]')
55
+ expect(plus_expr.children[2].to_s).to eq('m => m STAR t [2, 5]')
56
+
57
+ operand_plus = plus_expr.children[0]
58
+ expect(operand_plus.children.size).to eq(1)
59
+ expect(operand_plus.children[0].to_s).to eq('m => t [0, 1]')
60
+ expect(operand_plus.children[0].children.size).to eq(1)
61
+ expect(operand_plus.children[0].children[0].to_s).to eq('t => INTEGER [0, 1]')
62
+ expect(operand_plus.children[0].children[0].children[0].to_s).to eq('INTEGER: 2 [0, 1]')
63
+
64
+ expect(plus_expr.children[1].to_s).to eq('PLUS [1, 2]')
65
+
66
+ star_expr = plus_expr.children[2]
67
+ expect(star_expr.children.size).to eq(3)
68
+ expect(star_expr.children[0].to_s).to eq('m => t [2, 3]')
69
+ expect(star_expr.children[1].to_s).to eq('STAR [3, 4]')
70
+ expect(star_expr.children[2].to_s).to eq('t => INTEGER [4, 5]')
71
+
72
+ operand_star = star_expr.children[0]
73
+ expect(operand_star.children.size).to eq(1)
74
+ expect(operand_star.children[0].to_s).to eq('t => INTEGER [2, 3]')
75
+ expect(operand_star.children[0].children[0].to_s).to eq('INTEGER: 3 [2, 3]')
76
+
77
+ expect(star_expr.children[2].children.size).to eq(1)
78
+ expect(star_expr.children[2].children[0].to_s).to eq('INTEGER: 4 [4, 5]')
79
+ end
80
+
81
+ it 'generates a parse tree for grammar l10 (with left recursive rule)' do
82
+ recognizer = recognizer_for(grammar_l10, tokenizer_l10)
83
+ chart = recognizer.run('a a a a a')
84
+ walker = described_class.new(chart)
85
+ root = walker.walk(success_entry(chart, recognizer))
86
+
87
+ expect(root.to_s).to eq('A => A a [0, 5]')
88
+ expect(root.children.size).to eq(2)
89
+ expect(root.children[0].to_s).to eq('A => A a [0, 4]')
90
+ expect(root.children[1].to_s).to eq('a [4, 5]')
91
+
92
+ expect(root.children[0].children.size).to eq(2)
93
+ expect(root.children[0].children[0].to_s).to eq('A => A a [0, 3]')
94
+ expect(root.children[0].children[1].to_s).to eq('a [3, 4]')
95
+
96
+ grand_child = root.children[0].children[0]
97
+ expect(grand_child.children.size).to eq(2)
98
+ expect(grand_child.children[0].to_s).to eq('A => A a [0, 2]')
99
+ expect(grand_child.children[1].to_s).to eq('a [2, 3]')
100
+
101
+ expect(grand_child.children[0].children.size).to eq(2)
102
+ expect(grand_child.children[0].children[0].to_s).to eq('A => A a [0, 1]')
103
+ expect(grand_child.children[0].children[1].to_s).to eq('a [1, 2]')
104
+
105
+ expect(grand_child.children[0].children[0].children.size).to eq(2)
106
+ expect(grand_child.children[0].children[0].children[0].to_s).to eq('_ [0, 0]')
107
+ expect(grand_child.children[0].children[0].children[1].to_s).to eq('a [0, 1]')
108
+ end
109
+
110
+ it 'generates a parse tree for grammar l11 (with right recursive rule)' do
111
+ recognizer = recognizer_for(grammar_l11, tokenizer_l11)
112
+ chart = recognizer.run('a a a a a')
113
+ walker = described_class.new(chart)
114
+ root = walker.walk(success_entry(chart, recognizer))
115
+
116
+ expect(root.to_s).to eq('A => a A [0, 5]')
117
+ expect(root.children.size).to eq(2)
118
+ expect(root.children[0].to_s).to eq('a [0, 1]')
119
+ expect(root.children[1].to_s).to eq('A => a A [1, 5]')
120
+
121
+ expect(root.children[1].children.size).to eq(2)
122
+ expect(root.children[1].children[0].to_s).to eq('a [1, 2]')
123
+ expect(root.children[1].children[1].to_s).to eq('A => a A [2, 5]')
124
+
125
+ grand_child = root.children[1].children[1]
126
+ expect(grand_child.children.size).to eq(2)
127
+ expect(grand_child.children[0].to_s).to eq('a [2, 3]')
128
+ expect(grand_child.children[1].to_s).to eq('A => a A [3, 5]')
129
+
130
+ expect(grand_child.children[1].children.size).to eq(2)
131
+ expect(grand_child.children[1].children[0].to_s).to eq('a [3, 4]')
132
+ expect(grand_child.children[1].children[1].to_s).to eq('A => a A [4, 5]')
133
+
134
+ expect(grand_child.children[1].children[1].children.size).to eq(2)
135
+ expect(grand_child.children[1].children[1].children[0].to_s).to eq('a [4, 5]')
136
+ expect(grand_child.children[1].children[1].children[1].to_s).to eq('_ [5, 5]')
137
+ end
138
+ end # context
139
+
140
+ context 'Parsing ambiguous grammars' do
141
+ it "generates a parse forest for the G2 grammar that choked Earley's parsing algorithm" do
142
+ recognizer = recognizer_for(grammar_l8, tokenizer_l8)
143
+ chart = recognizer.run('x x x x')
144
+ walker = described_class.new(chart)
145
+ root = walker.walk(success_entry(chart, recognizer))
146
+
147
+ expect(root.to_s).to eq('OR: S [0, 4]')
148
+ expect(root.children.size).to eq(3)
149
+ root.children.each do |child|
150
+ expect(child.children.size).to eq(2)
151
+ expect(child.to_s).to eq('S => S S [0, 4]')
152
+ end
153
+ (a, b, c) = root.children
154
+
155
+ # Test structure of tree a
156
+ (child_a_0, child_a_1) = a.children
157
+ expect(child_a_0.to_s).to eq('S => S S [0, 2]')
158
+ expect(child_a_1.to_s).to eq('S => S S [2, 4]')
159
+ expect(child_a_0.children.size).to eq(2)
160
+ (child_a_0_0, child_a_0_1) = child_a_0.children
161
+ expect(child_a_0_0.to_s).to eq('S => x [0, 1]')
162
+ expect(child_a_0_1.to_s).to eq('S => x [1, 2]')
163
+ expect(child_a_0_0.children[0].to_s).to eq('x [0, 1]')
164
+ expect(child_a_0_1.children[0].to_s).to eq('x [1, 2]')
165
+
166
+ expect(child_a_1.children.size).to eq(2)
167
+ (child_a_1_0, child_a_1_1) = child_a_1.children
168
+ expect(child_a_1_0.to_s).to eq('S => x [2, 3]')
169
+ expect(child_a_1_1.to_s).to eq('S => x [3, 4]')
170
+ expect(child_a_1_0.children[0].to_s).to eq('x [2, 3]')
171
+ expect(child_a_1_1.children[0].to_s).to eq('x [3, 4]')
172
+
173
+ # Test structure of forest b
174
+ (child_b_0, child_b_1) = b.children
175
+ expect(child_b_0.to_s).to eq('OR: S [0, 3]')
176
+ expect(child_b_1.to_s).to eq('S => x [3, 4]')
177
+ expect(child_b_1.equal?(child_a_1_1)).to be_truthy # Sharing
178
+ expect(child_b_0.children.size).to eq(2)
179
+ (child_b_0_0, child_b_0_1) = child_b_0.children
180
+ expect(child_b_0_0.to_s).to eq('S => S S [0, 3]')
181
+ expect(child_b_0_1.to_s).to eq('S => S S [0, 3]')
182
+ expect(child_b_0_0.children.size).to eq(2)
183
+ (child_b_0_0_0, child_b_0_0_1) = child_b_0_0.children
184
+ expect(child_b_0_0_0.to_s).to eq('S => x [0, 1]')
185
+ expect(child_b_0_0_0.equal?(child_a_0_0)).to be_truthy # Sharing
186
+ expect(child_b_0_0_1.to_s).to eq('S => S S [1, 3]')
187
+ expect(child_b_0_0_1.children.size).to eq(2)
188
+ expect(child_b_0_0_1.children[0].to_s).to eq('S => x [1, 2]')
189
+ expect(child_b_0_0_1.children[0].equal?(child_a_0_1)).to be_truthy # Sharing
190
+ expect(child_b_0_0_1.children[1].to_s).to eq('S => x [2, 3]')
191
+ expect(child_b_0_0_1.children[1].equal?(child_a_1_0)).to be_truthy # Sharing
192
+
193
+ expect(child_b_0_1.children.size).to eq(2)
194
+ (child_b_0_1_0, child_b_0_1_1) = child_b_0_1.children
195
+ expect(child_b_0_1_0.to_s).to eq('S => S S [0, 2]')
196
+ expect(child_b_0_1_0.equal?(child_a_0)).to be_truthy # Sharing
197
+ expect(child_b_0_1_1.to_s).to eq('S => x [2, 3]')
198
+ expect(child_b_0_1_1.equal?(child_a_1_0)).to be_truthy # Sharing
199
+
200
+ # Test structure of forest c
201
+ (child_c_0, child_c_1) = c.children
202
+ expect(child_c_0.to_s).to eq('S => x [0, 1]')
203
+ expect(child_c_0.equal?(child_a_0_0)).to be_truthy # Sharing
204
+ expect(child_c_1.to_s).to eq('OR: S [1, 4]')
205
+ expect(child_c_1.children.size).to eq(2)
206
+ (child_c_1_0, child_c_1_1) = child_c_1.children
207
+ expect(child_c_1_0.to_s).to eq('S => S S [1, 4]')
208
+ expect(child_c_1_1.to_s).to eq('S => S S [1, 4]')
209
+ expect(child_c_1_0.children.size).to eq(2)
210
+ (child_c_1_0_0, child_c_1_0_1) = child_c_1_0.children
211
+ expect(child_c_1_0_0.to_s).to eq('S => x [1, 2]')
212
+ expect(child_c_1_0_0.equal?(child_a_0_1)).to be_truthy # Sharing
213
+ expect(child_c_1_0_1.to_s).to eq('S => S S [2, 4]')
214
+ expect(child_c_1_0_1.equal?(child_a_1)).to be_truthy # Sharing
215
+ (child_c_1_1_0, child_c_1_1_1) = child_c_1_1.children
216
+ expect(child_c_1_1_0.to_s).to eq('S => S S [1, 3]')
217
+ expect(child_c_1_1_0.equal?(child_b_0_0_1)).to be_truthy # Sharing
218
+ expect(child_c_1_1_1.to_s).to eq('S => x [3, 4]')
219
+ expect(child_c_1_1_1.equal?(child_b_1)).to be_truthy # Sharing
220
+ end
221
+
222
+ it "generates a parse forest for example 3 in paper 'SPPF-Style Parsing From Earley Recognisers'" do
223
+ recognizer = recognizer_for(grammar_l7, tokenizer_l7)
224
+ chart = recognizer.run('a a')
225
+ walker = described_class.new(chart)
226
+ root = walker.walk(success_entry(chart, recognizer))
227
+
228
+ expect(root.to_s).to eq('OR: S [0, 2]')
229
+ expect(root.children.size).to eq(2)
230
+ root.children.each do |ch|
231
+ expect(ch.to_s).to eq('S => S T [0, 2]')
232
+ expect(ch.children.size).to eq(2)
233
+ end
234
+ (child_0_0, child_0_1) = root.children[0].children
235
+ expect(child_0_0.to_s).to eq('S => a [0, 1]')
236
+ expect(child_0_1.to_s).to eq('T => a B [1, 2]')
237
+ expect(child_0_1.children.size).to eq(2)
238
+ expect(child_0_1.children[0].to_s).to eq('a [1, 2]')
239
+ expect(child_0_1.children[1].to_s).to eq('_ [2, 2]')
240
+
241
+ (child_1_0, child_1_1) = root.children[1].children
242
+ expect(child_1_0.to_s).to eq('S => a [0, 1]')
243
+ expect(child_1_1.to_s).to eq('T => a [1, 2]')
244
+ expect(child_1_0.equal?(child_0_0)).to be_truthy # Sharing
245
+ expect(child_1_1.children[0].to_s).to eq('a [1, 2]')
246
+ expect(child_1_1.children[0].equal?(child_0_1.children[0])).to be_truthy # Sharing
247
+ end
248
+ end # context
249
+ # rubocop: enable Naming/VariableNumber
250
+ end # describe
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../spec_helper'
4
+ require_relative '../../../lib\dendroid/syntax/terminal'
5
+ require_relative '../../../lib/dendroid/lexical/token_position'
6
+ require_relative '../../../lib/dendroid/lexical/literal'
7
+ require_relative '../../../lib/dendroid/parsing/terminal_node'
8
+
9
+ RSpec.describe Dendroid::Parsing::TerminalNode do
10
+ let(:ex_source) { '+' }
11
+ let(:ex_pos) { Dendroid::Lexical::TokenPosition.new(2, 5) }
12
+ let(:ex_terminal) { Dendroid::Syntax::Terminal.new('PLUS') }
13
+ let(:plus_token) { Dendroid::Lexical::Token.new(ex_source, ex_pos, ex_terminal) }
14
+ let(:plus_node) { described_class.new(ex_terminal, plus_token, 3) }
15
+
16
+ let(:int_source) { '2' }
17
+ let(:int_symbol) { Dendroid::Syntax::Terminal.new('INTEGER') }
18
+ let(:int_token) { Dendroid::Lexical::Literal.new(int_source, ex_pos, int_symbol, 2) }
19
+ let(:int_node) { described_class.new(int_symbol, int_token, 5) }
20
+
21
+ context 'Initialization:' do
22
+ it 'should be initialized with a symbol, terminal and a rank' do
23
+ expect { described_class.new(ex_terminal, plus_token, 3) }.not_to raise_error
24
+ end
25
+ end
26
+
27
+ context 'provided services' do
28
+ it 'renders a String representation of itself' do
29
+ expect(plus_node.to_s).to eq('PLUS [3, 4]')
30
+ end
31
+
32
+ it 'renders also the token value (if any)' do
33
+ expect(int_node.to_s).to eq('INTEGER: 2 [5, 6]')
34
+ end
35
+ end
36
+ end
@@ -4,7 +4,7 @@ require_relative '../../spec_helper'
4
4
  require_relative '../../../lib/dendroid/syntax/terminal'
5
5
  require_relative '../../../lib/dendroid/syntax/non_terminal'
6
6
  require_relative '../../../lib/dendroid/syntax/symbol_seq'
7
- require_relative '../../../lib/dendroid/syntax/production'
7
+ require_relative '../../../lib/dendroid/syntax/rule'
8
8
  require_relative '../../../lib/dendroid/grm_analysis/dotted_item'
9
9
  require_relative '../../../lib/dendroid/recognizer/e_item'
10
10
 
@@ -14,10 +14,10 @@ describe Dendroid::Recognizer::EItem do
14
14
  let(:expr_symb) { Dendroid::Syntax::NonTerminal.new('expression') }
15
15
  let(:rhs) { Dendroid::Syntax::SymbolSeq.new([num_symb, plus_symb, num_symb]) }
16
16
  let(:empty_body) { Dendroid::Syntax::SymbolSeq.new([]) }
17
- let(:prod) { Dendroid::Syntax::Production.new(expr_symb, rhs) }
18
- let(:empty_prod) { Dendroid::Syntax::Production.new(expr_symb, empty_body) }
19
- let(:sample_dotted) { Dendroid::GrmAnalysis::DottedItem.new(prod, 1) }
20
- let(:other_dotted) { Dendroid::GrmAnalysis::DottedItem.new(empty_prod, 0) }
17
+ let(:prod) { Dendroid::Syntax::Rule.new(expr_symb, [rhs]) }
18
+ let(:empty_prod) { Dendroid::Syntax::Rule.new(expr_symb, [empty_body]) }
19
+ let(:sample_dotted) { Dendroid::GrmAnalysis::DottedItem.new(prod, 1, 0) }
20
+ let(:other_dotted) { Dendroid::GrmAnalysis::DottedItem.new(empty_prod, 0, 0) }
21
21
  let(:sample_origin) { 3 }
22
22
 
23
23
  subject { described_class.new(sample_dotted, sample_origin) }
@@ -4,7 +4,7 @@ require_relative '../../spec_helper'
4
4
  require_relative '../../../lib/dendroid/syntax/terminal'
5
5
  require_relative '../../../lib/dendroid/syntax/non_terminal'
6
6
  require_relative '../../../lib/dendroid/syntax/symbol_seq'
7
- require_relative '../../../lib/dendroid/syntax/production'
7
+ require_relative '../../../lib/dendroid/syntax/rule'
8
8
  require_relative '../../../lib/dendroid/grm_analysis/dotted_item'
9
9
  require_relative '../../../lib/dendroid/recognizer/e_item'
10
10
  require_relative '../../../lib/dendroid/recognizer/item_set'
@@ -15,11 +15,11 @@ describe Dendroid::Recognizer::ItemSet do
15
15
  let(:expr_symb) { Dendroid::Syntax::NonTerminal.new('expression') }
16
16
  let(:rhs) { Dendroid::Syntax::SymbolSeq.new([num_symb, plus_symb, num_symb]) }
17
17
  let(:empty_body) { Dendroid::Syntax::SymbolSeq.new([]) }
18
- let(:prod) { Dendroid::Syntax::Production.new(expr_symb, rhs) }
19
- let(:empty_prod) { Dendroid::Syntax::Production.new(expr_symb, empty_body) }
20
- let(:sample_dotted) { Dendroid::GrmAnalysis::DottedItem.new(prod, 1) }
18
+ let(:prod) { Dendroid::Syntax::Rule.new(expr_symb, [rhs]) }
19
+ let(:empty_prod) { Dendroid::Syntax::Rule.new(expr_symb, [empty_body]) }
20
+ let(:sample_dotted) { Dendroid::GrmAnalysis::DottedItem.new(prod, 1, 0) }
21
21
  let(:sample_origin) { 3 }
22
- let(:other_dotted) { Dendroid::GrmAnalysis::DottedItem.new(empty_prod, 0) }
22
+ let(:other_dotted) { Dendroid::GrmAnalysis::DottedItem.new(empty_prod, 0, 0) }
23
23
  let(:first_element) { Dendroid::Recognizer::EItem.new(sample_dotted, sample_origin) }
24
24
  let(:second_element) { Dendroid::Recognizer::EItem.new(other_dotted, 5) }
25
25
 
@@ -37,15 +37,23 @@ describe Dendroid::Recognizer::ItemSet do
37
37
 
38
38
  context 'Provided services:' do
39
39
  it 'adds a new element' do
40
- subject.add_item(first_element)
40
+ added = subject.add_item(first_element)
41
41
  expect(subject.size).to eq(1)
42
+ expect(added).to eq(first_element)
42
43
 
43
- # Trying a second time, doesn't change the set
44
+ # Trying a second time with itentical item, doesn't change the set
44
45
  subject.add_item(first_element)
45
46
  expect(subject.size).to eq(1)
46
47
 
47
- subject.add_item(second_element)
48
+ # Trying a third time with equal item, doesn't change the set
49
+ similar = first_element.dup
50
+ added = subject.add_item(similar)
51
+ expect(subject.size).to eq(1)
52
+ expect(added).to eq(first_element)
53
+
54
+ added = subject.add_item(second_element)
48
55
  expect(subject.size).to eq(2)
56
+ expect(added).to eq(second_element)
49
57
  end
50
58
 
51
59
  it 'can render a String representation of itself' do