dendroid 0.2.02 → 0.2.04
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 +21 -0
- data/lib/dendroid/formatters/bracket_notation.rb +1 -1
- data/lib/dendroid/grm_analysis/dotted_item.rb +4 -4
- data/lib/dendroid/lexical/literal.rb +5 -0
- data/lib/dendroid/lexical/token.rb +5 -0
- data/lib/dendroid/parsing/and_node.rb +25 -3
- data/lib/dendroid/parsing/chart_walker.rb +278 -184
- data/lib/dendroid/parsing/composite_parse_node.rb +5 -0
- data/lib/dendroid/parsing/empty_rule_node.rb +11 -1
- data/lib/dendroid/parsing/or_node.rb +22 -2
- data/lib/dendroid/parsing/parse_node.rb +17 -4
- data/lib/dendroid/parsing/parse_tree_visitor.rb +1 -1
- data/lib/dendroid/parsing/terminal_node.rb +5 -2
- data/lib/dendroid/parsing/walk_progress.rb +50 -9
- data/lib/dendroid/recognizer/e_item.rb +3 -1
- data/lib/dendroid/syntax/rule.rb +1 -1
- data/lib/dendroid.rb +0 -2
- data/spec/dendroid/lexical/literal_spec.rb +5 -1
- data/spec/dendroid/lexical/token_spec.rb +4 -0
- data/spec/dendroid/parsing/chart_walker_spec.rb +76 -84
- data/spec/dendroid/parsing/composite_parse_node_spec.rb +37 -0
- data/spec/dendroid/parsing/empty_rule_node_spec.rb +50 -0
- data/spec/dendroid/parsing/parse_node_spec.rb +25 -0
- data/spec/dendroid/parsing/terminal_node_spec.rb +4 -4
- data/spec/dendroid/parsing/walk_progress_spec.rb +61 -0
- data/version.txt +1 -1
- metadata +6 -2
@@ -4,58 +4,160 @@ require_relative 'walk_progress'
|
|
4
4
|
|
5
5
|
module Dendroid
|
6
6
|
module Parsing
|
7
|
+
# Keeps track of the visited chart entries in order to implement
|
8
|
+
# the sharing of parse nodes.
|
9
|
+
class WalkContext
|
10
|
+
# Mapping chart item => ParseNode for the current item set.
|
11
|
+
# @return [Hash{Dendroid::Recognizer::EItem => ParseNode}]
|
12
|
+
attr_reader :entry2node
|
13
|
+
|
14
|
+
# @return [Hash{Syntax::Token => TerminalNode}]
|
15
|
+
attr_reader :token2node
|
16
|
+
|
17
|
+
# @return [Hash{OrNode => true}]
|
18
|
+
attr_reader :or_nodes_crossed
|
19
|
+
|
20
|
+
# @return [Hash{Parsing::ParseNode => Array<Dendroid::Parsing::WalkProgress>}]
|
21
|
+
attr_reader :sharing
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@entry2node = {}
|
25
|
+
@token2node = {}
|
26
|
+
@or_nodes_crossed = {}
|
27
|
+
@sharing = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Was the given chart entry already encountered?
|
31
|
+
# @param anEItem [Dendroid::Recognizer::EItem] chart entry to test
|
32
|
+
def known_entry?(anEItem)
|
33
|
+
entry2node.include?(anEItem)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Make the given chart entry the new current item and
|
37
|
+
# mark its related node as known (visited)
|
38
|
+
# @param walk_progress [Dendroid::Parsing::WalkProgress]
|
39
|
+
# @param anEItem [Dendroid::Recognizer::EItem]
|
40
|
+
# @param aNode [Dendroid::Parsing::ParseNode]
|
41
|
+
def advance(walk_progress, anEItem, aNode)
|
42
|
+
walk_progress.curr_item = anEItem
|
43
|
+
entry2node[anEItem] = aNode
|
44
|
+
end
|
45
|
+
|
46
|
+
# For a given token, make a terminal node a child of the current parent.
|
47
|
+
# @param walk_progress [Dendroid::Parsing::WalkProgress]
|
48
|
+
# @param token [Dendroid::Lexical::Token]
|
49
|
+
def add_terminal_node(walk_progress, token)
|
50
|
+
if token2node.include? token
|
51
|
+
walk_progress.add_child_node(token2node[token])
|
52
|
+
walk_progress.curr_rank -= 1
|
53
|
+
else
|
54
|
+
new_node = walk_progress.add_terminal_node(token)
|
55
|
+
token2node[token] = new_node
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add an and node as a child of current parent of given walk progress
|
60
|
+
# @param walk_progress [Dendroid::Parsing::WalkProgress]
|
61
|
+
# @param anEItem [Dendroid::Recognizer::EItem]
|
62
|
+
def add_and_node(walk_progress, anEItem)
|
63
|
+
new_node = walk_progress.push_and_node(anEItem)
|
64
|
+
advance(walk_progress, anEItem, new_node)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check whether the given node was already seen.
|
68
|
+
# If yes, set the state of the walk progress to Complete
|
69
|
+
# @param walk_progress [Dendroid::Parsing::WalkProgress]
|
70
|
+
# @param anOrNode [Dendroid::Parsing::OrNode]
|
71
|
+
# @return [Boolean] true if the walk progress is in Complete state
|
72
|
+
def join_or_node(walk_progress, anOrNode)
|
73
|
+
already_crossed = or_nodes_crossed.include?(anOrNode)
|
74
|
+
if already_crossed
|
75
|
+
walk_progress.state = :Complete
|
76
|
+
else
|
77
|
+
or_nodes_crossed[anOrNode] = true
|
78
|
+
end
|
79
|
+
|
80
|
+
already_crossed
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param anEItem [Dendroid::Recognizer::EItem]
|
84
|
+
# @param walk_progress [Dendroid::Parsing::WalkProgress]
|
85
|
+
def start_delegation(anEItem, walk_progress)
|
86
|
+
shared_node = entry2node[anEItem]
|
87
|
+
if sharing.include? shared_node
|
88
|
+
sharing[shared_node] << walk_progress
|
89
|
+
else
|
90
|
+
sharing[shared_node] = [walk_progress]
|
91
|
+
end
|
92
|
+
walk_progress.add_child_node(shared_node)
|
93
|
+
walk_progress.parents.push(shared_node)
|
94
|
+
walk_progress.state = :Delegating
|
95
|
+
end
|
96
|
+
|
97
|
+
# If the given node is shared by other WalkProgress, update them
|
98
|
+
# with the advancement of the provided WalkProgress & dissolve the delegation
|
99
|
+
# @param aNode [Dendroid::Parsing::ParseNode]
|
100
|
+
# @param walk_progress [Dendroid::Parsing::WalkProgress]
|
101
|
+
# @param desired_state [Symbol] New state of the delegating walk progresses
|
102
|
+
def stop_delegation(aNode, walk_progress, desired_state)
|
103
|
+
if sharing.include? aNode
|
104
|
+
delegating = sharing[aNode]
|
105
|
+
unless delegating.include? walk_progress
|
106
|
+
delegating.each do |dlg|
|
107
|
+
dlg.curr_rank = walk_progress.curr_rank
|
108
|
+
dlg.curr_item = walk_progress.curr_item
|
109
|
+
dlg.state = desired_state
|
110
|
+
end
|
111
|
+
sharing.delete(aNode)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Remove multiple parent from the parent stack of provided
|
117
|
+
# walk progress. If one of the removed node is an OrNode
|
118
|
+
# and it was already encountered, then the walk progress is deemed complete.
|
119
|
+
# @param walk_progress [Dendroid::Parsing::WalkProgress]
|
120
|
+
# @param count [Integer] the number of parents to pop; must be greater than one
|
121
|
+
def pop_multiple_parents(walk_progress, count)
|
122
|
+
removed = walk_progress.parents.pop(count)
|
123
|
+
if removed.is_a?(Array)
|
124
|
+
or_nodes = removed.select { |entry| entry.is_a?(OrNode) }
|
125
|
+
unless or_nodes.empty?
|
126
|
+
or_nodes.reverse_each do |or_nd|
|
127
|
+
break if join_or_node(walk_progress, or_nd)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
elsif removed.is_a?(OrNode)
|
131
|
+
join_or_node(walk_progress, removed)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end # class
|
135
|
+
|
136
|
+
# A chart walker visits a chart produced by the Earley recognizer.
|
137
|
+
# It visits the chart backwards: it begins with the chart entries
|
138
|
+
# representing a successful recognition then walks to the predecessor
|
139
|
+
# entries and so on.
|
7
140
|
class ChartWalker
|
141
|
+
# @return [Dendroid::Recognizer::Chart] The chart to visit
|
8
142
|
attr_reader :chart
|
9
|
-
attr_reader :last_item
|
10
|
-
|
11
|
-
# rubocop: disable Metrics/AbcSize
|
12
|
-
# rubocop: disable Metrics/CyclomaticComplexity
|
13
|
-
# rubocop: disable Metrics/PerceivedComplexity
|
14
143
|
|
144
|
+
# @param theChart [Dendroid::Recognizer::Chart] The chart to visit
|
15
145
|
def initialize(theChart)
|
16
146
|
@chart = theChart
|
17
147
|
end
|
18
148
|
|
149
|
+
# @param start_item [Dendroid::Recognizer::EItem] The chart entry to visit first.
|
19
150
|
def walk(start_item)
|
20
151
|
curr_rank = chart.size - 1
|
21
|
-
|
22
|
-
parents = []
|
23
|
-
progress = WalkProgress.new(curr_rank, start_item, parents)
|
152
|
+
progress = WalkProgress.new(curr_rank, start_item, [])
|
24
153
|
paths = [progress]
|
25
|
-
|
26
|
-
|
27
|
-
# Create n times start_item as predecessors, then for each path initialize to its unique own predecessor
|
28
|
-
forerunners = disambiguate(progress, start_item.predecessors)
|
29
|
-
if forerunners.size == 1
|
30
|
-
parents << ANDNode.new(start_item, curr_rank)
|
31
|
-
else
|
32
|
-
preds = sort_predecessors(forerunners)
|
33
|
-
if start_item.rule.rhs.size == 1
|
34
|
-
parents << ANDNode.new(start_item, curr_rank)
|
35
|
-
progress.push_or_node(start_item.origin, preds.size)
|
36
|
-
else
|
37
|
-
parents << OrNode.new(start_item.lhs, start_item.origin, curr_rank, preds.size)
|
38
|
-
end
|
39
|
-
progress.curr_item = start_item
|
40
|
-
fork(progress, paths, preds)
|
41
|
-
end
|
42
|
-
else
|
43
|
-
parents << ANDNode.new(start_item, curr_rank)
|
44
|
-
end
|
45
|
-
token2node = {}
|
46
|
-
entry2node = {}
|
47
|
-
sharing = {}
|
48
|
-
or_nodes_crossed = {}
|
154
|
+
visit_start_item(progress, paths, start_item)
|
155
|
+
ctx = WalkContext.new
|
49
156
|
|
50
157
|
loop do # Iterate over rank values
|
51
158
|
pass = :primary
|
52
159
|
loop do # Iterate over paths until all are ready for previous rank
|
53
|
-
paths
|
54
|
-
next if prg.state == :Complete || prg.state == :Delegating
|
55
|
-
next if pass == :secondary && prg.state == :Waiting
|
56
|
-
|
57
|
-
step_back(prg, paths, token2node, entry2node, sharing, or_nodes_crossed)
|
58
|
-
end
|
160
|
+
all_paths_advance(ctx, paths, pass)
|
59
161
|
# TODO: handle path removal
|
60
162
|
break if paths.none? { |pg| pg.state == :Running || pg.state == :Forking }
|
61
163
|
|
@@ -63,140 +165,76 @@ module Dendroid
|
|
63
165
|
end
|
64
166
|
break if paths.all? { |prg| prg.state == :Complete }
|
65
167
|
|
66
|
-
entry2node.clear
|
168
|
+
ctx.entry2node.clear
|
67
169
|
end
|
68
170
|
|
69
|
-
parents[0]
|
171
|
+
progress.parents[0]
|
70
172
|
end
|
71
173
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
174
|
+
# Start the visit of with the success (accept) chart entry.
|
175
|
+
# Build the root node(s) of parse tree/forest
|
176
|
+
# @param progress [Dendroid::Parsing::WalkProgress]
|
177
|
+
# @param paths [Array<Dendroid::Parsing::WalkProgress>]
|
178
|
+
# @param start_item [Dendroid::Recognizer::EItem]
|
179
|
+
def visit_start_item(progress, paths, start_item)
|
180
|
+
preds = disambiguate_predecessors(progress, start_item.predecessors)
|
181
|
+
if preds.size == 1
|
182
|
+
progress.push_and_node(start_item)
|
183
|
+
else
|
184
|
+
# Multiple predecessors...
|
185
|
+
if start_item.rule.rhs.size == 1
|
186
|
+
progress.push_and_node(start_item)
|
187
|
+
progress.push_or_node(start_item.origin, preds.size)
|
188
|
+
else
|
189
|
+
progress.parents << OrNode.new(start_item.lhs, start_item.origin, progress.curr_rank, preds.size)
|
190
|
+
end
|
191
|
+
progress.curr_item = start_item
|
192
|
+
fork(progress, paths, preds)
|
193
|
+
end
|
194
|
+
end
|
90
195
|
|
91
|
-
|
92
|
-
|
196
|
+
# Iterate over each path, if allowed perform a step back
|
197
|
+
# @param ctx [Dendroid::Parsing::WalkContext]
|
198
|
+
# @param paths [Array<Dendroid::Parsing::WalkProgress>]
|
199
|
+
# @param pass [Symbol] one of: :primary, :secondary
|
200
|
+
def all_paths_advance(ctx, paths, pass)
|
201
|
+
paths.each do |prg|
|
202
|
+
next if prg.state == :Complete || prg.state == :Delegating
|
203
|
+
next if pass == :secondary && prg.state == :Waiting
|
93
204
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
walk_progress.predecessor = nil
|
98
|
-
walk_progress.state = :Running
|
99
|
-
end
|
205
|
+
step_back(prg, ctx, paths)
|
206
|
+
end
|
207
|
+
end
|
100
208
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
209
|
+
# For the given walk_progress, perform the visit of predecessors of
|
210
|
+
# the chart entry designated as the current one.
|
211
|
+
# @param walk_progress [Dendroid::Parsing::WalkProgress]
|
212
|
+
# @param context [Dendroid::Parsing::WalkContext]
|
213
|
+
def step_back(walk_progress, context, paths)
|
214
|
+
loop do
|
215
|
+
predecessors = predecessors_for_state(context, walk_progress)
|
216
|
+
break if walk_progress.state == :Complete
|
105
217
|
|
106
218
|
case walk_progress.curr_item.algo
|
107
219
|
when :completer
|
108
|
-
completer_backwards(walk_progress,
|
220
|
+
completer_backwards(walk_progress, context, paths, predecessors)
|
109
221
|
break if walk_progress.state == :Delegating
|
110
222
|
|
111
223
|
when :scanner
|
112
224
|
curr_token = chart.tokens[walk_progress.curr_rank - 1]
|
113
|
-
|
114
|
-
|
115
|
-
walk_progress.curr_rank -= 1
|
116
|
-
else
|
117
|
-
new_node = walk_progress.add_terminal_node(chart.tokens[walk_progress.curr_rank - 1])
|
118
|
-
token2node[curr_token] = new_node
|
119
|
-
end
|
120
|
-
if predecessors.size == 1
|
121
|
-
walk_progress.curr_item = predecessors[0]
|
122
|
-
walk_progress.state = :Waiting
|
123
|
-
break
|
124
|
-
else
|
125
|
-
# TODO: challenge assumption single predecessor
|
126
|
-
raise StandardError
|
127
|
-
end
|
225
|
+
scanner_backwards(walk_progress, context, predecessors, curr_token)
|
226
|
+
break
|
128
227
|
|
129
228
|
when :predictor
|
130
229
|
unless walk_progress.parents.last.partial?
|
131
230
|
last_parent = walk_progress.parents.pop
|
132
|
-
|
133
|
-
delegating = sharing[last_parent]
|
134
|
-
unless delegating.include? walk_progress
|
135
|
-
delegating.each do |dlg|
|
136
|
-
dlg.curr_rank = walk_progress.curr_rank
|
137
|
-
dlg.curr_item = walk_progress.curr_item
|
138
|
-
dlg.state = :Running
|
139
|
-
end
|
140
|
-
sharing.delete(last_parent)
|
141
|
-
end
|
142
|
-
end
|
231
|
+
context.stop_delegation(last_parent, walk_progress, :Running)
|
143
232
|
if last_parent.is_a?(OrNode)
|
144
|
-
if
|
145
|
-
walk_progress.state = :Complete
|
146
|
-
break
|
147
|
-
else
|
148
|
-
or_nodes_crossed[last_parent] = true
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
index_empty = predecessors.find_index { |entry| entry.dotted_item.empty? }
|
153
|
-
if index_empty
|
154
|
-
entry_empty = predecessors.delete_at(index_empty)
|
155
|
-
walk_progress.add_node_empty(entry_empty)
|
156
|
-
raise StandardError unless predecessors.empty? # Uncovered case
|
157
|
-
|
158
|
-
walk_progress.curr_item = entry_empty
|
159
|
-
next
|
160
|
-
end
|
161
|
-
if predecessors.size == 1
|
162
|
-
walk_progress.curr_item = predecessors[0]
|
163
|
-
else
|
164
|
-
# curr_item has multiple predecessors from distinct rules
|
165
|
-
# look in lineage the latest entry that matches one of the ancestors AND
|
166
|
-
# has a free slot for the current symbol
|
167
|
-
matches = walk_progress.match_parent?(predecessors, true)
|
168
|
-
if matches.empty?
|
169
|
-
walk_progress.state = :Complete
|
170
|
-
break
|
171
|
-
end
|
172
|
-
(matching_pred, stack_offset) = matches.first
|
173
|
-
walk_progress.curr_item = matching_pred
|
174
|
-
unless stack_offset.zero?
|
175
|
-
removed = walk_progress.parents.pop(stack_offset)
|
176
|
-
if removed.is_a?(Array)
|
177
|
-
or_nodes = removed.select { |entry| entry.is_a?(OrNode) }
|
178
|
-
unless or_nodes.empty?
|
179
|
-
or_nodes.reverse_each do |or_nd|
|
180
|
-
if or_nodes_crossed.include?(or_nd)
|
181
|
-
walk_progress.state = :Complete
|
182
|
-
break
|
183
|
-
else
|
184
|
-
or_nodes_crossed[or_nd] = true
|
185
|
-
end
|
186
|
-
end
|
187
|
-
break if walk_progress.state == :Complete
|
188
|
-
|
189
|
-
end
|
190
|
-
elsif removed.is_a?(OrNode)
|
191
|
-
if or_nodes_crossed.include?(removed)
|
192
|
-
walk_progress.state = :Complete
|
193
|
-
break
|
194
|
-
else
|
195
|
-
or_nodes_crossed[removed] = true
|
196
|
-
end
|
197
|
-
end
|
233
|
+
break if context.join_or_node(walk_progress, last_parent)
|
198
234
|
end
|
199
235
|
end
|
236
|
+
predictor_backwards(walk_progress, context, predecessors)
|
237
|
+
break if walk_progress.state == :Complete
|
200
238
|
else
|
201
239
|
raise StandardError
|
202
240
|
end
|
@@ -205,10 +243,45 @@ module Dendroid
|
|
205
243
|
walk_progress
|
206
244
|
end
|
207
245
|
|
208
|
-
|
246
|
+
# Determine predecessors of current item according the walk progess state.
|
247
|
+
# If needed, update also the state.
|
248
|
+
# @param context [Dendroid::Parsing::WalkContext]
|
249
|
+
# @param walk_progress [Dendroid::Parsing::WalkProgress]
|
250
|
+
def predecessors_for_state(context, walk_progress)
|
251
|
+
case walk_progress.state
|
252
|
+
when :Waiting, :New
|
253
|
+
predecessors = predecessors_of(walk_progress.curr_item, walk_progress.parents)
|
254
|
+
last_parent = walk_progress.parents.last
|
255
|
+
context.stop_delegation(last_parent, walk_progress, :Waiting)
|
256
|
+
walk_progress.state = :Running
|
257
|
+
|
258
|
+
when :Running
|
259
|
+
predecessors = predecessors_of(walk_progress.curr_item, walk_progress.parents)
|
260
|
+
|
261
|
+
when :Forking
|
262
|
+
predecessors = [walk_progress.predecessor]
|
263
|
+
walk_progress.predecessor = nil
|
264
|
+
walk_progress.state = :Running
|
265
|
+
end
|
266
|
+
|
267
|
+
walk_progress.state = :Complete if predecessors.empty?
|
209
268
|
predecessors
|
210
269
|
end
|
211
270
|
|
271
|
+
# Check whether given chart entry has multiple predecessorss.
|
272
|
+
# If yes, then apply disambiguation to reduce the number of valid predecessors
|
273
|
+
# If there are still multiple predecessors, then sort them.
|
274
|
+
# @param _progress [Dendroid::Parsing::WalkProgress] Unused
|
275
|
+
# @param predecessors [Array<Dendroid::Recognizer::EItem>]
|
276
|
+
# @return [Array<Dendroid::Recognizer::EItem>]
|
277
|
+
def disambiguate_predecessors(_progress, predecessors)
|
278
|
+
if predecessors.size > 1
|
279
|
+
sort_predecessors(predecessors)
|
280
|
+
else
|
281
|
+
predecessors
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
212
285
|
def sort_predecessors(predecessors)
|
213
286
|
predecessors
|
214
287
|
end
|
@@ -222,6 +295,7 @@ module Dendroid
|
|
222
295
|
raise StandardError
|
223
296
|
|
224
297
|
end
|
298
|
+
|
225
299
|
preds = anEItem.predecessors.dup
|
226
300
|
preds.delete(anEItem)
|
227
301
|
preds
|
@@ -230,29 +304,29 @@ module Dendroid
|
|
230
304
|
end
|
231
305
|
end
|
232
306
|
|
233
|
-
def
|
307
|
+
def scanner_backwards(walk_progress, context, predecessors, curr_token)
|
308
|
+
context.add_terminal_node(walk_progress, curr_token)
|
309
|
+
if predecessors.size == 1
|
310
|
+
walk_progress.curr_item = predecessors[0]
|
311
|
+
walk_progress.state = :Waiting
|
312
|
+
else
|
313
|
+
# TODO: challenge assumption single predecessor
|
314
|
+
raise StandardError
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def completer_backwards(walk_progress, context, paths, predecessors)
|
234
319
|
# Trying to remove some predecessors with some disambiguation technique
|
235
|
-
forerunners =
|
320
|
+
forerunners = disambiguate_predecessors(walk_progress, predecessors)
|
236
321
|
|
237
322
|
if forerunners.size == 1
|
238
323
|
pred = forerunners[0]
|
239
|
-
if
|
240
|
-
|
241
|
-
if sharing.include? shared_node
|
242
|
-
sharing[shared_node] << walk_progress
|
243
|
-
else
|
244
|
-
sharing[shared_node] = [walk_progress]
|
245
|
-
end
|
246
|
-
walk_progress.add_child_node(shared_node)
|
247
|
-
walk_progress.parents.push(shared_node)
|
248
|
-
walk_progress.state = :Delegating
|
249
|
-
|
324
|
+
if context.known_entry? pred
|
325
|
+
context.start_delegation(pred, walk_progress)
|
250
326
|
elsif pred.predecessors.size == 1
|
251
|
-
|
252
|
-
walk_progress.curr_item = pred
|
253
|
-
entry2node[pred] = new_node
|
327
|
+
context.add_and_node(walk_progress, pred)
|
254
328
|
else
|
255
|
-
pre_forerunners =
|
329
|
+
pre_forerunners = disambiguate_predecessors(walk_progress, pred.predecessors)
|
256
330
|
index_empty = pre_forerunners.find_index { |entry| entry.dotted_item.empty? }
|
257
331
|
if index_empty
|
258
332
|
entry_empty = pre_forerunners.delete_at(index_empty)
|
@@ -260,23 +334,47 @@ module Dendroid
|
|
260
334
|
walk_progress.curr_item = entry_empty.predecessors[0] # Assuming only one predecessor
|
261
335
|
end
|
262
336
|
if pre_forerunners.size == 1
|
263
|
-
pred = forerunners[0]
|
264
337
|
new_node = walk_progress.push_and_node(pre_forerunners[0])
|
265
|
-
walk_progress
|
266
|
-
entry2node[pred] = new_node
|
338
|
+
context.advance(walk_progress, pred, new_node)
|
267
339
|
else
|
268
|
-
|
269
|
-
|
270
|
-
walk_progress
|
271
|
-
entry2node[pred] = new_node
|
272
|
-
fork(walk_progress, paths, prepreds)
|
340
|
+
new_node = walk_progress.push_or_node(pred.origin, pre_forerunners.size)
|
341
|
+
context.advance(walk_progress, pred, new_node)
|
342
|
+
fork(walk_progress, paths, pre_forerunners)
|
273
343
|
end
|
274
344
|
end
|
275
345
|
else
|
276
346
|
# AMBIGUITY: multiple valid predecessors
|
277
|
-
|
278
|
-
walk_progress
|
279
|
-
|
347
|
+
walk_progress.push_or_node(forerunners)
|
348
|
+
fork(walk_progress, paths, forerunners)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def predictor_backwards(walk_progress, context, predecessors)
|
353
|
+
index_empty = predecessors.find_index { |entry| entry.dotted_item.empty? }
|
354
|
+
if index_empty
|
355
|
+
entry_empty = predecessors.delete_at(index_empty)
|
356
|
+
walk_progress.add_node_empty(entry_empty)
|
357
|
+
raise StandardError unless predecessors.empty? # Uncovered case
|
358
|
+
|
359
|
+
walk_progress.curr_item = entry_empty
|
360
|
+
return
|
361
|
+
end
|
362
|
+
if predecessors.size == 1
|
363
|
+
walk_progress.curr_item = predecessors[0]
|
364
|
+
else
|
365
|
+
# curr_item has multiple predecessors from distinct rules
|
366
|
+
# look in lineage the latest entry that matches one of the ancestors AND
|
367
|
+
# has a free slot for the current symbol
|
368
|
+
matches = walk_progress.match_parent?(predecessors, true)
|
369
|
+
if matches.empty?
|
370
|
+
walk_progress.state = :Complete
|
371
|
+
else
|
372
|
+
(matching_pred, stack_offset) = matches.first
|
373
|
+
walk_progress.curr_item = matching_pred
|
374
|
+
unless stack_offset.zero?
|
375
|
+
context.pop_multiple_parents(walk_progress, stack_offset)
|
376
|
+
end
|
377
|
+
end
|
280
378
|
end
|
281
379
|
end
|
282
380
|
|
@@ -293,9 +391,5 @@ module Dendroid
|
|
293
391
|
progs.each { |pg| pg.push_and_node(pg.curr_item) }
|
294
392
|
end
|
295
393
|
end # class
|
296
|
-
|
297
|
-
# rubocop: enable Metrics/AbcSize
|
298
|
-
# rubocop: enable Metrics/CyclomaticComplexity
|
299
|
-
# rubocop: enable Metrics/PerceivedComplexity
|
300
394
|
end # module
|
301
395
|
end # module
|
@@ -4,9 +4,14 @@ require_relative 'parse_node'
|
|
4
4
|
|
5
5
|
module Dendroid
|
6
6
|
module Parsing
|
7
|
+
# Composite Pattern. A specialization of parse nodes that have themselves children nodes.
|
7
8
|
class CompositeParseNode < ParseNode
|
9
|
+
# @return [Array<Dendroid::Parsing::ParseNode|NilClass>] Sub-nodes. Nil values represent available slots
|
8
10
|
attr_reader :children
|
9
11
|
|
12
|
+
# @param lowerBound [Integer] Rank of first input token that is matched by this node
|
13
|
+
# @param upperBound [Integer] Rank of last input token that is matched by this node
|
14
|
+
# @param child_count [Integer] The expected number of child nodes
|
10
15
|
def initialize(lowerBound, upperBound, child_count)
|
11
16
|
super(lowerBound, upperBound)
|
12
17
|
@children = Array.new(child_count, nil)
|
@@ -1,21 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'weakref'
|
3
4
|
require_relative 'parse_node'
|
4
5
|
|
5
6
|
module Dendroid
|
6
7
|
module Parsing
|
8
|
+
# A parse tree/forest node that is related to a production rule with an empty
|
9
|
+
# RHS (right-hand side).
|
7
10
|
class EmptyRuleNode < ParseNode
|
11
|
+
# @return [WeakRef<Dendroid::Syntax::Rule>] Grammar rule
|
8
12
|
attr_reader :rule
|
13
|
+
|
14
|
+
# @return [Integer] Index of the rule alternative.
|
9
15
|
attr_reader :alt_index
|
10
16
|
|
17
|
+
# @param anEItem [Dendroid::Recognizer::EItem] An entry from the chart.
|
18
|
+
# @param rank [Integer] rank of the last input token matched by this node
|
11
19
|
def initialize(anEItem, rank)
|
12
20
|
super(rank, rank)
|
13
21
|
@rule = WeakRef.new(anEItem.dotted_item.rule)
|
14
22
|
@alt_index = anEItem.dotted_item.alt_index
|
15
23
|
end
|
16
24
|
|
25
|
+
# Return a String representation of itself
|
26
|
+
# @return [String] text representation of itself
|
17
27
|
def to_s
|
18
|
-
"_ #{
|
28
|
+
"_ #{range_to_s}"
|
19
29
|
end
|
20
30
|
|
21
31
|
# Part of the 'visitee' role in Visitor design pattern.
|
@@ -4,14 +4,25 @@ require_relative 'composite_parse_node'
|
|
4
4
|
|
5
5
|
module Dendroid
|
6
6
|
module Parsing
|
7
|
+
# A composite parse node that embodies multiple syntactical derivations of a right-hand side of a rule
|
8
|
+
# to a range of input tokens. Each child node corresponds to a distinct derivation.
|
7
9
|
class OrNode < CompositeParseNode
|
10
|
+
# @return [Dendroid::Syntax::NonTerminal] The non-terminal symbol at LHS of rule
|
8
11
|
attr_reader :symbol
|
9
12
|
|
13
|
+
# @param sym [Dendroid::Syntax::NonTerminal]
|
14
|
+
# @param lower [Integer] lowest token rank matching start of the rule
|
15
|
+
# @param upper [Integer] largest token rank matching start of the rule
|
16
|
+
# @param arity [Integer] Number of derivations of the given rule
|
10
17
|
def initialize(sym, lower, upper, arity)
|
11
18
|
@symbol = sym
|
12
19
|
super(lower, upper, arity)
|
13
20
|
end
|
14
21
|
|
22
|
+
# Add a child node as root of one derivation.
|
23
|
+
# Place it in an available child slot.
|
24
|
+
# @param child_node [Dendroid::Parsing::ParseNode]
|
25
|
+
# @param _index [Integer] Unused
|
15
26
|
def add_child(child_node, _index)
|
16
27
|
idx = children.find_index(&:nil?)
|
17
28
|
raise StandardError unless idx
|
@@ -20,20 +31,29 @@ module Dendroid
|
|
20
31
|
super(child_node, idx)
|
21
32
|
end
|
22
33
|
|
34
|
+
# Is the given chart entry matching this node?
|
35
|
+
# The chart entry matches this node if:
|
36
|
+
# - its origin equals to the start of the range; and,
|
37
|
+
# - both rules are the same; and,
|
38
|
+
# - each child matches this chart entry
|
39
|
+
# @return [Boolean] true if the entry corresponds to this node.
|
23
40
|
def match(anEItem)
|
24
|
-
return false if range
|
41
|
+
return false if range.begin != anEItem.origin
|
25
42
|
|
26
43
|
dotted = anEItem.dotted_item
|
27
44
|
(symbol == dotted.rule.lhs) && children.any? { |ch| ch.match(anEItem) }
|
28
45
|
end
|
29
46
|
|
47
|
+
# @return [FalseClass]
|
30
48
|
def partial?
|
31
49
|
# children.any?(&:nil?)
|
32
50
|
false
|
33
51
|
end
|
34
52
|
|
53
|
+
# Return a String representation of itself
|
54
|
+
# @return [String] text representation of itself
|
35
55
|
def to_s
|
36
|
-
"OR: #{symbol.name} #{
|
56
|
+
"OR: #{symbol.name} #{range_to_s}"
|
37
57
|
end
|
38
58
|
|
39
59
|
# Part of the 'visitee' role in Visitor design pattern.
|