dendroid 0.2.03 → 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 +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
|