dendroid 0.2.02 → 0.2.04

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -4,9 +4,14 @@ require_relative 'parse_node'
4
4
 
5
5
  module Dendroid
6
6
  module Parsing
7
+ # Composite Pattern. A specialization of parse nodes that have themselves children nodes.
7
8
  class CompositeParseNode < ParseNode
9
+ # @return [Array<Dendroid::Parsing::ParseNode|NilClass>] Sub-nodes. Nil values represent available slots
8
10
  attr_reader :children
9
11
 
12
+ # @param lowerBound [Integer] Rank of first input token that is matched by this node
13
+ # @param upperBound [Integer] Rank of last input token that is matched by this node
14
+ # @param child_count [Integer] The expected number of child nodes
10
15
  def initialize(lowerBound, upperBound, child_count)
11
16
  super(lowerBound, upperBound)
12
17
  @children = Array.new(child_count, nil)
@@ -1,21 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'weakref'
3
4
  require_relative 'parse_node'
4
5
 
5
6
  module Dendroid
6
7
  module Parsing
8
+ # A parse tree/forest node that is related to a production rule with an empty
9
+ # RHS (right-hand side).
7
10
  class EmptyRuleNode < ParseNode
11
+ # @return [WeakRef<Dendroid::Syntax::Rule>] Grammar rule
8
12
  attr_reader :rule
13
+
14
+ # @return [Integer] Index of the rule alternative.
9
15
  attr_reader :alt_index
10
16
 
17
+ # @param anEItem [Dendroid::Recognizer::EItem] An entry from the chart.
18
+ # @param rank [Integer] rank of the last input token matched by this node
11
19
  def initialize(anEItem, rank)
12
20
  super(rank, rank)
13
21
  @rule = WeakRef.new(anEItem.dotted_item.rule)
14
22
  @alt_index = anEItem.dotted_item.alt_index
15
23
  end
16
24
 
25
+ # Return a String representation of itself
26
+ # @return [String] text representation of itself
17
27
  def to_s
18
- "_ #{super}"
28
+ "_ #{range_to_s}"
19
29
  end
20
30
 
21
31
  # Part of the 'visitee' role in Visitor design pattern.
@@ -4,14 +4,25 @@ require_relative 'composite_parse_node'
4
4
 
5
5
  module Dendroid
6
6
  module Parsing
7
+ # A composite parse node that embodies multiple syntactical derivations of a right-hand side of a rule
8
+ # to a range of input tokens. Each child node corresponds to a distinct derivation.
7
9
  class OrNode < CompositeParseNode
10
+ # @return [Dendroid::Syntax::NonTerminal] The non-terminal symbol at LHS of rule
8
11
  attr_reader :symbol
9
12
 
13
+ # @param sym [Dendroid::Syntax::NonTerminal]
14
+ # @param lower [Integer] lowest token rank matching start of the rule
15
+ # @param upper [Integer] largest token rank matching start of the rule
16
+ # @param arity [Integer] Number of derivations of the given rule
10
17
  def initialize(sym, lower, upper, arity)
11
18
  @symbol = sym
12
19
  super(lower, upper, arity)
13
20
  end
14
21
 
22
+ # Add a child node as root of one derivation.
23
+ # Place it in an available child slot.
24
+ # @param child_node [Dendroid::Parsing::ParseNode]
25
+ # @param _index [Integer] Unused
15
26
  def add_child(child_node, _index)
16
27
  idx = children.find_index(&:nil?)
17
28
  raise StandardError unless idx
@@ -20,20 +31,29 @@ module Dendroid
20
31
  super(child_node, idx)
21
32
  end
22
33
 
34
+ # Is the given chart entry matching this node?
35
+ # The chart entry matches this node if:
36
+ # - its origin equals to the start of the range; and,
37
+ # - both rules are the same; and,
38
+ # - each child matches this chart entry
39
+ # @return [Boolean] true if the entry corresponds to this node.
23
40
  def match(anEItem)
24
- return false if range[0] != anEItem.origin
41
+ return false if range.begin != anEItem.origin
25
42
 
26
43
  dotted = anEItem.dotted_item
27
44
  (symbol == dotted.rule.lhs) && children.any? { |ch| ch.match(anEItem) }
28
45
  end
29
46
 
47
+ # @return [FalseClass]
30
48
  def partial?
31
49
  # children.any?(&:nil?)
32
50
  false
33
51
  end
34
52
 
53
+ # Return a String representation of itself
54
+ # @return [String] text representation of itself
35
55
  def to_s
36
- "OR: #{symbol.name} #{range}"
56
+ "OR: #{symbol.name} #{range_to_s}"
37
57
  end
38
58
 
39
59
  # Part of the 'visitee' role in Visitor design pattern.