rley 0.2.01 → 0.2.02
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/CHANGELOG.md +5 -0
- data/README.md +9 -6
- data/examples/parsers/parsing_L0.rb +1 -1
- data/examples/parsers/parsing_L1.rb +136 -0
- data/examples/parsers/parsing_abc.rb +1 -2
- data/examples/parsers/parsing_ambig.rb +85 -0
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/formatter/json.rb +1 -4
- data/lib/rley/parser/parse_state_tracker.rb +1 -1
- data/lib/rley/parser/parse_tree_builder.rb +12 -13
- data/lib/rley/parser/parsing.rb +61 -44
- data/lib/rley/ptree/non_terminal_node.rb +13 -0
- data/lib/rley/ptree/parse_tree_node.rb +7 -0
- data/lib/rley/ptree/terminal_node.rb +7 -0
- data/lib/rley/ptree/token_range.rb +18 -0
- data/spec/rley/formatter/json_spec.rb +1 -2
- data/spec/rley/parser/earley_parser_spec.rb +77 -4
- data/spec/rley/parser/parse_tree_builder_spec.rb +28 -27
- data/spec/rley/parser/parsing_spec.rb +230 -73
- data/spec/rley/ptree/non_terminal_node_spec.rb +42 -3
- data/spec/rley/ptree/terminal_node_spec.rb +39 -0
- data/spec/rley/ptree/token_range_spec.rb +45 -0
- data/spec/rley/support/ambiguous_grammar_helper.rb +36 -0
- data/spec/rley/support/grammar_b_expr_helper.rb +1 -1
- metadata +7 -2
@@ -16,6 +16,19 @@ module Rley # This module is used as a namespace
|
|
16
16
|
children << aChildNode
|
17
17
|
end
|
18
18
|
|
19
|
+
# Emit a (formatted) string representation of the node.
|
20
|
+
# Mainly used for diagnosis/debugging purposes.
|
21
|
+
def to_string(indentation)
|
22
|
+
connector = '+- '
|
23
|
+
selfie = super(indentation)
|
24
|
+
prefix = "\n" + (' ' * connector.size * indentation) + connector
|
25
|
+
children_repr = children.reduce('') do |sub_result, child|
|
26
|
+
sub_result << prefix + child.to_string(indentation + 1)
|
27
|
+
end
|
28
|
+
|
29
|
+
return selfie + children_repr
|
30
|
+
end
|
31
|
+
|
19
32
|
# Part of the 'visitee' role in Visitor design pattern.
|
20
33
|
# @param aVisitor[ParseTreeVisitor] the visitor
|
21
34
|
def accept(aVisitor)
|
@@ -17,6 +17,13 @@ module Rley # This module is used as a namespace
|
|
17
17
|
def range=(aRange)
|
18
18
|
range.assign(aRange)
|
19
19
|
end
|
20
|
+
|
21
|
+
# Emit a (formatted) string representation of the node.
|
22
|
+
# Mainly used for diagnosis/debugging purposes.
|
23
|
+
def to_string(indentation)
|
24
|
+
|
25
|
+
return "#{symbol.name}#{range.to_string(indentation)}"
|
26
|
+
end
|
20
27
|
end # class
|
21
28
|
end # module
|
22
29
|
end # module
|
@@ -10,6 +10,13 @@ module Rley # This module is used as a namespace
|
|
10
10
|
super(aTerminalSymbol, aRange)
|
11
11
|
end
|
12
12
|
|
13
|
+
# Emit a (formatted) string representation of the node.
|
14
|
+
# Mainly used for diagnosis/debugging purposes.
|
15
|
+
def to_string(indentation)
|
16
|
+
value = token.nil? ? '(nil)' : token.lexeme
|
17
|
+
super(indentation) + ": '#{value}'"
|
18
|
+
end
|
19
|
+
|
13
20
|
# Part of the 'visitee' role in Visitor design pattern.
|
14
21
|
# @param aVisitor[ParseTreeVisitor] the visitor
|
15
22
|
def accept(aVisitor)
|
@@ -42,6 +42,24 @@ module Rley # This module is used as a namespace
|
|
42
42
|
assign_high(aRange) if high.nil?
|
43
43
|
end
|
44
44
|
|
45
|
+
# Tell whether the given index value lies outside the range
|
46
|
+
def out_of_range?(index)
|
47
|
+
result = false
|
48
|
+
result = true if !low.nil? && index < low
|
49
|
+
result = true if !high.nil? && index > high
|
50
|
+
|
51
|
+
return result
|
52
|
+
end
|
53
|
+
|
54
|
+
# Emit a (formatted) string representation of the range.
|
55
|
+
# Mainly used for diagnosis/debugging purposes.
|
56
|
+
def to_string(_indentation)
|
57
|
+
low_text = low.nil? ? '?' : low.to_s
|
58
|
+
high_text = high.nil? ? '?' : high.to_s
|
59
|
+
|
60
|
+
return "[#{low_text}, #{high_text}]"
|
61
|
+
end
|
62
|
+
|
45
63
|
private
|
46
64
|
|
47
65
|
def assign_low(aRange)
|
@@ -6,6 +6,7 @@ require_relative '../../../lib/rley/syntax/production'
|
|
6
6
|
require_relative '../../../lib/rley/syntax/grammar_builder'
|
7
7
|
require_relative '../../../lib/rley/parser/token'
|
8
8
|
require_relative '../../../lib/rley/parser/dotted_item'
|
9
|
+
require_relative '../support/ambiguous_grammar_helper'
|
9
10
|
# Load the class under test
|
10
11
|
require_relative '../../../lib/rley/parser/earley_parser'
|
11
12
|
|
@@ -38,9 +39,9 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
38
39
|
|
39
40
|
# Grammar 1: A very simple language
|
40
41
|
# (based on example in N. Wirth "Compiler Construction" book, p. 6)
|
41
|
-
# S
|
42
|
-
# A
|
43
|
-
# A
|
42
|
+
# S => A.
|
43
|
+
# A => "a" A "c".
|
44
|
+
# A => "b".
|
44
45
|
# Let's create the grammar piece by piece
|
45
46
|
let(:nt_S) { Syntax::NonTerminal.new('S') }
|
46
47
|
let(:nt_A) { Syntax::NonTerminal.new('A') }
|
@@ -333,7 +334,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
333
334
|
compare_state_texts(parse_result.chart[1], expected)
|
334
335
|
end
|
335
336
|
|
336
|
-
it 'should parse an ambiguous grammar' do
|
337
|
+
it 'should parse an ambiguous grammar (I)' do
|
337
338
|
# Grammar 3: A ambiguous arithmetic expression language
|
338
339
|
# (based on example in article on Earley's algorithm in Wikipedia)
|
339
340
|
# P => S.
|
@@ -442,6 +443,78 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
442
443
|
compare_state_texts(parse_result.chart[5], expected)
|
443
444
|
end
|
444
445
|
|
446
|
+
it 'should parse an ambiguous grammar (II)' do
|
447
|
+
self.extend(AmbiguousGrammarHelper)
|
448
|
+
grammar = grammar_builder.grammar
|
449
|
+
instance = EarleyParser.new(grammar)
|
450
|
+
tokens = tokenize('abc + def + ghi', grammar)
|
451
|
+
expect { instance.parse(tokens) }.not_to raise_error
|
452
|
+
parse_result = instance.parse(tokens)
|
453
|
+
expect(parse_result.success?).to eq(true)
|
454
|
+
|
455
|
+
###################### S(0): . abc + def + ghi
|
456
|
+
# Expectation chart[0]:
|
457
|
+
expected = [
|
458
|
+
'S => . E | 0', # Start rule
|
459
|
+
'E => . E + E | 0', # predict from (1)
|
460
|
+
'E => . id | 0' # predict from (1)
|
461
|
+
]
|
462
|
+
compare_state_texts(parse_result.chart[0], expected)
|
463
|
+
|
464
|
+
###################### S(1): abc . + def + ghi
|
465
|
+
# Expectation chart[1]:
|
466
|
+
expected = [
|
467
|
+
'E => id . | 0', # scan from S(0) 3
|
468
|
+
'S => E . | 0', # complete from (1) and S(0) 2
|
469
|
+
'E => E . + E | 0' # complete from (1) and S(0) 3
|
470
|
+
]
|
471
|
+
compare_state_texts(parse_result.chart[1], expected)
|
472
|
+
|
473
|
+
###################### S(2): abc + . def + ghi
|
474
|
+
# Expectation chart[2]:
|
475
|
+
expected = [
|
476
|
+
'E => E + . E | 0', # Scan from S(1) 3
|
477
|
+
'E => . E + E | 2', # predict from (1)
|
478
|
+
'E => . id | 2' # predict from (1)
|
479
|
+
]
|
480
|
+
compare_state_texts(parse_result.chart[2], expected)
|
481
|
+
|
482
|
+
###################### S(3): abc + def . + ghi
|
483
|
+
# Expectation chart[3]:
|
484
|
+
expected = [
|
485
|
+
'E => id . | 2', # Scan from S(2) 3
|
486
|
+
'E => E + E . | 0', # complete from (1) and S(2) 1
|
487
|
+
'E => E . + E | 2', # complete from (1) and S(2) 2
|
488
|
+
'S => E . | 0', # complete from (1) and S(0) 1
|
489
|
+
'E => E . + E | 0' # complete from (1) and S(0) 2
|
490
|
+
]
|
491
|
+
compare_state_texts(parse_result.chart[3], expected)
|
492
|
+
|
493
|
+
###################### S(4): abc + def + . ghi
|
494
|
+
# Expectation chart[4]:
|
495
|
+
expected = [
|
496
|
+
'E => E + . E | 2', # Scan from S(3) 3
|
497
|
+
'E => E + . E | 0', # Scan from S(3) 5
|
498
|
+
'E => . E + E | 4', # predict from (1)
|
499
|
+
'E => . id | 4' # predict from (1)
|
500
|
+
]
|
501
|
+
compare_state_texts(parse_result.chart[4], expected)
|
502
|
+
|
503
|
+
###################### S(5): abc + def + ghi .
|
504
|
+
# Expectation chart[5]:
|
505
|
+
expected = [
|
506
|
+
'E => id . | 4', # Scan from S(4) 4
|
507
|
+
'E => E + E . | 2', # complete from (1) and S(4) 1
|
508
|
+
'E => E + E . | 0', # complete from (1) and S(4) 2
|
509
|
+
'E => E . + E | 4', # complete from (1) and S(4) 3
|
510
|
+
'E => E . + E | 2', # complete from (1) and S(2) 2
|
511
|
+
'S => E . | 0', # complete from (1) and S(0) 1
|
512
|
+
'E => E . + E | 0', # complete from (1) and S(0) 2
|
513
|
+
]
|
514
|
+
compare_state_texts(parse_result.chart[5], expected)
|
515
|
+
end
|
516
|
+
|
517
|
+
|
445
518
|
|
446
519
|
it 'should parse an invalid simple input' do
|
447
520
|
# Parse an erroneous input (b is missing)
|
@@ -21,8 +21,8 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
21
21
|
let(:small_a) { grammar_abc.name2symbol['a'] }
|
22
22
|
let(:small_b) { grammar_abc.name2symbol['b'] }
|
23
23
|
let(:small_c) { grammar_abc.name2symbol['c'] }
|
24
|
-
|
25
|
-
let(:start_prod) { grammar_abc.start_production }
|
24
|
+
|
25
|
+
let(:start_prod) { grammar_abc.start_production }
|
26
26
|
|
27
27
|
let(:tokens_abc) do
|
28
28
|
%w(a a b c c).map do |letter|
|
@@ -34,7 +34,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
34
34
|
parser = EarleyParser.new(grammar_abc)
|
35
35
|
parser.parse(tokens_abc)
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
subject { ParseTreeBuilder.new(start_prod, { low: 0, high: 5 }) }
|
39
39
|
|
40
40
|
context 'Initialization:' do
|
@@ -71,7 +71,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
71
71
|
# Add children to A
|
72
72
|
other_state = sample_parsing.chart.state_sets.last.states.first
|
73
73
|
subject.use_complete_state(other_state)
|
74
|
-
|
74
|
+
|
75
75
|
# Tree is:
|
76
76
|
# S[0,5]
|
77
77
|
# +- A[0,5]
|
@@ -85,7 +85,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
85
85
|
end
|
86
86
|
expect(child1.children[0].range.low).to eq(0)
|
87
87
|
expect(child1.children[-1].range.high).to eq(5)
|
88
|
-
|
88
|
+
|
89
89
|
subject.move_down # ... to c
|
90
90
|
subject.range = { low: 4 }
|
91
91
|
expect(child1.children[-1].range.low).to eq(4)
|
@@ -132,46 +132,47 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
132
132
|
subject.use_complete_state(other_state)
|
133
133
|
|
134
134
|
# Tree is:
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
135
|
+
tree_snapshot = <<-SNIPPET
|
136
|
+
S[0, 5]
|
137
|
+
+- A[0, 5]
|
138
|
+
+- a[0, ?]: '(nil)'
|
139
|
+
+- A[1, ?]
|
140
|
+
+- a[1, ?]: '(nil)'
|
141
|
+
+- A[?, ?]
|
142
|
+
+- c[?, ?]: '(nil)'
|
143
|
+
+- c[?, 5]: '(nil)'
|
144
|
+
SNIPPET
|
145
|
+
expect(subject.root.to_string(0)).to eq(tree_snapshot.chomp)
|
143
146
|
|
144
147
|
subject.move_down # ...to grand-grand-child c
|
145
|
-
expect(subject.current_node.
|
148
|
+
expect(subject.current_node.to_string(0)).to eq("c[?, ?]: '(nil)'")
|
146
149
|
|
147
150
|
subject.move_back # ...to grand-grand-child A
|
148
|
-
expect(subject.current_node.
|
151
|
+
expect(subject.current_node.to_string(0)).to eq('A[?, ?]')
|
149
152
|
|
150
153
|
subject.move_back # ...to grand-grand-child a
|
151
|
-
expect(subject.current_node.
|
154
|
+
expect(subject.current_node.to_string(0)).to eq("a[1, ?]: '(nil)'")
|
152
155
|
|
153
|
-
subject.move_back # ...to grand-child A
|
154
|
-
expect(subject.current_node.symbol).to eq(capital_a)
|
155
|
-
|
156
156
|
subject.move_back # ...to grand-child a
|
157
|
-
expect(subject.current_node.
|
158
|
-
|
159
|
-
subject.move_back # ...to child A
|
160
|
-
expect(subject.current_node.symbol).to eq(capital_a)
|
157
|
+
expect(subject.current_node.to_string(0)).to eq("a[0, ?]: '(nil)'")
|
161
158
|
|
162
159
|
subject.move_back # ...to S
|
163
|
-
expect(subject.current_node.symbol).to eq(capital_s)
|
160
|
+
expect(subject.current_node.symbol).to eq(capital_s)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should move through deeply nested structure' do
|
164
|
+
|
164
165
|
end
|
165
166
|
end # context
|
166
|
-
|
167
|
-
context 'Parse tree building:' do
|
167
|
+
|
168
|
+
context 'Parse tree building:' do
|
168
169
|
it 'should build a parse tree' do
|
169
170
|
expect(subject.parse_tree).to be_kind_of(PTree::ParseTree)
|
170
171
|
actual = subject.parse_tree
|
171
172
|
expect(actual.root).to eq(subject.root)
|
172
173
|
end
|
173
174
|
end # context
|
174
|
-
|
175
|
+
|
175
176
|
end # describe
|
176
177
|
end # module
|
177
178
|
end # module
|
@@ -21,9 +21,9 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
21
21
|
include GrammarBExprHelper # Mix-in with builder for simple expressions
|
22
22
|
|
23
23
|
# Grammar 1: A very simple language
|
24
|
-
# S
|
25
|
-
# A
|
26
|
-
# A
|
24
|
+
# S => A.
|
25
|
+
# A => "a" A "c".
|
26
|
+
# A => "b".
|
27
27
|
let(:nt_S) { Syntax::NonTerminal.new('S') }
|
28
28
|
let(:nt_A) { Syntax::NonTerminal.new('A') }
|
29
29
|
let(:a_) { Syntax::VerbatimSymbol.new('a') }
|
@@ -139,6 +139,215 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
139
139
|
b_expr_grammar.name2symbol[aSymbolName]
|
140
140
|
end
|
141
141
|
|
142
|
+
subject do
|
143
|
+
parser = EarleyParser.new(b_expr_grammar)
|
144
|
+
tokens = expr_tokenizer('2 + 3 * 4', b_expr_grammar)
|
145
|
+
instance = parser.parse(tokens)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Helper. Build a state tracker and a parse tree builder.
|
149
|
+
def prepare_parse_tree(aParsing)
|
150
|
+
# Accessing private methods by sending message
|
151
|
+
state_tracker = aParsing.send(:new_state_tracker)
|
152
|
+
builder = aParsing.send(:tree_builder, state_tracker.state_set_index)
|
153
|
+
return [state_tracker, builder]
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
it 'should create the root of a parse tree' do
|
158
|
+
(state_tracker, builder) = prepare_parse_tree(subject)
|
159
|
+
# The root node should correspond to the start symbol and
|
160
|
+
# its direct children should correspond to rhs of start production
|
161
|
+
expected_text = <<-SNIPPET
|
162
|
+
P[0, 5]
|
163
|
+
+- S[0, 5]
|
164
|
+
SNIPPET
|
165
|
+
root_text = builder.root.to_string(0)
|
166
|
+
expect(root_text).to eq(expected_text.chomp)
|
167
|
+
|
168
|
+
expect(state_tracker.state_set_index).to eq(subject.tokens.size)
|
169
|
+
expected_state = 'P => S . | 0'
|
170
|
+
expect(state_tracker.parse_state.to_s).to eq(expected_state)
|
171
|
+
expect(builder.current_node.to_string(0)).to eq('S[0, 5]')
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'should use a reduce item for a matched non-terminal' do
|
175
|
+
# Setup
|
176
|
+
(state_tracker, builder) = prepare_parse_tree(subject)
|
177
|
+
# Same state as in previous example
|
178
|
+
|
179
|
+
# Given matched symbol is S[0, 5]
|
180
|
+
# And its reduce item is S => S + M . | 0
|
181
|
+
# Then add child nodes corresponding to the rhs symbols
|
182
|
+
# And make M[?, 5] the current symbol
|
183
|
+
subject.insert_matched_symbol(state_tracker, builder)
|
184
|
+
expected_text = <<-SNIPPET
|
185
|
+
P[0, 5]
|
186
|
+
+- S[0, 5]
|
187
|
+
+- S[0, ?]
|
188
|
+
+- +[?, ?]: '(nil)'
|
189
|
+
+- M[?, 5]
|
190
|
+
SNIPPET
|
191
|
+
root_text = builder.root.to_string(0)
|
192
|
+
expect(root_text).to eq(expected_text.chomp)
|
193
|
+
expected_state = 'S => S + M . | 0'
|
194
|
+
expect(state_tracker.parse_state.to_s).to eq(expected_state)
|
195
|
+
expect(state_tracker.state_set_index).to eq(5)
|
196
|
+
expect(builder.current_node.to_string(0)).to eq('M[?, 5]')
|
197
|
+
|
198
|
+
# Second similar test
|
199
|
+
|
200
|
+
# Given matched symbol is M[?, 5]
|
201
|
+
# And its reduce item is M => M * T . | 2
|
202
|
+
# Then add child nodes corresponding to the rhs symbols
|
203
|
+
# And make T[?, 5] the current symbol
|
204
|
+
subject.insert_matched_symbol(state_tracker, builder)
|
205
|
+
expected_text = <<-SNIPPET
|
206
|
+
P[0, 5]
|
207
|
+
+- S[0, 5]
|
208
|
+
+- S[0, ?]
|
209
|
+
+- +[?, ?]: '(nil)'
|
210
|
+
+- M[2, 5]
|
211
|
+
+- M[2, ?]
|
212
|
+
+- *[?, ?]: '(nil)'
|
213
|
+
+- T[?, 5]
|
214
|
+
SNIPPET
|
215
|
+
root_text = builder.root.to_string(0)
|
216
|
+
expect(root_text).to eq(expected_text.chomp)
|
217
|
+
expected_state = 'M => M * T . | 2'
|
218
|
+
expect(state_tracker.parse_state.to_s).to eq(expected_state)
|
219
|
+
expect(state_tracker.state_set_index).to eq(5)
|
220
|
+
expect(builder.current_node.to_string(0)).to eq('T[?, 5]')
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
|
225
|
+
it 'should use a previous item for a terminal symbol' do
|
226
|
+
# Setup
|
227
|
+
(state_tracker, builder) = prepare_parse_tree(subject)
|
228
|
+
3.times do
|
229
|
+
subject.insert_matched_symbol(state_tracker, builder)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Given matched symbol is T[?, 5]
|
233
|
+
# And its reduce item is T => integer . | 4
|
234
|
+
# Then add child node corresponding to the rhs symbol
|
235
|
+
# And make integer[4, 5]: '(nil)' the current symbol
|
236
|
+
expected_text = <<-SNIPPET
|
237
|
+
P[0, 5]
|
238
|
+
+- S[0, 5]
|
239
|
+
+- S[0, ?]
|
240
|
+
+- +[?, ?]: '(nil)'
|
241
|
+
+- M[2, 5]
|
242
|
+
+- M[2, ?]
|
243
|
+
+- *[?, ?]: '(nil)'
|
244
|
+
+- T[4, 5]
|
245
|
+
+- integer[4, 5]: '(nil)'
|
246
|
+
SNIPPET
|
247
|
+
root_text = builder.root.to_string(0)
|
248
|
+
expect(root_text).to eq(expected_text.chomp)
|
249
|
+
expected_state = 'T => integer . | 4'
|
250
|
+
expect(state_tracker.parse_state.to_s).to eq(expected_state)
|
251
|
+
expect(state_tracker.state_set_index).to eq(5)
|
252
|
+
expect(builder.current_node.to_string(0)).to eq("integer[4, 5]: '(nil)'")
|
253
|
+
|
254
|
+
# Given current tree symbol is integer[4, 5]: '(nil)'
|
255
|
+
# And its previous item is T => . integer | 4
|
256
|
+
# Then attach the token to the terminal node
|
257
|
+
# And decrement the state index by one
|
258
|
+
# Make *[?, ?]: '(nil)' the current symbol
|
259
|
+
subject.insert_matched_symbol(state_tracker, builder)
|
260
|
+
expected_text = <<-SNIPPET
|
261
|
+
P[0, 5]
|
262
|
+
+- S[0, 5]
|
263
|
+
+- S[0, ?]
|
264
|
+
+- +[?, ?]: '(nil)'
|
265
|
+
+- M[2, 5]
|
266
|
+
+- M[2, ?]
|
267
|
+
+- *[?, ?]: '(nil)'
|
268
|
+
+- T[4, 5]
|
269
|
+
+- integer[4, 5]: '4'
|
270
|
+
SNIPPET
|
271
|
+
root_text = builder.root.to_string(0)
|
272
|
+
expect(root_text).to eq(expected_text.chomp)
|
273
|
+
expected_state = 'T => . integer | 4'
|
274
|
+
expect(state_tracker.parse_state.to_s).to eq(expected_state)
|
275
|
+
expect(state_tracker.state_set_index).to eq(4)
|
276
|
+
next_symbol = "*[?, ?]: '(nil)'"
|
277
|
+
expect(builder.current_node.to_string(0)).to eq(next_symbol)
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'should handle [no symbol before dot, terminal tree node] case' do
|
281
|
+
# Setup
|
282
|
+
(state_tracker, builder) = prepare_parse_tree(subject)
|
283
|
+
4.times do
|
284
|
+
subject.insert_matched_symbol(state_tracker, builder)
|
285
|
+
end
|
286
|
+
|
287
|
+
# Given current tree symbol is *[?, ?]: '(nil)'
|
288
|
+
# And current dotted item is T => . integer | 4
|
289
|
+
# When one retrieves the parse state expecting the T
|
290
|
+
# Then new parse state is changed to: M => M * . T | 2
|
291
|
+
subject.insert_matched_symbol(state_tracker, builder)
|
292
|
+
|
293
|
+
expected_text = <<-SNIPPET
|
294
|
+
P[0, 5]
|
295
|
+
+- S[0, 5]
|
296
|
+
+- S[0, ?]
|
297
|
+
+- +[?, ?]: '(nil)'
|
298
|
+
+- M[2, 5]
|
299
|
+
+- M[2, ?]
|
300
|
+
+- *[?, ?]: '(nil)'
|
301
|
+
+- T[4, 5]
|
302
|
+
+- integer[4, 5]: '4'
|
303
|
+
SNIPPET
|
304
|
+
root_text = builder.root.to_string(0)
|
305
|
+
expect(root_text).to eq(expected_text.chomp)
|
306
|
+
expected_state = 'M => M * . T | 2'
|
307
|
+
expect(state_tracker.parse_state.to_s).to eq(expected_state)
|
308
|
+
expect(state_tracker.state_set_index).to eq(4)
|
309
|
+
next_symbol = "*[?, ?]: '(nil)'"
|
310
|
+
expect(builder.current_node.to_string(0)).to eq(next_symbol)
|
311
|
+
|
312
|
+
subject.insert_matched_symbol(state_tracker, builder)
|
313
|
+
next_symbol = 'M[2, ?]'
|
314
|
+
expect(builder.current_node.to_string(0)).to eq(next_symbol)
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'should handle the end of parse tree generation' do
|
318
|
+
# Begin setup
|
319
|
+
is_done = false
|
320
|
+
(state_tracker, builder) = prepare_parse_tree(subject)
|
321
|
+
16.times do
|
322
|
+
is_done = subject.insert_matched_symbol(state_tracker, builder)
|
323
|
+
end
|
324
|
+
|
325
|
+
expected_text = <<-SNIPPET
|
326
|
+
P[0, 5]
|
327
|
+
+- S[0, 5]
|
328
|
+
+- S[0, 1]
|
329
|
+
+- M[0, 1]
|
330
|
+
+- T[0, 1]
|
331
|
+
+- integer[0, 1]: '2'
|
332
|
+
+- +[1, 2]: '+'
|
333
|
+
+- M[2, 5]
|
334
|
+
+- M[2, 3]
|
335
|
+
+- T[2, 3]
|
336
|
+
+- integer[2, 3]: '3'
|
337
|
+
+- *[3, 4]: '*'
|
338
|
+
+- T[4, 5]
|
339
|
+
+- integer[4, 5]: '4'
|
340
|
+
SNIPPET
|
341
|
+
root_text = builder.root.to_string(0)
|
342
|
+
expect(root_text).to eq(expected_text.chomp)
|
343
|
+
|
344
|
+
expected_state = 'T => . integer | 0'
|
345
|
+
expect(state_tracker.parse_state.to_s).to eq(expected_state)
|
346
|
+
expect(state_tracker.state_set_index).to eq(0)
|
347
|
+
expect(is_done).to eq(true)
|
348
|
+
end
|
349
|
+
|
350
|
+
|
142
351
|
|
143
352
|
it 'should build the parse tree for a simple non-ambiguous grammar' do
|
144
353
|
parser = EarleyParser.new(sample_grammar1)
|
@@ -155,76 +364,24 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
155
364
|
expect(ptree).to be_kind_of(PTree::ParseTree)
|
156
365
|
|
157
366
|
# Expect parse tree:
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
(
|
176
|
-
expect(node_s.symbol).to eq(grm_symbol('S'))
|
177
|
-
expect(node_s.range).to eq(low: 0, high: 1)
|
178
|
-
expect(node_s.children.size).to eq(1)
|
179
|
-
expect(node_plus.symbol).to eq(grm_symbol('+'))
|
180
|
-
expect(node_plus.range).to eq(low: 0, high: 1) # TODO: fix this
|
181
|
-
expect(node_plus.token.lexeme). to eq('+')
|
182
|
-
expect(node_m.symbol).to eq(grm_symbol('M'))
|
183
|
-
expect(node_m.range).to eq(low: 2, high: 5)
|
184
|
-
expect(node_m.children.size).to eq(3)
|
185
|
-
|
186
|
-
node = node_s.children[0] # M
|
187
|
-
expect(node.symbol).to eq(grm_symbol('M'))
|
188
|
-
expect(node.range).to eq([0, 1])
|
189
|
-
expect(node.children.size).to eq(1)
|
190
|
-
|
191
|
-
node = node.children[0] # T
|
192
|
-
expect(node.symbol).to eq(grm_symbol('T'))
|
193
|
-
expect(node.range).to eq([0, 1])
|
194
|
-
expect(node.children.size).to eq(1)
|
195
|
-
|
196
|
-
node = node.children[0] # integer(2)
|
197
|
-
expect(node.symbol).to eq(grm_symbol('integer'))
|
198
|
-
expect(node.range).to eq([0, 1])
|
199
|
-
expect(node.token.lexeme).to eq('2')
|
200
|
-
|
201
|
-
(node_m2, node_star, node_t3) = node_m.children
|
202
|
-
expect(node_m2.symbol).to eq(grm_symbol('M'))
|
203
|
-
expect(node_m2.range).to eq([2, 3])
|
204
|
-
expect(node_m2.children.size).to eq(1)
|
205
|
-
|
206
|
-
node_t2 = node_m2.children[0] # T
|
207
|
-
expect(node_t2.symbol).to eq(grm_symbol('T'))
|
208
|
-
expect(node_t2.range).to eq([2, 3])
|
209
|
-
expect(node_t2.children.size).to eq(1)
|
210
|
-
|
211
|
-
node = node_t2.children[0] # integer(3)
|
212
|
-
expect(node.symbol).to eq(grm_symbol('integer'))
|
213
|
-
expect(node.range).to eq([2, 3])
|
214
|
-
expect(node.token.lexeme).to eq('3')
|
215
|
-
|
216
|
-
expect(node_star.symbol).to eq(grm_symbol('*'))
|
217
|
-
expect(node_star.range).to eq([2, 3]) # Fix this
|
218
|
-
expect(node_star.token.lexeme). to eq('*')
|
219
|
-
|
220
|
-
expect(node_t3.symbol).to eq(grm_symbol('T'))
|
221
|
-
expect(node_t3.range).to eq([4, 5])
|
222
|
-
expect(node_t3.children.size).to eq(1)
|
223
|
-
|
224
|
-
node = node_t3.children[0] # integer(4)
|
225
|
-
expect(node.symbol).to eq(grm_symbol('integer'))
|
226
|
-
expect(node.range).to eq([4, 5])
|
227
|
-
expect(node.token.lexeme).to eq('4')
|
367
|
+
expected_text = <<-SNIPPET
|
368
|
+
P[0, 5]
|
369
|
+
+- S[0, 5]
|
370
|
+
+- S[0, 1]
|
371
|
+
+- M[0, 1]
|
372
|
+
+- T[0, 1]
|
373
|
+
+- integer[0, 1]: '2'
|
374
|
+
+- +[1, 2]: '+'
|
375
|
+
+- M[2, 5]
|
376
|
+
+- M[2, 3]
|
377
|
+
+- T[2, 3]
|
378
|
+
+- integer[2, 3]: '3'
|
379
|
+
+- *[3, 4]: '*'
|
380
|
+
+- T[4, 5]
|
381
|
+
+- integer[4, 5]: '4'
|
382
|
+
SNIPPET
|
383
|
+
actual = ptree.root.to_string(0)
|
384
|
+
expect(actual).to eq(expected_text.chomp)
|
228
385
|
end
|
229
386
|
end # context
|
230
387
|
end # describe
|