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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +6 -0
- data/lib/dendroid/formatters/ascii_tree.rb +142 -0
- data/lib/dendroid/formatters/base_formatter.rb +24 -0
- data/lib/dendroid/formatters/bracket_notation.rb +50 -0
- data/lib/dendroid/grm_analysis/dotted_item.rb +45 -30
- data/lib/dendroid/grm_analysis/grm_analyzer.rb +10 -4
- data/lib/dendroid/grm_analysis/{choice_items.rb → rule_items.rb} +10 -10
- data/lib/dendroid/grm_dsl/base_grm_builder.rb +3 -4
- data/lib/dendroid/parsing/and_node.rb +54 -0
- data/lib/dendroid/parsing/chart_walker.rb +301 -0
- data/lib/dendroid/parsing/composite_parse_node.rb +21 -0
- data/lib/dendroid/parsing/empty_rule_node.rb +28 -0
- data/lib/dendroid/parsing/or_node.rb +46 -0
- data/lib/dendroid/parsing/parse_node.rb +26 -0
- data/lib/dendroid/parsing/parse_tree_visitor.rb +127 -0
- data/lib/dendroid/parsing/parser.rb +185 -0
- data/lib/dendroid/parsing/terminal_node.rb +32 -0
- data/lib/dendroid/parsing/walk_progress.rb +121 -0
- data/lib/dendroid/recognizer/chart.rb +3 -0
- data/lib/dendroid/recognizer/e_item.rb +21 -2
- data/lib/dendroid/recognizer/item_set.rb +7 -2
- data/lib/dendroid/recognizer/recognizer.rb +42 -23
- data/lib/dendroid/syntax/grammar.rb +5 -1
- data/lib/dendroid/syntax/rule.rb +71 -13
- data/spec/dendroid/grm_analysis/dotted_item_spec.rb +59 -47
- data/spec/dendroid/grm_analysis/{choice_items_spec.rb → rule_items_spec.rb} +5 -6
- data/spec/dendroid/parsing/chart_walker_spec.rb +250 -0
- data/spec/dendroid/parsing/terminal_node_spec.rb +36 -0
- data/spec/dendroid/recognizer/e_item_spec.rb +5 -5
- data/spec/dendroid/recognizer/item_set_spec.rb +16 -8
- data/spec/dendroid/recognizer/recognizer_spec.rb +56 -5
- data/spec/dendroid/support/sample_grammars.rb +2 -2
- data/spec/dendroid/syntax/grammar_spec.rb +16 -21
- data/spec/dendroid/syntax/rule_spec.rb +56 -7
- data/version.txt +1 -1
- metadata +20 -13
- data/lib/dendroid/grm_analysis/alternative_item.rb +0 -70
- data/lib/dendroid/grm_analysis/production_items.rb +0 -55
- data/lib/dendroid/syntax/choice.rb +0 -95
- data/lib/dendroid/syntax/production.rb +0 -82
- data/spec/dendroid/grm_analysis/alternative_item_spec.rb +0 -12
- data/spec/dendroid/grm_analysis/production_items_spec.rb +0 -68
- data/spec/dendroid/syntax/choice_spec.rb +0 -68
- data/spec/dendroid/syntax/production_spec.rb +0 -92
data/lib/dendroid/syntax/rule.rb
CHANGED
@@ -2,28 +2,60 @@
|
|
2
2
|
|
3
3
|
module Dendroid
|
4
4
|
module Syntax
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# and the right-hand side (RHS) consists of one or more sequence of symbols.
|
8
|
-
# The symbols in RHS can be either terminal or non-terminal symbols.
|
9
|
-
# The rule stipulates that the LHS is equivalent to the RHS,
|
10
|
-
# in other words every occurrence of the LHS can be substituted to
|
11
|
-
# corresponding RHS.
|
5
|
+
# A specialization of the Rule class.
|
6
|
+
# A choice is a rule with multiple rhs
|
12
7
|
class Rule
|
13
8
|
# @return [Dendroid::Syntax::NonTerminal] The left-hand side of the rule.
|
14
9
|
attr_reader :head
|
15
10
|
alias lhs head
|
16
11
|
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
# @return [Array<Dendroid::Syntax::SymbolSeq>]
|
13
|
+
attr_reader :alternatives
|
14
|
+
|
15
|
+
# Create a Choice instance.
|
16
|
+
# @param theLhs [Dendroid::Syntax::NonTerminal] The left-hand side of the rule.
|
17
|
+
# @param alt [Array<Dendroid::Syntax::SymbolSeq>] the alternatives (each as a sequence of symbols).
|
18
|
+
def initialize(theLhs, alt)
|
19
|
+
@head = valid_head(theLhs)
|
20
|
+
@alternatives = valid_alternatives(alt)
|
21
21
|
end
|
22
22
|
|
23
|
-
# Return the text representation of the
|
23
|
+
# Return the text representation of the choice
|
24
24
|
# @return [String]
|
25
25
|
def to_s
|
26
|
-
head.
|
26
|
+
"#{head} => #{alternatives.join(' | ')}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Predicate method to check whether the choice rule body is productive.
|
30
|
+
# It is productive when at least one of its alternative is productive.
|
31
|
+
# @return [Boolean]
|
32
|
+
def productive?
|
33
|
+
productive_alts = alternatives.select(&:productive?)
|
34
|
+
return false if productive_alts.empty?
|
35
|
+
|
36
|
+
@productive = Set.new(productive_alts)
|
37
|
+
head.productive = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# Predicate method to check whether the rule has at least one empty alternative.
|
41
|
+
# @return [Boolean]
|
42
|
+
def empty?
|
43
|
+
alternatives.any?(&:empty?)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns an array with the symbol sequence of its alternatives
|
47
|
+
# @return [Array<Dendroid::Syntax::SymbolSeq>]
|
48
|
+
def rhs
|
49
|
+
alternatives
|
50
|
+
end
|
51
|
+
|
52
|
+
# Equality operator
|
53
|
+
# Two production rules are equal when their head and alternatives are equal.
|
54
|
+
# @return [Boolean]
|
55
|
+
def ==(other)
|
56
|
+
return true if equal?(other)
|
57
|
+
|
58
|
+
(head == other.head) && (alternatives == other.alternatives)
|
27
59
|
end
|
28
60
|
|
29
61
|
# The set of all grammar symbols that occur in the rhs.
|
@@ -70,6 +102,32 @@ module Dendroid
|
|
70
102
|
|
71
103
|
lhs
|
72
104
|
end
|
105
|
+
|
106
|
+
def valid_alternatives(alt)
|
107
|
+
raise StandardError, "Expecting an Array, found a #{rhs.class} instead." unless alt.is_a?(Array)
|
108
|
+
|
109
|
+
if alt.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\
|
7
|
+
require_relative '..\..\..\lib\dendroid\syntax\rule'
|
8
8
|
require_relative '..\..\..\lib\dendroid\grm_analysis\dotted_item'
|
9
9
|
|
10
10
|
describe Dendroid::GrmAnalysis::DottedItem do
|
11
11
|
let(:num_symb) { Dendroid::Syntax::Terminal.new('NUMBER') }
|
12
12
|
let(:plus_symb) { Dendroid::Syntax::Terminal.new('PLUS') }
|
13
|
+
let(:minus_symb) { Dendroid::Syntax::Terminal.new('MINUS') }
|
13
14
|
let(:expr_symb) { Dendroid::Syntax::NonTerminal.new('expression') }
|
14
|
-
let(:
|
15
|
+
let(:rhs1) { Dendroid::Syntax::SymbolSeq.new([num_symb, plus_symb, num_symb]) }
|
16
|
+
let(:rhs2) { Dendroid::Syntax::SymbolSeq.new([num_symb, minus_symb, num_symb]) }
|
15
17
|
let(:empty_body) { Dendroid::Syntax::SymbolSeq.new([]) }
|
16
|
-
let(:
|
17
|
-
let(:empty_prod) { Dendroid::Syntax::Production.new(expr_symb, empty_body) }
|
18
|
+
let(:choice) { Dendroid::Syntax::Rule.new(expr_symb, [rhs1, rhs2, empty_body]) }
|
18
19
|
|
19
|
-
# Implements a dotted item: expression => NUMBER .
|
20
|
-
subject { described_class.new(
|
20
|
+
# Implements a dotted item: expression => NUMBER . MINUS NUMBER
|
21
|
+
subject { described_class.new(choice, 1, 1) }
|
21
22
|
|
22
23
|
context 'Initialization:' do
|
23
24
|
it 'is initialized with a production and a dot position' do
|
24
|
-
expect { described_class.new(
|
25
|
+
expect { described_class.new(choice, 1, 1) }.not_to raise_error
|
25
26
|
end
|
26
27
|
|
27
28
|
it 'knows its related production' do
|
28
|
-
expect(subject.rule).to eq(
|
29
|
+
expect(subject.rule).to eq(choice)
|
29
30
|
end
|
30
31
|
|
31
32
|
it 'knows its position' do
|
@@ -35,67 +36,78 @@ describe Dendroid::GrmAnalysis::DottedItem do
|
|
35
36
|
|
36
37
|
context 'Provided services:' do
|
37
38
|
it 'renders a String representation of itself' do
|
38
|
-
expect(subject.to_s).to eq('expression => NUMBER .
|
39
|
+
expect(subject.to_s).to eq('expression => NUMBER . MINUS NUMBER')
|
39
40
|
end
|
40
41
|
|
41
42
|
it 'knows its state' do
|
42
|
-
expect(described_class.new(
|
43
|
-
expect(described_class.new(
|
44
|
-
expect(described_class.new(
|
43
|
+
expect(described_class.new(choice, 0, 1).state).to eq(:initial)
|
44
|
+
expect(described_class.new(choice, 1, 1).state).to eq(:partial)
|
45
|
+
expect(described_class.new(choice, 3, 1).state).to eq(:completed)
|
45
46
|
|
46
|
-
# Case of an empty
|
47
|
-
expect(described_class.new(
|
47
|
+
# Case of an empty alternative
|
48
|
+
expect(described_class.new(choice, 0, 2).state).to eq(:initial_and_completed)
|
48
49
|
end
|
49
50
|
|
50
51
|
it 'knows whether it is in the initial position' do
|
51
|
-
expect(described_class.new(
|
52
|
-
expect(described_class.new(
|
53
|
-
expect(described_class.new(
|
52
|
+
expect(described_class.new(choice, 0, 0)).to be_initial_pos
|
53
|
+
expect(described_class.new(choice, 2, 0)).not_to be_initial_pos
|
54
|
+
expect(described_class.new(choice, 3, 0)).not_to be_initial_pos
|
54
55
|
|
55
|
-
# Case of an empty
|
56
|
-
expect(described_class.new(
|
56
|
+
# Case of an empty alternative
|
57
|
+
expect(described_class.new(choice, 0, 2)).to be_initial_pos
|
57
58
|
end
|
58
59
|
|
59
60
|
it 'knows whether it is in the final position' do
|
60
|
-
expect(described_class.new(
|
61
|
-
expect(described_class.new(
|
62
|
-
expect(described_class.new(
|
63
|
-
expect(described_class.new(
|
61
|
+
expect(described_class.new(choice, 0, 1)).not_to be_final_pos
|
62
|
+
expect(described_class.new(choice, 2, 1)).not_to be_final_pos
|
63
|
+
expect(described_class.new(choice, 3, 1)).to be_final_pos
|
64
|
+
expect(described_class.new(choice, 3, 1)).to be_completed
|
64
65
|
|
65
|
-
# Case of an empty
|
66
|
-
expect(described_class.new(
|
66
|
+
# Case of an empty alternative
|
67
|
+
expect(described_class.new(choice, 0, 2)).to be_final_pos
|
67
68
|
end
|
68
69
|
|
69
70
|
it 'knows whether it is in an intermediate position' do
|
70
|
-
expect(described_class.new(
|
71
|
-
expect(described_class.new(
|
72
|
-
expect(described_class.new(
|
71
|
+
expect(described_class.new(choice, 0, 0)).not_to be_intermediate_pos
|
72
|
+
expect(described_class.new(choice, 2, 0)).to be_intermediate_pos
|
73
|
+
expect(described_class.new(choice, 3, 0)).not_to be_intermediate_pos
|
73
74
|
|
74
|
-
# Case of an empty
|
75
|
-
expect(described_class.new(
|
75
|
+
# Case of an empty alternative
|
76
|
+
expect(described_class.new(choice, 0, 2)).not_to be_intermediate_pos
|
76
77
|
end
|
77
78
|
|
78
79
|
it 'knows the symbol after the dot (if any)' do
|
79
|
-
expect(described_class.new(
|
80
|
-
expect(described_class.new(
|
81
|
-
expect(described_class.new(
|
82
|
-
expect(described_class.new(
|
80
|
+
expect(described_class.new(choice, 0, 1).next_symbol.name).to eq(:NUMBER)
|
81
|
+
expect(described_class.new(choice, 1, 1).next_symbol.name).to eq(:MINUS)
|
82
|
+
expect(described_class.new(choice, 2, 1).next_symbol.name).to eq(:NUMBER)
|
83
|
+
expect(described_class.new(choice, 3, 1).next_symbol).to be_nil
|
83
84
|
|
84
|
-
# Case of an empty
|
85
|
-
expect(described_class.new(
|
85
|
+
# Case of an empty alternative
|
86
|
+
expect(described_class.new(choice, 0, 2).next_symbol).to be_nil
|
86
87
|
end
|
87
88
|
|
88
|
-
it '
|
89
|
-
expect(described_class.new(
|
90
|
-
expect(described_class.new(
|
91
|
-
expect(described_class.new(
|
92
|
-
expect(described_class.new(
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
89
|
+
it 'knows the symbol before the dot (if any)' do
|
90
|
+
expect(described_class.new(choice, 0, 1).prev_symbol).to be_nil
|
91
|
+
expect(described_class.new(choice, 1, 1).prev_symbol.name).to eq(:NUMBER)
|
92
|
+
expect(described_class.new(choice, 2, 1).prev_symbol.name).to eq(:MINUS)
|
93
|
+
expect(described_class.new(choice, 3, 1).prev_symbol.name).to eq(:NUMBER)
|
94
|
+
|
95
|
+
# Case of an empty alternative
|
96
|
+
expect(described_class.new(choice, 0, 1).prev_symbol).to be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'can compare a given symbol to the one expected' do
|
100
|
+
expect(described_class.new(choice, 0, 1)).to be_expecting(num_symb)
|
101
|
+
expect(described_class.new(choice, 0, 1)).not_to be_expecting(plus_symb)
|
102
|
+
expect(described_class.new(choice, 1, 0)).to be_expecting(plus_symb)
|
103
|
+
expect(described_class.new(choice, 1, 1)).to be_expecting(minus_symb)
|
104
|
+
expect(described_class.new(choice, 2, 0)).to be_expecting(num_symb)
|
105
|
+
expect(described_class.new(choice, 3, 1)).not_to be_expecting(num_symb)
|
106
|
+
expect(described_class.new(choice, 3, 1)).not_to be_expecting(plus_symb)
|
107
|
+
|
108
|
+
# Case of an empty alternative
|
109
|
+
expect(described_class.new(choice, 0, 2)).not_to be_expecting(num_symb)
|
110
|
+
expect(described_class.new(choice, 0, 2)).not_to be_expecting(plus_symb)
|
99
111
|
end
|
100
112
|
end # context
|
101
113
|
end # describe
|
@@ -4,11 +4,10 @@ require_relative '..\..\spec_helper'
|
|
4
4
|
require_relative '..\..\..\lib\dendroid\syntax\terminal'
|
5
5
|
require_relative '..\..\..\lib\dendroid\syntax\non_terminal'
|
6
6
|
require_relative '..\..\..\lib\dendroid\syntax\symbol_seq'
|
7
|
-
require_relative '..\..\..\lib\dendroid\syntax\
|
8
|
-
|
9
|
-
require_relative '..\..\..\lib\dendroid\grm_analysis\choice_items'
|
7
|
+
require_relative '..\..\..\lib\dendroid\syntax\rule'
|
8
|
+
require_relative '..\..\..\lib\dendroid\grm_analysis\rule_items'
|
10
9
|
|
11
|
-
describe Dendroid::GrmAnalysis::
|
10
|
+
describe Dendroid::GrmAnalysis::RuleItems do
|
12
11
|
let(:num_symb) { Dendroid::Syntax::Terminal.new('NUMBER') }
|
13
12
|
let(:plus_symb) { Dendroid::Syntax::Terminal.new('PLUS') }
|
14
13
|
let(:star_symb) { Dendroid::Syntax::Terminal.new('STAR') }
|
@@ -17,8 +16,8 @@ describe Dendroid::GrmAnalysis::ChoiceItems do
|
|
17
16
|
let(:alt2) { Dendroid::Syntax::SymbolSeq.new([num_symb, star_symb, num_symb]) }
|
18
17
|
let(:alt3) { Dendroid::Syntax::SymbolSeq.new([]) }
|
19
18
|
subject do
|
20
|
-
choice = Dendroid::Syntax::
|
21
|
-
choice.extend(Dendroid::GrmAnalysis::
|
19
|
+
choice = Dendroid::Syntax::Rule.new(expr_symb, [alt1, alt2, alt3])
|
20
|
+
choice.extend(Dendroid::GrmAnalysis::RuleItems)
|
22
21
|
choice.build_items
|
23
22
|
choice
|
24
23
|
end
|
@@ -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/
|
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::
|
18
|
-
let(:empty_prod) { Dendroid::Syntax::
|
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/
|
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::
|
19
|
-
let(:empty_prod) { Dendroid::Syntax::
|
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
|
-
|
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
|