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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba78964528c386b4be6024fdd2f1f76edc87478e83013ac40933d183e0d04d02
4
- data.tar.gz: 1a5840b2d92a8b8e525bdf1313122ae3f3160fbb9676700c8e4d3edede71928b
3
+ metadata.gz: 27c301e0fa5d044507f9b3b670305852444b9dcd31592fa862fed32df0d1a46a
4
+ data.tar.gz: abd3263f035f72641bbf4f9a1219eb160be179d593487c3a4afd9a8889e479d6
5
5
  SHA512:
6
- metadata.gz: 241163fd9a9f7ab036a1d750ef30844f538b40e2fea401af86929798a98f515ec9a82d136f2db7e61c3253ce7c88e9166db55ef24373a47acc0a6dd043e84c61
7
- data.tar.gz: ee0faca7992fa087ca100b0de21b7e85464828a832462cded6593688d20b6b834a11d87284f7a3fcce137dbc670db98499e5601587119e32d25f5ab2f61a4134
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
- if start_item.predecessors.size > 1
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.each do |prg|
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
- def step_back(walk_progress, paths, token2node, entry2node, sharing, or_nodes_crossed)
73
- loop do
74
- case walk_progress.state
75
- when :Waiting, :New
76
- predecessors = predecessors_of(walk_progress.curr_item, walk_progress.parents)
77
- last_parent = walk_progress.parents.last
78
- if sharing.include? last_parent
79
- delegating = sharing[last_parent]
80
- unless delegating.include? walk_progress
81
- delegating.each do |dlg|
82
- dlg.curr_rank = walk_progress.curr_rank
83
- dlg.curr_item = walk_progress.curr_item
84
- dlg.state = :Waiting
85
- end
86
- sharing.delete(last_parent)
87
- end
88
- end
89
- walk_progress.state = :Running
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
- when :Running
92
- predecessors = predecessors_of(walk_progress.curr_item, walk_progress.parents)
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
- when :Forking
95
- # predecessors = [walk_progress.curr_item]
96
- predecessors = [walk_progress.predecessor]
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
- if predecessors.empty?
102
- walk_progress.state = :Complete
103
- break
104
- end
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, paths, entry2node, sharing, predecessors)
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
- if token2node.include? curr_token
114
- walk_progress.add_child_node(token2node[curr_token])
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
- if sharing.include? last_parent
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 or_nodes_crossed.include?(last_parent)
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
- def disambiguate(_progress, predecessors)
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 completer_backwards(walk_progress, paths, entry2node, sharing, predecessors)
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 = disambiguate(walk_progress, predecessors)
320
+ forerunners = disambiguate_predecessors(walk_progress, predecessors)
236
321
 
237
322
  if forerunners.size == 1
238
323
  pred = forerunners[0]
239
- if entry2node.include? pred
240
- shared_node = entry2node[pred]
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
- new_node = walk_progress.push_and_node(pred)
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 = disambiguate(walk_progress, pred.predecessors)
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.curr_item = pred
266
- entry2node[pred] = new_node
338
+ context.advance(walk_progress, pred, new_node)
267
339
  else
268
- prepreds = sort_predecessors(pre_forerunners)
269
- new_node = walk_progress.push_or_node(pred.origin, prepreds.size)
270
- walk_progress.curr_item = pred
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
- preds = sort_predecessors(forerunners)
278
- walk_progress.push_or_node(preds)
279
- fork(walk_progress, paths, preds)
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.rb'
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.03
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.03
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-22 00:00:00.000000000 Z
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