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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/lib/dendroid/formatters/ascii_tree.rb +142 -0
- data/lib/dendroid/formatters/base_formatter.rb +25 -0
- data/lib/dendroid/formatters/bracket_notation.rb +50 -0
- data/lib/dendroid/grm_analysis/dotted_item.rb +46 -30
- data/lib/dendroid/grm_analysis/grm_analyzer.rb +2 -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 +56 -0
- data/lib/dendroid/parsing/chart_walker.rb +293 -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 +51 -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 +117 -0
- data/lib/dendroid/recognizer/chart.rb +8 -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 +33 -20
- data/lib/dendroid/syntax/grammar.rb +1 -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 +223 -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 +57 -5
- data/spec/dendroid/support/sample_grammars.rb +2 -0
- 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
|
@@ -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,223 @@
|
|
|
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
|
+
context 'Parsing non-ambiguous grammars' do
|
|
41
|
+
it 'generates a parse tree for the example from Wikipedia' do
|
|
42
|
+
recognizer = recognizer_for(grammar_l1, tokenizer_l1)
|
|
43
|
+
chart = recognizer.run('2 + 3 * 4')
|
|
44
|
+
walker = described_class.new(chart)
|
|
45
|
+
root = walker.walk(success_entry(chart, recognizer))
|
|
46
|
+
|
|
47
|
+
expect(root.to_s).to eq('p => s [0, 5]')
|
|
48
|
+
expect(root.children.size). to eq(1)
|
|
49
|
+
expect(root.children[-1].to_s).to eq('s => s PLUS m [0, 5]')
|
|
50
|
+
plus_expr = root.children[-1]
|
|
51
|
+
expect(plus_expr.children.size).to eq(3)
|
|
52
|
+
expect(plus_expr.children[0].to_s).to eq('s => m [0, 1]')
|
|
53
|
+
expect(plus_expr.children[1].to_s).to eq('PLUS [1, 2]')
|
|
54
|
+
expect(plus_expr.children[2].to_s).to eq('m => m STAR t [2, 5]')
|
|
55
|
+
|
|
56
|
+
operand_plus = plus_expr.children[0]
|
|
57
|
+
expect(operand_plus.children.size).to eq(1)
|
|
58
|
+
expect(operand_plus.children[0].to_s).to eq('m => t [0, 1]')
|
|
59
|
+
expect(operand_plus.children[0].children.size).to eq(1)
|
|
60
|
+
expect(operand_plus.children[0].children[0].to_s).to eq('t => INTEGER [0, 1]')
|
|
61
|
+
expect(operand_plus.children[0].children[0].children[0].to_s).to eq('INTEGER: 2 [0, 1]')
|
|
62
|
+
|
|
63
|
+
expect(plus_expr.children[1].to_s).to eq('PLUS [1, 2]')
|
|
64
|
+
|
|
65
|
+
star_expr = plus_expr.children[2]
|
|
66
|
+
expect(star_expr.children.size).to eq(3)
|
|
67
|
+
expect(star_expr.children[0].to_s).to eq('m => t [2, 3]')
|
|
68
|
+
expect(star_expr.children[1].to_s).to eq('STAR [3, 4]')
|
|
69
|
+
expect(star_expr.children[2].to_s).to eq('t => INTEGER [4, 5]')
|
|
70
|
+
|
|
71
|
+
operand_star = star_expr.children[0]
|
|
72
|
+
expect(operand_star.children.size).to eq(1)
|
|
73
|
+
expect(operand_star.children[0].to_s).to eq('t => INTEGER [2, 3]')
|
|
74
|
+
expect(operand_star.children[0].children[0].to_s).to eq('INTEGER: 3 [2, 3]')
|
|
75
|
+
|
|
76
|
+
expect(star_expr.children[2].children.size).to eq(1)
|
|
77
|
+
expect(star_expr.children[2].children[0].to_s).to eq('INTEGER: 4 [4, 5]')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'generates a parse tree for grammar l10 (with left recursive rule)' do
|
|
81
|
+
recognizer = recognizer_for(grammar_l10, tokenizer_l10)
|
|
82
|
+
chart = recognizer.run('a a a a a')
|
|
83
|
+
walker = described_class.new(chart)
|
|
84
|
+
root = walker.walk(success_entry(chart, recognizer))
|
|
85
|
+
|
|
86
|
+
expect(root.to_s).to eq('A => A a [0, 5]')
|
|
87
|
+
expect(root.children.size). to eq(2)
|
|
88
|
+
expect(root.children[0].to_s).to eq('A => A a [0, 4]')
|
|
89
|
+
expect(root.children[1].to_s).to eq('a [4, 5]')
|
|
90
|
+
|
|
91
|
+
expect(root.children[0].children.size).to eq(2)
|
|
92
|
+
expect(root.children[0].children[0].to_s).to eq('A => A a [0, 3]')
|
|
93
|
+
expect(root.children[0].children[1].to_s).to eq('a [3, 4]')
|
|
94
|
+
|
|
95
|
+
grand_child = root.children[0].children[0]
|
|
96
|
+
expect(grand_child.children.size).to eq(2)
|
|
97
|
+
expect(grand_child.children[0].to_s).to eq('A => A a [0, 2]')
|
|
98
|
+
expect(grand_child.children[1].to_s).to eq('a [2, 3]')
|
|
99
|
+
|
|
100
|
+
expect(grand_child.children[0].children.size).to eq(2)
|
|
101
|
+
expect(grand_child.children[0].children[0].to_s).to eq('A => A a [0, 1]')
|
|
102
|
+
expect(grand_child.children[0].children[1].to_s).to eq('a [1, 2]')
|
|
103
|
+
|
|
104
|
+
expect(grand_child.children[0].children[0].children.size).to eq(2)
|
|
105
|
+
expect(grand_child.children[0].children[0].children[0].to_s).to eq('_ [0, 0]')
|
|
106
|
+
expect(grand_child.children[0].children[0].children[1].to_s).to eq('a [0, 1]')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'generates a parse tree for grammar l11 (with right recursive rule)' do
|
|
110
|
+
recognizer = recognizer_for(grammar_l11, tokenizer_l11)
|
|
111
|
+
chart = recognizer.run('a a a a a')
|
|
112
|
+
walker = described_class.new(chart)
|
|
113
|
+
root = walker.walk(success_entry(chart, recognizer))
|
|
114
|
+
|
|
115
|
+
expect(root.to_s).to eq('A => a A [0, 5]')
|
|
116
|
+
expect(root.children.size). to eq(2)
|
|
117
|
+
expect(root.children[0].to_s).to eq('a [0, 1]')
|
|
118
|
+
expect(root.children[1].to_s).to eq('A => a A [1, 5]')
|
|
119
|
+
|
|
120
|
+
expect(root.children[1].children.size).to eq(2)
|
|
121
|
+
expect(root.children[1].children[0].to_s).to eq('a [1, 2]')
|
|
122
|
+
expect(root.children[1].children[1].to_s).to eq('A => a A [2, 5]')
|
|
123
|
+
|
|
124
|
+
grand_child = root.children[1].children[1]
|
|
125
|
+
expect(grand_child.children.size).to eq(2)
|
|
126
|
+
expect(grand_child.children[0].to_s).to eq('a [2, 3]')
|
|
127
|
+
expect(grand_child.children[1].to_s).to eq('A => a A [3, 5]')
|
|
128
|
+
|
|
129
|
+
expect(grand_child.children[1].children.size).to eq(2)
|
|
130
|
+
expect(grand_child.children[1].children[0].to_s).to eq('a [3, 4]')
|
|
131
|
+
expect(grand_child.children[1].children[1].to_s).to eq('A => a A [4, 5]')
|
|
132
|
+
|
|
133
|
+
expect(grand_child.children[1].children[1].children.size).to eq(2)
|
|
134
|
+
expect(grand_child.children[1].children[1].children[0].to_s).to eq('a [4, 5]')
|
|
135
|
+
expect(grand_child.children[1].children[1].children[1].to_s).to eq('_ [5, 5]')
|
|
136
|
+
end
|
|
137
|
+
end # context
|
|
138
|
+
|
|
139
|
+
context 'Parsing ambiguous grammars' do
|
|
140
|
+
it "generates a parse forest for the G2 grammar that choked Earley's parsing algorithm" do
|
|
141
|
+
recognizer = recognizer_for(grammar_l8, tokenizer_l8)
|
|
142
|
+
chart = recognizer.run('x x x x')
|
|
143
|
+
walker = described_class.new(chart)
|
|
144
|
+
root = walker.walk(success_entry(chart, recognizer))
|
|
145
|
+
|
|
146
|
+
expect(root.to_s).to eq('OR: S [0, 4]')
|
|
147
|
+
expect(root.children.size). to eq(3)
|
|
148
|
+
root.children.each do |child|
|
|
149
|
+
expect(child.children.size).to eq(2)
|
|
150
|
+
expect(child.to_s).to eq('S => S S [0, 4]')
|
|
151
|
+
end
|
|
152
|
+
(a, b, c) = root.children
|
|
153
|
+
|
|
154
|
+
# Test structure of tree a
|
|
155
|
+
(child_a_0, child_a_1) = a.children
|
|
156
|
+
expect(child_a_0.to_s).to eq('S => S S [0, 2]')
|
|
157
|
+
expect(child_a_1.to_s).to eq('S => S S [2, 4]')
|
|
158
|
+
expect(child_a_0.children.size).to eq(2)
|
|
159
|
+
(child_a_0_0, child_a_0_1) = child_a_0.children
|
|
160
|
+
expect(child_a_0_0.to_s).to eq('S => x [0, 1]')
|
|
161
|
+
expect(child_a_0_1.to_s).to eq('S => x [1, 2]')
|
|
162
|
+
expect(child_a_0_0.children[0].to_s).to eq('x [0, 1]')
|
|
163
|
+
expect(child_a_0_1.children[0].to_s).to eq('x [1, 2]')
|
|
164
|
+
|
|
165
|
+
expect(child_a_1.children.size).to eq(2)
|
|
166
|
+
(child_a_1_0, child_a_1_1) = child_a_1.children
|
|
167
|
+
expect(child_a_1_0.to_s).to eq('S => x [2, 3]')
|
|
168
|
+
expect(child_a_1_1.to_s).to eq('S => x [3, 4]')
|
|
169
|
+
expect(child_a_1_0.children[0].to_s).to eq('x [2, 3]')
|
|
170
|
+
expect(child_a_1_1.children[0].to_s).to eq('x [3, 4]')
|
|
171
|
+
|
|
172
|
+
# Test structure of forest b
|
|
173
|
+
(child_b_0, child_b_1) = b.children
|
|
174
|
+
expect(child_b_0.to_s).to eq('OR: S [0, 3]')
|
|
175
|
+
expect(child_b_1.to_s).to eq('S => x [3, 4]')
|
|
176
|
+
expect(child_b_1.equal?(child_a_1_1)).to be_truthy # Sharing
|
|
177
|
+
expect(child_b_0.children.size).to eq(2)
|
|
178
|
+
(child_b_0_0, child_b_0_1) = child_b_0.children
|
|
179
|
+
expect(child_b_0_0.to_s).to eq('S => S S [0, 3]')
|
|
180
|
+
expect(child_b_0_1.to_s).to eq('S => S S [0, 3]')
|
|
181
|
+
expect(child_b_0_0.children.size).to eq(2)
|
|
182
|
+
(child_b_0_0_0, child_b_0_0_1) = child_b_0_0.children
|
|
183
|
+
expect(child_b_0_0_0.to_s).to eq('S => x [0, 1]')
|
|
184
|
+
expect(child_b_0_0_0.equal?(child_a_0_0)).to be_truthy # Sharing
|
|
185
|
+
expect(child_b_0_0_1.to_s).to eq('S => S S [1, 3]')
|
|
186
|
+
expect(child_b_0_0_1.children.size).to eq(2)
|
|
187
|
+
expect(child_b_0_0_1.children[0].to_s).to eq('S => x [1, 2]')
|
|
188
|
+
expect(child_b_0_0_1.children[0].equal?(child_a_0_1)).to be_truthy # Sharing
|
|
189
|
+
expect(child_b_0_0_1.children[1].to_s).to eq('S => x [2, 3]')
|
|
190
|
+
expect(child_b_0_0_1.children[1].equal?(child_a_1_0)).to be_truthy # Sharing
|
|
191
|
+
|
|
192
|
+
expect(child_b_0_1.children.size).to eq(2)
|
|
193
|
+
(child_b_0_1_0, child_b_0_1_1) = child_b_0_1.children
|
|
194
|
+
expect(child_b_0_1_0.to_s).to eq('S => S S [0, 2]')
|
|
195
|
+
expect(child_b_0_1_0.equal?(child_a_0)).to be_truthy # Sharing
|
|
196
|
+
expect(child_b_0_1_1.to_s).to eq('S => x [2, 3]')
|
|
197
|
+
expect(child_b_0_1_1.equal?(child_a_1_0)).to be_truthy # Sharing
|
|
198
|
+
|
|
199
|
+
# Test structure of forest c
|
|
200
|
+
(child_c_0, child_c_1) = c.children
|
|
201
|
+
expect(child_c_0.to_s).to eq('S => x [0, 1]')
|
|
202
|
+
expect(child_c_0.equal?(child_a_0_0)).to be_truthy # Sharing
|
|
203
|
+
expect(child_c_1.to_s).to eq('OR: S [1, 4]')
|
|
204
|
+
expect(child_c_1.children.size).to eq(2)
|
|
205
|
+
(child_c_1_0, child_c_1_1) = child_c_1.children
|
|
206
|
+
expect(child_c_1_0.to_s).to eq('S => S S [1, 4]')
|
|
207
|
+
expect(child_c_1_1.to_s).to eq('S => S S [1, 4]')
|
|
208
|
+
expect(child_c_1_0.children.size).to eq(2)
|
|
209
|
+
(child_c_1_0_0, child_c_1_0_1) = child_c_1_0.children
|
|
210
|
+
expect(child_c_1_0_0.to_s).to eq('S => x [1, 2]')
|
|
211
|
+
expect(child_c_1_0_0.equal?(child_a_0_1)).to be_truthy # Sharing
|
|
212
|
+
expect(child_c_1_0_1.to_s).to eq('S => S S [2, 4]')
|
|
213
|
+
expect(child_c_1_0_1.equal?(child_a_1)).to be_truthy # Sharing
|
|
214
|
+
(child_c_1_1_0, child_c_1_1_1) = child_c_1_1.children
|
|
215
|
+
expect(child_c_1_1_0.to_s).to eq('S => S S [1, 3]')
|
|
216
|
+
expect(child_c_1_1_0.equal?(child_b_0_0_1)).to be_truthy # Sharing
|
|
217
|
+
expect(child_c_1_1_1.to_s).to eq('S => x [3, 4]')
|
|
218
|
+
expect(child_c_1_1_1.equal?(child_b_1)).to be_truthy # Sharing
|
|
219
|
+
end
|
|
220
|
+
end # context
|
|
221
|
+
end # describe
|
|
222
|
+
|
|
223
|
+
|
|
@@ -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
|
|
@@ -51,7 +51,7 @@ describe Dendroid::Recognizer::Recognizer do
|
|
|
51
51
|
'm => t . @ 0',
|
|
52
52
|
's => m . @ 0',
|
|
53
53
|
# 'm => m . STAR t @ 0',
|
|
54
|
-
'p => s . @ 0',
|
|
54
|
+
'p => s . @ 0', # Can be ruled out (next token != eos)
|
|
55
55
|
's => s . PLUS m @ 0'
|
|
56
56
|
]
|
|
57
57
|
set2 = [ # 2 + . 3 * 4'
|
|
@@ -65,7 +65,7 @@ describe Dendroid::Recognizer::Recognizer do
|
|
|
65
65
|
'm => t . @ 2',
|
|
66
66
|
's => s PLUS m . @ 0',
|
|
67
67
|
'm => m . STAR t @ 2',
|
|
68
|
-
'p => s . @ 0'
|
|
68
|
+
'p => s . @ 0' # Can be ruled out (next token != eos)
|
|
69
69
|
# 's => s . PLUS m @ 0'
|
|
70
70
|
]
|
|
71
71
|
set4 = [ # 2 + 3 * . 4'
|
|
@@ -422,6 +422,55 @@ describe Dendroid::Recognizer::Recognizer do
|
|
|
422
422
|
comp_expected_actuals(chart, expectations)
|
|
423
423
|
end
|
|
424
424
|
|
|
425
|
+
|
|
426
|
+
it 'accepts an input with multiple levels of ambiguity' do
|
|
427
|
+
recognizer = described_class.new(grammar_l8, tokenizer_l8)
|
|
428
|
+
chart = recognizer.run('x x x x')
|
|
429
|
+
expect(chart).to be_successful
|
|
430
|
+
|
|
431
|
+
set0 = [ # . x x x x
|
|
432
|
+
'S => . S S @ 0',
|
|
433
|
+
'S => . x @ 0'
|
|
434
|
+
]
|
|
435
|
+
set1 = [ # x . x x x
|
|
436
|
+
'S => x . @ 0',
|
|
437
|
+
'S => S . S @ 0',
|
|
438
|
+
'S => . S S @ 1',
|
|
439
|
+
'S => . x @ 1'
|
|
440
|
+
]
|
|
441
|
+
set2 = [ # x x . x x
|
|
442
|
+
'S => x . @ 1',
|
|
443
|
+
'S => S S . @ 0',
|
|
444
|
+
'S => S . S @ 1',
|
|
445
|
+
'S => S . S @ 0',
|
|
446
|
+
'S => . S S @ 2',
|
|
447
|
+
'S => . x @ 2'
|
|
448
|
+
]
|
|
449
|
+
set3 = [ # x x x . x
|
|
450
|
+
'S => x . @ 2',
|
|
451
|
+
'S => S S . @ 1',
|
|
452
|
+
'S => S S . @ 0',
|
|
453
|
+
'S => S . S @ 2',
|
|
454
|
+
'S => S . S @ 1',
|
|
455
|
+
'S => S . S @ 0',
|
|
456
|
+
'S => . S S @ 3',
|
|
457
|
+
'S => . x @ 3'
|
|
458
|
+
]
|
|
459
|
+
set4 = [ # x x x x .
|
|
460
|
+
'S => x . @ 3',
|
|
461
|
+
'S => S S . @ 2',
|
|
462
|
+
'S => S S . @ 1',
|
|
463
|
+
'S => S S . @ 0', # Success entry
|
|
464
|
+
'S => S . S @ 3',
|
|
465
|
+
'S => S . S @ 2',
|
|
466
|
+
'S => S . S @ 1',
|
|
467
|
+
'S => S . S @ 0',
|
|
468
|
+
'S => . S S @ 4'
|
|
469
|
+
]
|
|
470
|
+
expectations = [set0, set1, set2, set3, set4]
|
|
471
|
+
comp_expected_actuals(chart, expectations)
|
|
472
|
+
end
|
|
473
|
+
|
|
425
474
|
it 'swallows the input from an infinite ambiguity grammar' do
|
|
426
475
|
recognizer = described_class.new(grammar_l9, tokenizer_l9)
|
|
427
476
|
chart = recognizer.run('x x x')
|
|
@@ -441,7 +490,8 @@ describe Dendroid::Recognizer::Recognizer do
|
|
|
441
490
|
'S => . S S @ 1',
|
|
442
491
|
'S => . @ 1',
|
|
443
492
|
'S => . x @ 1',
|
|
444
|
-
'S => S . S @ 1'
|
|
493
|
+
'S => S . S @ 1',
|
|
494
|
+
'S => S S . @ 1'
|
|
445
495
|
]
|
|
446
496
|
set2 = [ # x x . x
|
|
447
497
|
'S => x . @ 1',
|
|
@@ -452,7 +502,8 @@ describe Dendroid::Recognizer::Recognizer do
|
|
|
452
502
|
'S => . S S @ 2',
|
|
453
503
|
'S => . @ 2',
|
|
454
504
|
'S => . x @ 2',
|
|
455
|
-
'S => S . S @ 2'
|
|
505
|
+
'S => S . S @ 2',
|
|
506
|
+
'S => S S . @ 2'
|
|
456
507
|
]
|
|
457
508
|
set3 = [ # x x x .
|
|
458
509
|
'S => x . @ 2',
|
|
@@ -465,7 +516,8 @@ describe Dendroid::Recognizer::Recognizer do
|
|
|
465
516
|
'S => . S S @ 3',
|
|
466
517
|
'S => . @ 3',
|
|
467
518
|
# 'S => . x @ 3',
|
|
468
|
-
'S => S . S @ 3'
|
|
519
|
+
'S => S . S @ 3',
|
|
520
|
+
'S => S S . @ 3'
|
|
469
521
|
]
|
|
470
522
|
expectations = [set0, set1, set2, set3]
|
|
471
523
|
comp_expected_actuals(chart, expectations)
|
|
@@ -211,6 +211,7 @@ module SampleGrammars
|
|
|
211
211
|
end
|
|
212
212
|
|
|
213
213
|
def grammar_l10
|
|
214
|
+
# Grammar with left recursive rule
|
|
214
215
|
builder = Dendroid::GrmDSL::BaseGrmBuilder.new do
|
|
215
216
|
declare_terminals('a')
|
|
216
217
|
|
|
@@ -230,6 +231,7 @@ module SampleGrammars
|
|
|
230
231
|
|
|
231
232
|
def grammar_l11
|
|
232
233
|
builder = Dendroid::GrmDSL::BaseGrmBuilder.new do
|
|
234
|
+
# Grammar with right-recursive rule
|
|
233
235
|
declare_terminals('a')
|
|
234
236
|
|
|
235
237
|
rule 'A' => ['a A', '']
|