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 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