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