dendroid 0.2.03 → 0.2.04
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/dendroid/parsing/chart_walker.rb +278 -184
- data/lib/dendroid/parsing/walk_progress.rb +8 -8
- data/lib/dendroid.rb +0 -2
- data/spec/dendroid/parsing/chart_walker_spec.rb +0 -8
- data/spec/dendroid/parsing/walk_progress_spec.rb +61 -0
- data/version.txt +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27c301e0fa5d044507f9b3b670305852444b9dcd31592fa862fed32df0d1a46a
|
4
|
+
data.tar.gz: abd3263f035f72641bbf4f9a1219eb160be179d593487c3a4afd9a8889e479d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 011f8688d569b73abacfd4a75c4b6dea7f1524701e4af98fad7ebd426bb3ed01e0552e4eb80a0c6d633c4c28d8805c48d6dd75879d73274f6a898cbf70f1c83c
|
7
|
+
data.tar.gz: add10309df54041eef176bb38190c86fc3bfda400148dfc784863d69a9765fee37e9491449a62b35c805fc67338825791c82429c0a4527dc6ccf782ef2921a99
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [0.2.04] - 2023-12-24
|
6
|
+
Some code refactoring.
|
7
|
+
|
8
|
+
### Added
|
9
|
+
- File `walk_progress_spec.rb`
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
- Class `WalkProgress` refactoring & documentation
|
13
|
+
- Class `ChartWalker` refactoring & documentation
|
14
|
+
|
5
15
|
## [0.2.03] - 2023-12-21
|
6
16
|
|
7
17
|
### Added
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'and_node
|
3
|
+
require_relative 'and_node'
|
4
4
|
require_relative 'or_node'
|
5
5
|
require_relative 'terminal_node'
|
6
6
|
require_relative 'empty_rule_node'
|
@@ -10,9 +10,9 @@ module Dendroid
|
|
10
10
|
# This object holds the current state of the visit of a Chart by one
|
11
11
|
# ChartWalker through one single visit path. A path corresponds to a
|
12
12
|
# chain from the current item back to the initial item(s) through the predecessors links.
|
13
|
-
# It is used to construct (part of) the parse tree from the root node.
|
13
|
+
# It is used to construct (part of) the parse tree beginning from the root node.
|
14
14
|
class WalkProgress
|
15
|
-
# @return [Symbol] One of: :New, :Forking,
|
15
|
+
# @return [Symbol] One of: :New, :Running, :Waiting, :Complete, :Forking, :Delegating
|
16
16
|
attr_accessor :state
|
17
17
|
|
18
18
|
# @return [Integer] rank of the item set from the chart being visited
|
@@ -28,9 +28,6 @@ module Dendroid
|
|
28
28
|
# @return [Array<Dendroid::Parsing::CompositeParseNode>] The ancestry of current parse node.
|
29
29
|
attr_reader :parents
|
30
30
|
|
31
|
-
# rubocop: disable Metrics/CyclomaticComplexity
|
32
|
-
# rubocop: disable Metrics/PerceivedComplexity
|
33
|
-
|
34
31
|
# @param start_rank [Integer] Initial rank at the start of the visit
|
35
32
|
# @param start_item [Dendroid::Recognizer::EItem] Initial chart entry to visit
|
36
33
|
# @param parents [Array<Dendroid::Parsing::CompositeParseNode>]
|
@@ -116,15 +113,18 @@ module Dendroid
|
|
116
113
|
# @param aNode [Dendroid::Parsing::ParseNode]
|
117
114
|
# @return [Dendroid::Parsing::ParseNode]
|
118
115
|
def add_child_node(aNode)
|
119
|
-
parents.last.add_child(aNode, curr_item.position - 1)
|
116
|
+
parents.last.add_child(aNode, curr_item.position - 1) unless parents.empty?
|
120
117
|
aNode
|
121
118
|
end
|
122
119
|
|
120
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
121
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
122
|
+
|
123
123
|
# Do the given EItems match one of the parent?
|
124
124
|
# Matching = corresponds to the same rule and range
|
125
125
|
# @param entries [Dendroid::Recognizer::EItem]
|
126
126
|
# @param stop_at_first [Boolean] Must be true
|
127
|
-
# @return [Array<EItem
|
127
|
+
# @return [Array<Array<EItem, Integer>>]
|
128
128
|
def match_parent?(entries, stop_at_first)
|
129
129
|
matching = []
|
130
130
|
min_origin = entries[0].origin
|
data/lib/dendroid.rb
CHANGED
@@ -6,10 +6,8 @@
|
|
6
6
|
module Dendroid
|
7
7
|
end # module
|
8
8
|
|
9
|
-
|
10
9
|
# This file acts as a jumping-off point for loading dependencies expected
|
11
10
|
# for a Dendroid client.
|
12
|
-
|
13
11
|
require_relative './dendroid/grm_dsl/base_grm_builder'
|
14
12
|
require_relative './dendroid/utils/base_tokenizer'
|
15
13
|
require_relative './dendroid/recognizer/recognizer'
|
@@ -5,14 +5,6 @@ require_relative '../support/sample_grammars'
|
|
5
5
|
require_relative '../../../lib/dendroid/recognizer/recognizer'
|
6
6
|
require_relative '../../../lib/dendroid/parsing/chart_walker'
|
7
7
|
|
8
|
-
# require_relative '../grm_dsl/base_grm_builder'
|
9
|
-
# require_relative '../utils/base_tokenizer'
|
10
|
-
# require_relative '../recognizer/recognizer'
|
11
|
-
# require_relative 'chart_walker'
|
12
|
-
# require_relative 'parse_tree_visitor'
|
13
|
-
# require_relative '../formatters/bracket_notation'
|
14
|
-
# require_relative '../formatters/ascii_tree'
|
15
|
-
|
16
8
|
RSpec.describe Dendroid::Parsing::ChartWalker do
|
17
9
|
include SampleGrammars
|
18
10
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../spec_helper'
|
4
|
+
require_relative '../support/sample_grammars'
|
5
|
+
require_relative '../../../lib/dendroid/recognizer/recognizer'
|
6
|
+
require_relative '../../../lib/dendroid/parsing/walk_progress'
|
7
|
+
|
8
|
+
RSpec.describe Dendroid::Parsing::WalkProgress do
|
9
|
+
include SampleGrammars
|
10
|
+
|
11
|
+
def retrieve_success_item(chart, grammar)
|
12
|
+
last_item_set = chart.item_sets.last
|
13
|
+
result = nil
|
14
|
+
last_item_set.items.reverse_each do |itm|
|
15
|
+
if itm.origin.zero? && itm.dotted_item.completed? && itm.dotted_item.rule.lhs == grammar.start_symbol
|
16
|
+
result = itm
|
17
|
+
break
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
def recognizer_for(grammar, tokenizer)
|
25
|
+
Dendroid::Recognizer::Recognizer.new(grammar, tokenizer)
|
26
|
+
end
|
27
|
+
|
28
|
+
def success_entry(chart, recognizer)
|
29
|
+
retrieve_success_item(chart, recognizer.grm_analysis.grammar)
|
30
|
+
end
|
31
|
+
|
32
|
+
subject do
|
33
|
+
recognizer = recognizer_for(grammar_l8, tokenizer_l8)
|
34
|
+
chart = recognizer.run('x x x x')
|
35
|
+
described_class.new(4, success_entry(chart, recognizer), [])
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'Initialization:' do
|
39
|
+
it 'should be initialized with a symbol, terminal and a rank' do
|
40
|
+
recognizer = recognizer_for(grammar_l8, tokenizer_l8)
|
41
|
+
chart = recognizer.run('x x x x')
|
42
|
+
expect { described_class.new(4, success_entry(chart, recognizer), []) }.not_to raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'is in New state' do
|
46
|
+
expect(subject.state).to eq(:New)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'has no overriding predecessor at start' do
|
50
|
+
expect(subject.predecessor).to be_nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'knows the current rank' do
|
54
|
+
expect(subject.curr_rank).to eq(4)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'knows the current item' do
|
58
|
+
expect(subject.curr_item.to_s).to eq('S => S S . @ 0')
|
59
|
+
end
|
60
|
+
end # context
|
61
|
+
end # describe
|
data/version.txt
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.04
|
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.2.
|
4
|
+
version: 0.2.04
|
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-12-
|
11
|
+
date: 2023-12-24 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
|
@@ -66,6 +66,7 @@ files:
|
|
66
66
|
- spec/dendroid/parsing/empty_rule_node_spec.rb
|
67
67
|
- spec/dendroid/parsing/parse_node_spec.rb
|
68
68
|
- spec/dendroid/parsing/terminal_node_spec.rb
|
69
|
+
- spec/dendroid/parsing/walk_progress_spec.rb
|
69
70
|
- spec/dendroid/recognizer/chart_spec.rb
|
70
71
|
- spec/dendroid/recognizer/e_item_spec.rb
|
71
72
|
- spec/dendroid/recognizer/item_set_spec.rb
|