rley 0.2.00 → 0.2.01
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +4 -0
- data/examples/parsers/parsing_L0.rb +0 -1
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/formatter/base_formatter.rb +0 -1
- data/lib/rley/parser/chart.rb +1 -1
- data/lib/rley/parser/parse_state_tracker.rb +3 -3
- data/lib/rley/parser/parse_tree_builder.rb +178 -181
- data/lib/rley/parser/parsing.rb +14 -16
- data/lib/rley/ptree/parse_tree.rb +0 -1
- data/lib/rley/syntax/grammar.rb +15 -15
- data/spec/rley/parser/parse_tree_builder_spec.rb +179 -179
- data/spec/rley/parser/parsing_spec.rb +3 -4
- data/spec/rley/support/grammar_b_expr_helper.rb +13 -13
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NzlkNmE1NzA2OWEzMzVlNTNjOTBkNDI3ZGQ1MDljYTI4M2I3ZmNhNQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NGNlMGZhM2UyNjNiYmMwZTBjMGFiNmEwM2U3NWI2MjUxMzFlYmY5YQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MjA1YjJmYjRiNjFjZTViNmFhMDk4ZjA3MDVkOWJjNDBhMjExYjhkMDg5MTUy
|
10
|
+
ZTJkNzhjN2I1NjY5M2I2MWY5NzEyZmJkMjJiZTIwYjEyNTk2MzFmN2ViZWU2
|
11
|
+
YTYzYzkzNTUxYWE1MjU3NGI5NTk3YzAzNmI0MGNkNGExYmEyMDE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
N2U4Y2I5MjA0YWQyMWZmMmU4MDM0N2ZlNGU1NzA5MzViMDAxZGI1YTc5Zjcy
|
14
|
+
M2MzMDFmOWU5ZGI1NjllODliZTkwNjE2MmMyOTQzZjRkODBjNzUwNDg2MDJl
|
15
|
+
NmExN2FiMjA4ZjBhNTdkNDE4YWUxMzgxZTQ1Y2M4ZDE0OTE4NGY=
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
### 0.2.01 / 2015-01-03
|
2
|
+
* [CHANGE] File `.rubocop.yml`: AbcMetric setting relaxed.
|
3
|
+
* [CHANGE] Fixed most style offenses reported by Rubocop.
|
4
|
+
|
1
5
|
### 0.2.00 / 2015-01-03
|
2
6
|
Version number bump: major re-design of the parse tree generation.
|
3
7
|
* [NEW] Class `ParseTreeBuilder`: builder for creating parse tree.
|
@@ -109,7 +109,6 @@ puts "Parsing success? #{result.success?}"
|
|
109
109
|
########################################
|
110
110
|
# Step 6. Generate a parse tree from the parse result
|
111
111
|
ptree = result.parse_tree
|
112
|
-
pp ptree
|
113
112
|
|
114
113
|
########################################
|
115
114
|
# Step 7. Render the parse tree (in JSON)
|
data/lib/rley/constants.rb
CHANGED
data/lib/rley/parser/chart.rb
CHANGED
@@ -27,7 +27,7 @@ module Rley # This module is used as a namespace
|
|
27
27
|
|
28
28
|
# Return the index value of the last non-empty state set.
|
29
29
|
def last_index()
|
30
|
-
first_empty = state_sets.find_index
|
30
|
+
first_empty = state_sets.find_index(&:empty?)
|
31
31
|
if first_empty.nil?
|
32
32
|
index = state_sets.size - 1
|
33
33
|
else
|
@@ -26,12 +26,12 @@ module Rley # This module is used as a namespace
|
|
26
26
|
# Write accessor. Set the given parse state as the current one.
|
27
27
|
def parse_state=(aParseState)
|
28
28
|
@parse_state = aParseState
|
29
|
-
|
29
|
+
processed_states[parse_state] = true
|
30
30
|
end
|
31
31
|
|
32
32
|
# Take the first provided state that wasn't processed yet.
|
33
33
|
def select_state(theStates)
|
34
|
-
a_state = theStates.find { |st| !
|
34
|
+
a_state = theStates.find { |st| !processed_states.include?(st) }
|
35
35
|
self.parse_state = a_state
|
36
36
|
end
|
37
37
|
|
@@ -46,7 +46,7 @@ module Rley # This module is used as a namespace
|
|
46
46
|
|
47
47
|
# Notification that one begins with the previous state set
|
48
48
|
def to_prev_state_set()
|
49
|
-
self.state_set_index =
|
49
|
+
self.state_set_index = state_set_index - 1
|
50
50
|
end
|
51
51
|
end # class
|
52
52
|
end # module
|
@@ -1,181 +1,178 @@
|
|
1
|
-
|
2
|
-
require_relative '../ptree/
|
3
|
-
require_relative '../ptree/
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
module
|
8
|
-
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
attr_reader(:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
start_symbol
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
prod
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
curr_node
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
path_increment
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
(
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
current_path <<
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
when Syntax::
|
112
|
-
new_node = PTree::
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
curr_node
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
end # module
|
180
|
-
|
181
|
-
# End of file
|
1
|
+
require_relative '../ptree/terminal_node'
|
2
|
+
require_relative '../ptree/non_terminal_node'
|
3
|
+
require_relative '../ptree/parse_tree'
|
4
|
+
|
5
|
+
|
6
|
+
module Rley # This module is used as a namespace
|
7
|
+
module Parser # This module is used as a namespace
|
8
|
+
# Builder GoF pattern. Builder pattern builds a complex object
|
9
|
+
# (say, a parse tree) from simpler objects (terminal and non-terminal
|
10
|
+
# nodes) and using a step by step approach.
|
11
|
+
class ParseTreeBuilder
|
12
|
+
attr_reader(:root)
|
13
|
+
attr_reader(:current_path)
|
14
|
+
|
15
|
+
def initialize(aStartProduction, aRange)
|
16
|
+
@current_path = []
|
17
|
+
start_symbol = aStartProduction.lhs
|
18
|
+
add_node(start_symbol, aRange)
|
19
|
+
use_production(aStartProduction, aRange)
|
20
|
+
move_down
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return the active node.
|
24
|
+
def current_node()
|
25
|
+
return current_path.last
|
26
|
+
end
|
27
|
+
|
28
|
+
# Factory method.
|
29
|
+
def parse_tree()
|
30
|
+
return PTree::ParseTree.new(root)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Given that the current node is also lhs of the production
|
35
|
+
# associated with the complete parse state,
|
36
|
+
# Then add the rhs constituents as child nodes of the current node.
|
37
|
+
# Assumption: current node is lhs of the production association
|
38
|
+
# with the parse state.
|
39
|
+
# @param aCompleteState [ParseState] A complete parse state
|
40
|
+
# (dot is at end of rhs)
|
41
|
+
def use_complete_state(aCompleteState)
|
42
|
+
prod = aCompleteState.dotted_rule.production
|
43
|
+
use_production(prod, { low: aCompleteState.origin })
|
44
|
+
end
|
45
|
+
|
46
|
+
# Given that the current node is a non-terminal
|
47
|
+
# Make its last child node the current node.
|
48
|
+
def move_down()
|
49
|
+
curr_node = current_node
|
50
|
+
unless curr_node.is_a?(PTree::NonTerminalNode)
|
51
|
+
msg = "Current node isn't a non-terminal node #{curr_node.class}"
|
52
|
+
fail StandardError, msg
|
53
|
+
end
|
54
|
+
children = curr_node.children
|
55
|
+
path_increment = [children.size - 1, children.last]
|
56
|
+
@current_path.concat(path_increment)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Make the predecessor of current node the
|
61
|
+
# new current node.
|
62
|
+
def move_back()
|
63
|
+
begin
|
64
|
+
if current_path.length == 1
|
65
|
+
msg = 'Cannot move further back'
|
66
|
+
fail StandardError, msg
|
67
|
+
end
|
68
|
+
(parent, pos) = current_path[-3, 2]
|
69
|
+
current_path.pop(2)
|
70
|
+
if pos > 0
|
71
|
+
new_pos = pos - 1
|
72
|
+
new_curr_node = parent.children[new_pos]
|
73
|
+
current_path << new_pos
|
74
|
+
current_path << new_curr_node
|
75
|
+
end
|
76
|
+
end while pos == 0 && new_curr_node.is_a?(PTree::NonTerminalNode)
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# Add a child node to the current node.
|
81
|
+
def add_node(aSymbol, aRange)
|
82
|
+
# Create the node
|
83
|
+
a_node = new_node(aSymbol, aRange)
|
84
|
+
|
85
|
+
# Add it to the current node
|
86
|
+
add_child(a_node)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Set unbound endpoints of current node range
|
90
|
+
# to the given range.
|
91
|
+
def range=(aRange)
|
92
|
+
curr_node = current_node
|
93
|
+
return if curr_node.nil?
|
94
|
+
lower = low_bound(aRange)
|
95
|
+
unless lower.nil?
|
96
|
+
current_node.range = lower
|
97
|
+
if curr_node.is_a?(PTree::TerminalNode)
|
98
|
+
current_node.range = high_bound(lower[:low] + 1)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
upper = high_bound(aRange)
|
102
|
+
current_node.range = upper unless upper.nil?
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def new_node(aSymbol, aRange)
|
108
|
+
case aSymbol
|
109
|
+
when Syntax::Terminal
|
110
|
+
new_node = PTree::TerminalNode.new(aSymbol, aRange)
|
111
|
+
when Syntax::NonTerminal
|
112
|
+
new_node = PTree::NonTerminalNode.new(aSymbol, aRange)
|
113
|
+
end
|
114
|
+
|
115
|
+
return new_node
|
116
|
+
end
|
117
|
+
|
118
|
+
# Add children nodes to current one.
|
119
|
+
# The children correspond to the members of the rhs of the production.
|
120
|
+
def use_production(aProduction, aRange)
|
121
|
+
prod = aProduction
|
122
|
+
curr_node = current_node
|
123
|
+
|
124
|
+
if curr_node.symbol != prod.lhs
|
125
|
+
msg = "Current node is a #{curr_node.symbol} instead of #{prod.lhs}"
|
126
|
+
fail StandardError, msg
|
127
|
+
end
|
128
|
+
self.range = aRange
|
129
|
+
prod.rhs.each { |symb| add_node(symb, {}) }
|
130
|
+
|
131
|
+
return if curr_node.children.empty?
|
132
|
+
curr_node.children.first.range.assign(low: curr_node.range.low)
|
133
|
+
curr_node.children.last.range.assign(high: curr_node.range.high)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Add the given node as child node of current node
|
137
|
+
def add_child(aNode)
|
138
|
+
curr_node = current_node
|
139
|
+
|
140
|
+
if curr_node.nil?
|
141
|
+
self.root = aNode
|
142
|
+
else
|
143
|
+
curr_node.children << aNode
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Set the root node of the tree.
|
148
|
+
def root=(aNode)
|
149
|
+
@root = aNode
|
150
|
+
@current_path = [ @root ]
|
151
|
+
root.range = low_bound(0)
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
def low_bound(aRange)
|
156
|
+
result = case aRange
|
157
|
+
when Fixnum then aRange
|
158
|
+
when Hash then aRange[:low]
|
159
|
+
when PTree::TokenRange then aRange.low
|
160
|
+
end
|
161
|
+
|
162
|
+
return { low: result }
|
163
|
+
end
|
164
|
+
|
165
|
+
def high_bound(aRange)
|
166
|
+
result = case aRange
|
167
|
+
when Fixnum then aRange
|
168
|
+
when Hash then aRange[:high]
|
169
|
+
when PTree::TokenRange then aRange.high
|
170
|
+
end
|
171
|
+
|
172
|
+
return { high: result }
|
173
|
+
end
|
174
|
+
end # class
|
175
|
+
end # module
|
176
|
+
end # module
|
177
|
+
|
178
|
+
# End of file
|
data/lib/rley/parser/parsing.rb
CHANGED
@@ -123,11 +123,11 @@ module Rley # This module is used as a namespace
|
|
123
123
|
return expecting if !toSort || expecting.size < 2
|
124
124
|
|
125
125
|
# Put predicted states ahead
|
126
|
-
(predicted, others) = expecting.partition
|
126
|
+
(predicted, others) = expecting.partition(&:predicted?)
|
127
127
|
|
128
128
|
# Sort state in reverse order of their origin value
|
129
129
|
[predicted, others].each do |set|
|
130
|
-
set.sort! { |a,b| b.origin <=> a.origin }
|
130
|
+
set.sort! { |a, b| b.origin <=> a.origin }
|
131
131
|
end
|
132
132
|
|
133
133
|
return predicted + others
|
@@ -158,13 +158,13 @@ module Rley # This module is used as a namespace
|
|
158
158
|
|
159
159
|
# A terminal symbol is on the left of dot.
|
160
160
|
# Go to the predecessor state for the given terminal
|
161
|
-
def predecessor_state_terminal(
|
161
|
+
def predecessor_state_terminal(_a_symb, aStateTracker, aTreeBuilder)
|
162
162
|
aTreeBuilder.current_node.range = { low: aStateTracker.state_set_index }
|
163
163
|
link_node_to_token(aTreeBuilder, aStateTracker.state_set_index)
|
164
164
|
unless aTreeBuilder.current_node.is_a?(PTree::TerminalNode)
|
165
165
|
pp aTreeBuilder.root
|
166
166
|
pp aTreeBuilder.current_node
|
167
|
-
fail StandardError,
|
167
|
+
fail StandardError, 'Expected terminal node'
|
168
168
|
end
|
169
169
|
aTreeBuilder.move_back
|
170
170
|
state_set = chart[aStateTracker.state_set_index]
|
@@ -174,12 +174,12 @@ module Rley # This module is used as a namespace
|
|
174
174
|
|
175
175
|
|
176
176
|
# Retrieve a complete state with given symbol as lhs.
|
177
|
-
def completed_state_for(a_symb,
|
178
|
-
new_states = chart[
|
179
|
-
|
180
|
-
aTreeBuilder.range = { high:
|
181
|
-
aTreeBuilder.use_complete_state(
|
182
|
-
link_node_to_token(aTreeBuilder,
|
177
|
+
def completed_state_for(a_symb, aTracker, aTreeBuilder)
|
178
|
+
new_states = chart[aTracker.state_set_index].states_rewriting(a_symb)
|
179
|
+
aTracker.select_state(new_states)
|
180
|
+
aTreeBuilder.range = { high: aTracker.state_set_index }
|
181
|
+
aTreeBuilder.use_complete_state(aTracker.parse_state)
|
182
|
+
link_node_to_token(aTreeBuilder, aTracker.state_set_index - 1)
|
183
183
|
aTreeBuilder.move_down
|
184
184
|
end
|
185
185
|
|
@@ -204,10 +204,10 @@ module Rley # This module is used as a namespace
|
|
204
204
|
# If the current node is a terminal node
|
205
205
|
# then link the token to that node
|
206
206
|
def link_node_to_token(aTreeBuilder, aStateSetIndex)
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
207
|
+
return unless aTreeBuilder.current_node.is_a?(PTree::TerminalNode)
|
208
|
+
|
209
|
+
a_node = aTreeBuilder.current_node
|
210
|
+
a_node.token = tokens[aStateSetIndex] unless a_node.token
|
211
211
|
end
|
212
212
|
|
213
213
|
# Factory method. Initializes a ParseTreeBuilder object
|
@@ -216,8 +216,6 @@ module Rley # This module is used as a namespace
|
|
216
216
|
start_production = chart.start_dotted_rule.production
|
217
217
|
return ParseTreeBuilder.new(start_production, full_range)
|
218
218
|
end
|
219
|
-
|
220
|
-
|
221
219
|
end # class
|
222
220
|
end # module
|
223
221
|
end # module
|
data/lib/rley/syntax/grammar.rb
CHANGED
@@ -20,7 +20,7 @@ module Rley # This module is used as a namespace
|
|
20
20
|
|
21
21
|
# The list of grammar symbols in the language.
|
22
22
|
attr_reader(:symbols)
|
23
|
-
|
23
|
+
|
24
24
|
# A Hash with pairs of the kind: symbol name => grammar symbol
|
25
25
|
attr_reader(:name2symbol)
|
26
26
|
|
@@ -41,7 +41,7 @@ module Rley # This module is used as a namespace
|
|
41
41
|
def non_terminals()
|
42
42
|
return symbols.select { |s| s.kind_of?(NonTerminal) }
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
# @return [Production] The start production of the grammar (i.e.
|
46
46
|
# the rule that specifies the syntax for the start symbol.
|
47
47
|
def start_production()
|
@@ -66,17 +66,17 @@ module Rley # This module is used as a namespace
|
|
66
66
|
aProduction.rhs.each { |symb| add_symbol(symb) }
|
67
67
|
end
|
68
68
|
|
69
|
-
|
69
|
+
|
70
70
|
# For each non-terminal determine whether it is nullable or not.
|
71
71
|
# A nullable nonterminal is a nonterminal that can match an empty string.
|
72
72
|
def compute_nullable()
|
73
73
|
non_terminals.each { |nterm| nterm.nullable = false }
|
74
74
|
nullable_sets = [ direct_nullable ]
|
75
|
-
|
75
|
+
|
76
76
|
# Drop productions with one terminal in rhs or with a nullable lhs
|
77
77
|
filtered_rules = rules.reject do |prod|
|
78
|
-
prod.lhs.nullable? || prod.rhs.find do |symb|
|
79
|
-
symb.kind_of?(Terminal)
|
78
|
+
prod.lhs.nullable? || prod.rhs.find do |symb|
|
79
|
+
symb.kind_of?(Terminal)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -96,8 +96,8 @@ module Rley # This module is used as a namespace
|
|
96
96
|
nullable_sets[i] = nullable_sets[i - 1].merge(new_nullables)
|
97
97
|
end
|
98
98
|
end
|
99
|
-
|
100
|
-
|
99
|
+
|
100
|
+
|
101
101
|
# Return the set of nonterminals which have one of their
|
102
102
|
# production rules empty
|
103
103
|
def direct_nullable()
|
@@ -108,18 +108,18 @@ module Rley # This module is used as a namespace
|
|
108
108
|
prod.lhs.nullable = true
|
109
109
|
nullable << prod.lhs
|
110
110
|
end
|
111
|
-
|
111
|
+
|
112
112
|
return nullable
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
private
|
116
|
-
|
116
|
+
|
117
117
|
def add_symbol(aSymbol)
|
118
118
|
its_name = aSymbol.name
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
119
|
+
return if name2symbol.include? its_name
|
120
|
+
|
121
|
+
@symbols << aSymbol
|
122
|
+
@name2symbol[its_name] = aSymbol
|
123
123
|
end
|
124
124
|
end # class
|
125
125
|
end # module
|
@@ -1,179 +1,179 @@
|
|
1
|
-
require_relative '../../spec_helper'
|
2
|
-
require_relative '../../../lib/rley/parser/token'
|
3
|
-
require_relative '../../../lib/rley/parser/earley_parser'
|
4
|
-
require_relative '../../../lib/rley/parser/parsing'
|
5
|
-
# Load the class under test
|
6
|
-
require_relative '../../../lib/rley/parser/parse_tree_builder'
|
7
|
-
require_relative '../support/grammar_abc_helper'
|
8
|
-
|
9
|
-
module Rley # Open this namespace to avoid module qualifier prefixes
|
10
|
-
module Parser # Open this namespace to avoid module qualifier prefixes
|
11
|
-
describe ParseTreeBuilder do
|
12
|
-
include GrammarABCHelper # Mix-in module with builder for grammar abc
|
13
|
-
|
14
|
-
let(:grammar_abc) do
|
15
|
-
builder = grammar_abc_builder
|
16
|
-
builder.grammar
|
17
|
-
end
|
18
|
-
|
19
|
-
let(:capital_a) { grammar_abc.name2symbol['A'] }
|
20
|
-
let(:capital_s) { grammar_abc.name2symbol['S'] }
|
21
|
-
let(:small_a) { grammar_abc.name2symbol['a'] }
|
22
|
-
let(:small_b) { grammar_abc.name2symbol['b'] }
|
23
|
-
let(:small_c) { grammar_abc.name2symbol['c'] }
|
24
|
-
|
25
|
-
let(:start_prod) { grammar_abc.start_production }
|
26
|
-
|
27
|
-
let(:tokens_abc) do
|
28
|
-
%w(a a b c c).map do |letter|
|
29
|
-
Token.new(letter, grammar_abc.name2symbol[letter])
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
let(:sample_parsing) do
|
34
|
-
parser = EarleyParser.new(grammar_abc)
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
|
-
subject { ParseTreeBuilder.new(start_prod, {low: 0, high: 5}) }
|
39
|
-
|
40
|
-
context 'Initialization:' do
|
41
|
-
it 'should be created with a proposition and a range' do
|
42
|
-
expect { ParseTreeBuilder.new(start_prod, {}) }.not_to raise_error
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'should have a root node at start' do
|
46
|
-
expect(subject.root.symbol).to eq(capital_s)
|
47
|
-
end
|
48
|
-
|
49
|
-
it
|
50
|
-
expect(subject.current_path).not_to be_empty
|
51
|
-
end
|
52
|
-
|
53
|
-
it
|
54
|
-
expect(subject.current_node.symbol).to eq(capital_a)
|
55
|
-
end
|
56
|
-
end # context
|
57
|
-
|
58
|
-
context 'Adding nodes to parse tree:' do
|
59
|
-
it 'should process parse state for a non-terminal node' do
|
60
|
-
# Expectation:
|
61
|
-
# S[0, 5]
|
62
|
-
# +- A[0,5]
|
63
|
-
expect(subject.root.symbol).to eq(capital_s)
|
64
|
-
expect(subject.root.children.size).to eq(1)
|
65
|
-
child1 = subject.root.children[0]
|
66
|
-
expect(child1.symbol).to eq(capital_a)
|
67
|
-
expect(child1.range.low).to eq(0)
|
68
|
-
expect(child1.range.high).to eq(5)
|
69
|
-
expect(subject.current_node).to eq(child1)
|
70
|
-
|
71
|
-
# Add children to A
|
72
|
-
other_state = sample_parsing.chart.state_sets.last.states.first
|
73
|
-
subject.use_complete_state(other_state)
|
74
|
-
|
75
|
-
# Tree is:
|
76
|
-
# S[0,5]
|
77
|
-
# +- A[0,5]
|
78
|
-
# +- a[0, ?]
|
79
|
-
# +- A[?, ?]
|
80
|
-
# +- c[?, 5]
|
81
|
-
expect(child1.children.size).to eq(3) # a A c
|
82
|
-
%w(a A c).each_with_index do |letter, i|
|
83
|
-
grm_symbol = grammar_abc.name2symbol[letter]
|
84
|
-
expect(child1.children[i].symbol).to eq(grm_symbol)
|
85
|
-
end
|
86
|
-
expect(child1.children[0].range.low).to eq(0)
|
87
|
-
expect(child1.children[-1].range.high).to eq(5)
|
88
|
-
|
89
|
-
subject.move_down # ... to c
|
90
|
-
subject.range = {low: 4}
|
91
|
-
expect(child1.children[-1].range.low).to eq(4)
|
92
|
-
expect(child1.children.last).to eq(subject.current_node)
|
93
|
-
subject.move_back # ... to A
|
94
|
-
expect(subject.current_node).to eq(child1.children[1])
|
95
|
-
grand_child_A = subject.current_node
|
96
|
-
|
97
|
-
other_state = sample_parsing.chart.state_sets[4].first
|
98
|
-
subject.use_complete_state(other_state)
|
99
|
-
expect(grand_child_A.children.size).to eq(3) # a A c
|
100
|
-
%w(a A c).each_with_index do |letter, i|
|
101
|
-
grm_symbol = grammar_abc.name2symbol[letter]
|
102
|
-
expect(grand_child_A.children[i].symbol).to eq(grm_symbol)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end # context
|
106
|
-
|
107
|
-
context 'Moving the current node:' do
|
108
|
-
it 'should move down to last child' do
|
109
|
-
# Tree is:
|
110
|
-
# S[0,?]
|
111
|
-
# +- A[0,?]
|
112
|
-
|
113
|
-
# Add children to A
|
114
|
-
parse_state = sample_parsing.chart.state_sets.last.states.first
|
115
|
-
subject.use_complete_state(parse_state)
|
116
|
-
|
117
|
-
# Tree is:
|
118
|
-
# S[0,?]
|
119
|
-
# +- A[0,?]
|
120
|
-
# +- a[0, ?]
|
121
|
-
# +- A[?, ?]
|
122
|
-
# +- c[?, ?]
|
123
|
-
subject.move_down # ...to grand-child c
|
124
|
-
expect(subject.current_node.symbol).to eq(small_c)
|
125
|
-
|
126
|
-
|
127
|
-
subject.move_back # ...to grand-child A
|
128
|
-
expect(subject.current_node.symbol).to eq(capital_a)
|
129
|
-
|
130
|
-
# Add more children
|
131
|
-
other_state = sample_parsing.chart.state_sets[4].states.first
|
132
|
-
subject.use_complete_state(other_state)
|
133
|
-
|
134
|
-
# Tree is:
|
135
|
-
# S[0,?]
|
136
|
-
# +- A[0,?]
|
137
|
-
# +- a[0, ?]
|
138
|
-
# +- A[?, ?]
|
139
|
-
# +- a[?, ?]
|
140
|
-
# +- A[?, ?]
|
141
|
-
# +- c [?, ?]
|
142
|
-
# +- c[?, ?]
|
143
|
-
|
144
|
-
subject.move_down # ...to grand-grand-child c
|
145
|
-
expect(subject.current_node.symbol).to eq(small_c)
|
146
|
-
|
147
|
-
subject.move_back # ...to grand-grand-child A
|
148
|
-
expect(subject.current_node.symbol).to eq(capital_a)
|
149
|
-
|
150
|
-
subject.move_back # ...to grand-grand-child a
|
151
|
-
expect(subject.current_node.symbol).to eq(small_a)
|
152
|
-
|
153
|
-
subject.move_back # ...to grand-child A
|
154
|
-
expect(subject.current_node.symbol).to eq(capital_a)
|
155
|
-
|
156
|
-
subject.move_back # ...to grand-child a
|
157
|
-
expect(subject.current_node.symbol).to eq(small_a)
|
158
|
-
|
159
|
-
subject.move_back # ...to child A
|
160
|
-
expect(subject.current_node.symbol).to eq(capital_a)
|
161
|
-
|
162
|
-
subject.move_back # ...to S
|
163
|
-
expect(subject.current_node.symbol).to eq(capital_s)
|
164
|
-
end
|
165
|
-
end # context
|
166
|
-
|
167
|
-
context 'Parse tree building:' do
|
168
|
-
it 'should build a parse tree' do
|
169
|
-
expect(subject.parse_tree).to be_kind_of(PTree::ParseTree)
|
170
|
-
actual = subject.parse_tree
|
171
|
-
expect(actual.root).to eq(subject.root)
|
172
|
-
end
|
173
|
-
end # context
|
174
|
-
|
175
|
-
end # describe
|
176
|
-
end # module
|
177
|
-
end # module
|
178
|
-
|
179
|
-
# End of file
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require_relative '../../../lib/rley/parser/token'
|
3
|
+
require_relative '../../../lib/rley/parser/earley_parser'
|
4
|
+
require_relative '../../../lib/rley/parser/parsing'
|
5
|
+
# Load the class under test
|
6
|
+
require_relative '../../../lib/rley/parser/parse_tree_builder'
|
7
|
+
require_relative '../support/grammar_abc_helper'
|
8
|
+
|
9
|
+
module Rley # Open this namespace to avoid module qualifier prefixes
|
10
|
+
module Parser # Open this namespace to avoid module qualifier prefixes
|
11
|
+
describe ParseTreeBuilder do
|
12
|
+
include GrammarABCHelper # Mix-in module with builder for grammar abc
|
13
|
+
|
14
|
+
let(:grammar_abc) do
|
15
|
+
builder = grammar_abc_builder
|
16
|
+
builder.grammar
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:capital_a) { grammar_abc.name2symbol['A'] }
|
20
|
+
let(:capital_s) { grammar_abc.name2symbol['S'] }
|
21
|
+
let(:small_a) { grammar_abc.name2symbol['a'] }
|
22
|
+
let(:small_b) { grammar_abc.name2symbol['b'] }
|
23
|
+
let(:small_c) { grammar_abc.name2symbol['c'] }
|
24
|
+
|
25
|
+
let(:start_prod) { grammar_abc.start_production }
|
26
|
+
|
27
|
+
let(:tokens_abc) do
|
28
|
+
%w(a a b c c).map do |letter|
|
29
|
+
Token.new(letter, grammar_abc.name2symbol[letter])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
let(:sample_parsing) do
|
34
|
+
parser = EarleyParser.new(grammar_abc)
|
35
|
+
parser.parse(tokens_abc)
|
36
|
+
end
|
37
|
+
|
38
|
+
subject { ParseTreeBuilder.new(start_prod, { low: 0, high: 5 }) }
|
39
|
+
|
40
|
+
context 'Initialization:' do
|
41
|
+
it 'should be created with a proposition and a range' do
|
42
|
+
expect { ParseTreeBuilder.new(start_prod, {}) }.not_to raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should have a root node at start' do
|
46
|
+
expect(subject.root.symbol).to eq(capital_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should have current path at start' do
|
50
|
+
expect(subject.current_path).not_to be_empty
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should have current node at start' do
|
54
|
+
expect(subject.current_node.symbol).to eq(capital_a)
|
55
|
+
end
|
56
|
+
end # context
|
57
|
+
|
58
|
+
context 'Adding nodes to parse tree:' do
|
59
|
+
it 'should process parse state for a non-terminal node' do
|
60
|
+
# Expectation:
|
61
|
+
# S[0, 5]
|
62
|
+
# +- A[0,5]
|
63
|
+
expect(subject.root.symbol).to eq(capital_s)
|
64
|
+
expect(subject.root.children.size).to eq(1)
|
65
|
+
child1 = subject.root.children[0]
|
66
|
+
expect(child1.symbol).to eq(capital_a)
|
67
|
+
expect(child1.range.low).to eq(0)
|
68
|
+
expect(child1.range.high).to eq(5)
|
69
|
+
expect(subject.current_node).to eq(child1)
|
70
|
+
|
71
|
+
# Add children to A
|
72
|
+
other_state = sample_parsing.chart.state_sets.last.states.first
|
73
|
+
subject.use_complete_state(other_state)
|
74
|
+
|
75
|
+
# Tree is:
|
76
|
+
# S[0,5]
|
77
|
+
# +- A[0,5]
|
78
|
+
# +- a[0, ?]
|
79
|
+
# +- A[?, ?]
|
80
|
+
# +- c[?, 5]
|
81
|
+
expect(child1.children.size).to eq(3) # a A c
|
82
|
+
%w(a A c).each_with_index do |letter, i|
|
83
|
+
grm_symbol = grammar_abc.name2symbol[letter]
|
84
|
+
expect(child1.children[i].symbol).to eq(grm_symbol)
|
85
|
+
end
|
86
|
+
expect(child1.children[0].range.low).to eq(0)
|
87
|
+
expect(child1.children[-1].range.high).to eq(5)
|
88
|
+
|
89
|
+
subject.move_down # ... to c
|
90
|
+
subject.range = { low: 4 }
|
91
|
+
expect(child1.children[-1].range.low).to eq(4)
|
92
|
+
expect(child1.children.last).to eq(subject.current_node)
|
93
|
+
subject.move_back # ... to A
|
94
|
+
expect(subject.current_node).to eq(child1.children[1])
|
95
|
+
grand_child_A = subject.current_node
|
96
|
+
|
97
|
+
other_state = sample_parsing.chart.state_sets[4].first
|
98
|
+
subject.use_complete_state(other_state)
|
99
|
+
expect(grand_child_A.children.size).to eq(3) # a A c
|
100
|
+
%w(a A c).each_with_index do |letter, i|
|
101
|
+
grm_symbol = grammar_abc.name2symbol[letter]
|
102
|
+
expect(grand_child_A.children[i].symbol).to eq(grm_symbol)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end # context
|
106
|
+
|
107
|
+
context 'Moving the current node:' do
|
108
|
+
it 'should move down to last child' do
|
109
|
+
# Tree is:
|
110
|
+
# S[0,?]
|
111
|
+
# +- A[0,?]
|
112
|
+
|
113
|
+
# Add children to A
|
114
|
+
parse_state = sample_parsing.chart.state_sets.last.states.first
|
115
|
+
subject.use_complete_state(parse_state)
|
116
|
+
|
117
|
+
# Tree is:
|
118
|
+
# S[0,?]
|
119
|
+
# +- A[0,?]
|
120
|
+
# +- a[0, ?]
|
121
|
+
# +- A[?, ?]
|
122
|
+
# +- c[?, ?]
|
123
|
+
subject.move_down # ...to grand-child c
|
124
|
+
expect(subject.current_node.symbol).to eq(small_c)
|
125
|
+
|
126
|
+
|
127
|
+
subject.move_back # ...to grand-child A
|
128
|
+
expect(subject.current_node.symbol).to eq(capital_a)
|
129
|
+
|
130
|
+
# Add more children
|
131
|
+
other_state = sample_parsing.chart.state_sets[4].states.first
|
132
|
+
subject.use_complete_state(other_state)
|
133
|
+
|
134
|
+
# Tree is:
|
135
|
+
# S[0,?]
|
136
|
+
# +- A[0,?]
|
137
|
+
# +- a[0, ?]
|
138
|
+
# +- A[?, ?]
|
139
|
+
# +- a[?, ?]
|
140
|
+
# +- A[?, ?]
|
141
|
+
# +- c [?, ?]
|
142
|
+
# +- c[?, ?]
|
143
|
+
|
144
|
+
subject.move_down # ...to grand-grand-child c
|
145
|
+
expect(subject.current_node.symbol).to eq(small_c)
|
146
|
+
|
147
|
+
subject.move_back # ...to grand-grand-child A
|
148
|
+
expect(subject.current_node.symbol).to eq(capital_a)
|
149
|
+
|
150
|
+
subject.move_back # ...to grand-grand-child a
|
151
|
+
expect(subject.current_node.symbol).to eq(small_a)
|
152
|
+
|
153
|
+
subject.move_back # ...to grand-child A
|
154
|
+
expect(subject.current_node.symbol).to eq(capital_a)
|
155
|
+
|
156
|
+
subject.move_back # ...to grand-child a
|
157
|
+
expect(subject.current_node.symbol).to eq(small_a)
|
158
|
+
|
159
|
+
subject.move_back # ...to child A
|
160
|
+
expect(subject.current_node.symbol).to eq(capital_a)
|
161
|
+
|
162
|
+
subject.move_back # ...to S
|
163
|
+
expect(subject.current_node.symbol).to eq(capital_s)
|
164
|
+
end
|
165
|
+
end # context
|
166
|
+
|
167
|
+
context 'Parse tree building:' do
|
168
|
+
it 'should build a parse tree' do
|
169
|
+
expect(subject.parse_tree).to be_kind_of(PTree::ParseTree)
|
170
|
+
actual = subject.parse_tree
|
171
|
+
expect(actual.root).to eq(subject.root)
|
172
|
+
end
|
173
|
+
end # context
|
174
|
+
|
175
|
+
end # describe
|
176
|
+
end # module
|
177
|
+
end # module
|
178
|
+
|
179
|
+
# End of file
|
@@ -116,7 +116,6 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
116
116
|
expect(new_state.dotted_rule).to eq(item1)
|
117
117
|
expect(new_state.origin).to eq(0)
|
118
118
|
end
|
119
|
-
|
120
119
|
end # context
|
121
120
|
|
122
121
|
context 'Parse tree building:' do
|
@@ -175,13 +174,13 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
175
174
|
|
176
175
|
(node_s, node_plus, node_m) = node.children
|
177
176
|
expect(node_s.symbol).to eq(grm_symbol('S'))
|
178
|
-
expect(node_s.range).to eq(
|
177
|
+
expect(node_s.range).to eq(low: 0, high: 1)
|
179
178
|
expect(node_s.children.size).to eq(1)
|
180
179
|
expect(node_plus.symbol).to eq(grm_symbol('+'))
|
181
|
-
expect(node_plus.range).to eq(
|
180
|
+
expect(node_plus.range).to eq(low: 0, high: 1) # TODO: fix this
|
182
181
|
expect(node_plus.token.lexeme). to eq('+')
|
183
182
|
expect(node_m.symbol).to eq(grm_symbol('M'))
|
184
|
-
expect(node_m.range).to eq(
|
183
|
+
expect(node_m.range).to eq(low: 2, high: 5)
|
185
184
|
expect(node_m.children.size).to eq(3)
|
186
185
|
|
187
186
|
node = node_s.children[0] # M
|
@@ -21,19 +21,19 @@ module GrammarBExprHelper
|
|
21
21
|
|
22
22
|
# Basic expression tokenizer
|
23
23
|
def expr_tokenizer(aText, aGrammar)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
tokens = aText.scan(/\S+/).map do |lexeme|
|
25
|
+
case lexeme
|
26
|
+
when '+', '*'
|
27
|
+
terminal = aGrammar.name2symbol[lexeme]
|
28
|
+
when /^[-+]?\d+$/
|
29
|
+
terminal = aGrammar.name2symbol['integer']
|
30
|
+
else
|
31
|
+
msg = "Unknown input text '#{lexeme}'"
|
32
|
+
fail StandardError, msg
|
33
|
+
end
|
34
|
+
Rley::Parser::Token.new(lexeme, terminal)
|
33
35
|
end
|
34
|
-
|
36
|
+
|
37
|
+
return tokens
|
35
38
|
end
|
36
|
-
|
37
|
-
return tokens
|
38
|
-
end
|
39
39
|
end # module
|