dendroid 0.1.00 → 0.2.00
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 +3 -0
- data/lib/dendroid/formatters/ascii_tree.rb +142 -0
- data/lib/dendroid/formatters/base_formatter.rb +25 -0
- data/lib/dendroid/formatters/bracket_notation.rb +50 -0
- data/lib/dendroid/grm_analysis/dotted_item.rb +46 -30
- data/lib/dendroid/grm_analysis/grm_analyzer.rb +2 -4
- data/lib/dendroid/grm_analysis/{choice_items.rb → rule_items.rb} +10 -10
- data/lib/dendroid/grm_dsl/base_grm_builder.rb +3 -4
- data/lib/dendroid/parsing/and_node.rb +56 -0
- data/lib/dendroid/parsing/chart_walker.rb +293 -0
- data/lib/dendroid/parsing/composite_parse_node.rb +21 -0
- data/lib/dendroid/parsing/empty_rule_node.rb +28 -0
- data/lib/dendroid/parsing/or_node.rb +51 -0
- data/lib/dendroid/parsing/parse_node.rb +26 -0
- data/lib/dendroid/parsing/parse_tree_visitor.rb +127 -0
- data/lib/dendroid/parsing/parser.rb +185 -0
- data/lib/dendroid/parsing/terminal_node.rb +32 -0
- data/lib/dendroid/parsing/walk_progress.rb +117 -0
- data/lib/dendroid/recognizer/chart.rb +8 -0
- data/lib/dendroid/recognizer/e_item.rb +21 -2
- data/lib/dendroid/recognizer/item_set.rb +7 -2
- data/lib/dendroid/recognizer/recognizer.rb +33 -20
- data/lib/dendroid/syntax/grammar.rb +1 -1
- data/lib/dendroid/syntax/rule.rb +71 -13
- data/spec/dendroid/grm_analysis/dotted_item_spec.rb +59 -47
- data/spec/dendroid/grm_analysis/{choice_items_spec.rb → rule_items_spec.rb} +5 -6
- data/spec/dendroid/parsing/chart_walker_spec.rb +223 -0
- data/spec/dendroid/parsing/terminal_node_spec.rb +36 -0
- data/spec/dendroid/recognizer/e_item_spec.rb +5 -5
- data/spec/dendroid/recognizer/item_set_spec.rb +16 -8
- data/spec/dendroid/recognizer/recognizer_spec.rb +57 -5
- data/spec/dendroid/support/sample_grammars.rb +2 -0
- data/spec/dendroid/syntax/grammar_spec.rb +16 -21
- data/spec/dendroid/syntax/rule_spec.rb +56 -7
- data/version.txt +1 -1
- metadata +20 -13
- data/lib/dendroid/grm_analysis/alternative_item.rb +0 -70
- data/lib/dendroid/grm_analysis/production_items.rb +0 -55
- data/lib/dendroid/syntax/choice.rb +0 -95
- data/lib/dendroid/syntax/production.rb +0 -82
- data/spec/dendroid/grm_analysis/alternative_item_spec.rb +0 -12
- data/spec/dendroid/grm_analysis/production_items_spec.rb +0 -68
- data/spec/dendroid/syntax/choice_spec.rb +0 -68
- data/spec/dendroid/syntax/production_spec.rb +0 -92
@@ -0,0 +1,293 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'walk_progress'
|
4
|
+
|
5
|
+
module Dendroid
|
6
|
+
module Parsing
|
7
|
+
class ChartWalker
|
8
|
+
attr_reader :chart
|
9
|
+
attr_reader :last_item
|
10
|
+
|
11
|
+
def initialize(theChart)
|
12
|
+
@chart = theChart
|
13
|
+
end
|
14
|
+
|
15
|
+
def walk(start_item)
|
16
|
+
curr_rank = chart.size - 1
|
17
|
+
|
18
|
+
parents = []
|
19
|
+
progress = WalkProgress.new(curr_rank, start_item, parents)
|
20
|
+
paths = [progress]
|
21
|
+
|
22
|
+
if start_item.predecessors.size > 1
|
23
|
+
# Create n times start_item as predecessors, then for each path initialize to its unique own predecessor
|
24
|
+
forerunners = disambiguate(progress, start_item.predecessors)
|
25
|
+
if forerunners.size == 1
|
26
|
+
parents << ANDNode.new(start_item, curr_rank)
|
27
|
+
else
|
28
|
+
preds = sort_predecessors(forerunners)
|
29
|
+
if start_item.rule.rhs.size == 1
|
30
|
+
parents << ANDNode.new(start_item, curr_rank)
|
31
|
+
progress.push_or_node(start_item.origin, preds.size)
|
32
|
+
progress.curr_item = start_item
|
33
|
+
fork(progress, paths, preds)
|
34
|
+
else
|
35
|
+
parents << OrNode.new(start_item.lhs, start_item.origin, curr_rank, preds.size)
|
36
|
+
progress.curr_item = start_item
|
37
|
+
fork(progress, paths, preds)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
else
|
41
|
+
parents << ANDNode.new(start_item, curr_rank)
|
42
|
+
end
|
43
|
+
token2node = {}
|
44
|
+
entry2node = {}
|
45
|
+
sharing = {}
|
46
|
+
or_nodes_crossed = {}
|
47
|
+
|
48
|
+
loop do # Iterate over rank values
|
49
|
+
pass = :primary
|
50
|
+
loop do # Iterate over paths until all are ready for previous rank
|
51
|
+
paths.each do |prg|
|
52
|
+
next if prg.state == :Complete || prg.state == :Delegating
|
53
|
+
next if pass == :secondary && prg.state == :Waiting
|
54
|
+
|
55
|
+
step_back(prg, paths, token2node, entry2node, sharing, or_nodes_crossed)
|
56
|
+
end
|
57
|
+
# TODO: handle path removal
|
58
|
+
break if paths.none? { |pg| pg.state == :Running || pg.state == :Forking }
|
59
|
+
pass = :secondary
|
60
|
+
end
|
61
|
+
break if paths.all? { |prg| prg.state == :Complete }
|
62
|
+
|
63
|
+
entry2node.clear
|
64
|
+
end
|
65
|
+
|
66
|
+
parents[0]
|
67
|
+
end
|
68
|
+
|
69
|
+
def step_back(walk_progress, paths, token2node, entry2node, sharing, or_nodes_crossed)
|
70
|
+
loop do
|
71
|
+
case walk_progress.state
|
72
|
+
when :Waiting, :New
|
73
|
+
predecessors = predecessors_of(walk_progress.curr_item, walk_progress.parents)
|
74
|
+
last_parent = walk_progress.parents.last
|
75
|
+
if sharing.include? last_parent
|
76
|
+
delegating = sharing[last_parent]
|
77
|
+
unless delegating.include? walk_progress
|
78
|
+
delegating.each do |dlg|
|
79
|
+
dlg.curr_rank = walk_progress.curr_rank
|
80
|
+
dlg.curr_item = walk_progress.curr_item
|
81
|
+
dlg.state = :Waiting
|
82
|
+
end
|
83
|
+
sharing.delete(last_parent)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
walk_progress.state = :Running
|
87
|
+
|
88
|
+
when :Running
|
89
|
+
predecessors = predecessors_of(walk_progress.curr_item, walk_progress.parents)
|
90
|
+
|
91
|
+
when :Forking
|
92
|
+
# predecessors = [walk_progress.curr_item]
|
93
|
+
predecessors = [walk_progress.predecessor]
|
94
|
+
walk_progress.predecessor = nil
|
95
|
+
walk_progress.state = :Running
|
96
|
+
end
|
97
|
+
|
98
|
+
if predecessors.empty?
|
99
|
+
walk_progress.state = :Complete
|
100
|
+
break
|
101
|
+
end
|
102
|
+
|
103
|
+
case walk_progress.curr_item.algo
|
104
|
+
when :completer
|
105
|
+
completer_backwards(walk_progress, paths, entry2node, sharing, predecessors)
|
106
|
+
break if walk_progress.state == :Delegating
|
107
|
+
|
108
|
+
when :scanner
|
109
|
+
curr_token = chart.tokens[walk_progress.curr_rank - 1]
|
110
|
+
if token2node.include? curr_token
|
111
|
+
walk_progress.add_child_node(token2node[curr_token])
|
112
|
+
walk_progress.curr_rank -= 1
|
113
|
+
else
|
114
|
+
new_node = walk_progress.add_terminal_node(chart.tokens[walk_progress.curr_rank - 1])
|
115
|
+
token2node[curr_token] = new_node
|
116
|
+
end
|
117
|
+
if predecessors.size == 1
|
118
|
+
walk_progress.curr_item = predecessors[0]
|
119
|
+
walk_progress.state = :Waiting
|
120
|
+
break
|
121
|
+
else
|
122
|
+
# TODO: fix assumption single predecessor
|
123
|
+
raise StandardError
|
124
|
+
end
|
125
|
+
|
126
|
+
when :predictor
|
127
|
+
unless walk_progress.parents.last.partial?
|
128
|
+
last_parent = walk_progress.parents.pop
|
129
|
+
if sharing.include? last_parent
|
130
|
+
delegating = sharing[last_parent]
|
131
|
+
unless delegating.include? walk_progress
|
132
|
+
delegating.each do |dlg|
|
133
|
+
dlg.curr_rank = walk_progress.curr_rank
|
134
|
+
dlg.curr_item = walk_progress.curr_item
|
135
|
+
dlg.state = :Running
|
136
|
+
end
|
137
|
+
sharing.delete(last_parent)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
if last_parent.is_a?(OrNode)
|
141
|
+
if or_nodes_crossed.include?(last_parent)
|
142
|
+
walk_progress.state = :Complete
|
143
|
+
break
|
144
|
+
else
|
145
|
+
or_nodes_crossed[last_parent] = true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
index_empty = predecessors.find_index { |entry| entry.dotted_item.empty? }
|
150
|
+
if index_empty
|
151
|
+
entry_empty = predecessors.delete_at(index_empty)
|
152
|
+
walk_progress.add_node_empty(entry_empty)
|
153
|
+
raise StandardError unless predecessors.empty? # Uncovered case
|
154
|
+
walk_progress.curr_item = entry_empty
|
155
|
+
next
|
156
|
+
end
|
157
|
+
if predecessors.size == 1
|
158
|
+
walk_progress.curr_item = predecessors[0]
|
159
|
+
else
|
160
|
+
# curr_item has multiple predecessors from distinct rules
|
161
|
+
# look in lineage the latest entry that matches one of the ancestors AND
|
162
|
+
# has a free slot for the current symbol
|
163
|
+
matches = walk_progress.match_parent?(predecessors, true)
|
164
|
+
if matches.empty?
|
165
|
+
walk_progress.state = :Complete
|
166
|
+
break
|
167
|
+
end
|
168
|
+
(matching_pred, stack_offset) = matches.first
|
169
|
+
walk_progress.curr_item = matching_pred
|
170
|
+
unless stack_offset.zero?
|
171
|
+
removed = walk_progress.parents.pop(stack_offset)
|
172
|
+
if removed.is_a?(Array)
|
173
|
+
or_nodes = removed.select { |entry| entry.is_a?(OrNode) }
|
174
|
+
unless or_nodes.empty?
|
175
|
+
or_nodes.reverse_each do |or_nd|
|
176
|
+
if or_nodes_crossed.include?(or_nd)
|
177
|
+
walk_progress.state = :Complete
|
178
|
+
break
|
179
|
+
else
|
180
|
+
or_nodes_crossed[or_nd] = true
|
181
|
+
end
|
182
|
+
end
|
183
|
+
break if walk_progress.state == :Complete
|
184
|
+
|
185
|
+
end
|
186
|
+
elsif removed.is_a?(OrNode)
|
187
|
+
if or_nodes_crossed.include?(removed)
|
188
|
+
walk_progress.state = :Complete
|
189
|
+
break
|
190
|
+
else
|
191
|
+
or_nodes_crossed[removed] = true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
else
|
197
|
+
raise StandardError
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
walk_progress
|
202
|
+
end
|
203
|
+
|
204
|
+
def disambiguate(_progress, predecessors)
|
205
|
+
predecessors
|
206
|
+
end
|
207
|
+
|
208
|
+
def sort_predecessors(predecessors)
|
209
|
+
predecessors
|
210
|
+
end
|
211
|
+
|
212
|
+
def predecessors_of(anEItem, parents)
|
213
|
+
# Rule: if anEItem has itself as predecessor AND parents contains
|
214
|
+
# only a start item, then remove anEItem from its own predecessor(s).
|
215
|
+
if (parents.size == 1) && anEItem.predecessors.include?(anEItem)
|
216
|
+
# raise StandardError unless parents[0].match(anEItem)
|
217
|
+
unless parents[0].match(anEItem)
|
218
|
+
raise StandardError
|
219
|
+
end
|
220
|
+
preds = anEItem.predecessors.dup
|
221
|
+
preds.delete(anEItem)
|
222
|
+
preds
|
223
|
+
else
|
224
|
+
anEItem.predecessors
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def completer_backwards(walk_progress, paths, entry2node, sharing, predecessors)
|
229
|
+
# Trying to remove some predecessors with some disambiguation technique
|
230
|
+
forerunners = disambiguate(walk_progress, predecessors)
|
231
|
+
|
232
|
+
if forerunners.size == 1
|
233
|
+
pred = forerunners[0]
|
234
|
+
if entry2node.include? pred
|
235
|
+
shared_node = entry2node[pred]
|
236
|
+
if sharing.include? shared_node
|
237
|
+
sharing[shared_node] << walk_progress
|
238
|
+
else
|
239
|
+
sharing[shared_node] = [walk_progress]
|
240
|
+
end
|
241
|
+
walk_progress.add_child_node(shared_node)
|
242
|
+
walk_progress.parents.push(shared_node)
|
243
|
+
walk_progress.state = :Delegating
|
244
|
+
|
245
|
+
elsif pred.predecessors.size == 1
|
246
|
+
new_node = walk_progress.push_and_node(pred)
|
247
|
+
walk_progress.curr_item = pred
|
248
|
+
entry2node[pred] = new_node
|
249
|
+
else
|
250
|
+
pre_forerunners = disambiguate(walk_progress, pred.predecessors)
|
251
|
+
index_empty = pre_forerunners.find_index { |entry| entry.dotted_item.empty? }
|
252
|
+
if index_empty
|
253
|
+
entry_empty = pre_forerunners.delete_at(index_empty)
|
254
|
+
walk_progress.add_node_empty(entry_empty)
|
255
|
+
walk_progress.curr_item = entry_empty.predecessors[0] # Assuming only one predecessor
|
256
|
+
end
|
257
|
+
if pre_forerunners.size == 1
|
258
|
+
pred = forerunners[0]
|
259
|
+
new_node = walk_progress.push_and_node(pre_forerunners[0])
|
260
|
+
walk_progress.curr_item = pred
|
261
|
+
entry2node[pred] = new_node
|
262
|
+
else
|
263
|
+
prepreds = sort_predecessors(pre_forerunners)
|
264
|
+
new_node = walk_progress.push_or_node(pred.origin, prepreds.size)
|
265
|
+
walk_progress.curr_item = pred
|
266
|
+
entry2node[pred] = new_node
|
267
|
+
fork(walk_progress, paths, prepreds)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
else
|
271
|
+
# AMBIGUITY: multiple valid predecessors
|
272
|
+
preds = sort_predecessors(forerunners)
|
273
|
+
walk_progress.push_or_node(preds)
|
274
|
+
fork(walk_progress, paths, preds)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def fork(walk_progress, paths, sorted_predecessors)
|
279
|
+
progs = [walk_progress]
|
280
|
+
walk_progress.fork(sorted_predecessors[0])
|
281
|
+
sorted_predecessors[1..-1].each do |prd|
|
282
|
+
alternate = walk_progress.dup
|
283
|
+
alternate.fork(prd)
|
284
|
+
paths << alternate
|
285
|
+
progs << alternate
|
286
|
+
end
|
287
|
+
|
288
|
+
progs.each { |pg| pg.push_and_node(pg.curr_item) }
|
289
|
+
end
|
290
|
+
|
291
|
+
end # class
|
292
|
+
end # module
|
293
|
+
end # module
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'parse_node'
|
4
|
+
|
5
|
+
module Dendroid
|
6
|
+
module Parsing
|
7
|
+
class CompositeParseNode < ParseNode
|
8
|
+
attr_reader :range
|
9
|
+
attr_reader :children
|
10
|
+
|
11
|
+
def initialize(lowerBound, upperBound, child_count)
|
12
|
+
super(lowerBound, upperBound)
|
13
|
+
@children = Array.new(child_count, nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_child(child_node, index)
|
17
|
+
children[index] = child_node
|
18
|
+
end
|
19
|
+
end # class
|
20
|
+
end # module
|
21
|
+
end # module
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'parse_node'
|
4
|
+
|
5
|
+
module Dendroid
|
6
|
+
module Parsing
|
7
|
+
class EmptyRuleNode < ParseNode
|
8
|
+
attr_reader :rule
|
9
|
+
attr_reader :alt_index
|
10
|
+
|
11
|
+
def initialize(anEItem, rank)
|
12
|
+
super(rank, rank)
|
13
|
+
@rule = WeakRef.new(anEItem.dotted_item.rule)
|
14
|
+
@alt_index = anEItem.dotted_item.alt_index
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"_ #{super}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
22
|
+
# @param aVisitor[ParseTreeVisitor] the visitor
|
23
|
+
def accept(aVisitor)
|
24
|
+
aVisitor.visit_empty_rule_node(self)
|
25
|
+
end
|
26
|
+
end # class
|
27
|
+
end # module
|
28
|
+
end # module
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'composite_parse_node'
|
4
|
+
|
5
|
+
module Dendroid
|
6
|
+
module Parsing
|
7
|
+
class OrNode < CompositeParseNode
|
8
|
+
attr_reader :symbol
|
9
|
+
|
10
|
+
def initialize(sym, lower, upper, arity)
|
11
|
+
@symbol = sym
|
12
|
+
super(lower, upper, arity)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_child(child_node, _index)
|
16
|
+
idx = children.find_index(&:nil?)
|
17
|
+
if idx
|
18
|
+
# Use first found available slot...
|
19
|
+
super(child_node, idx)
|
20
|
+
if children.size > 3
|
21
|
+
raise StandardError
|
22
|
+
end
|
23
|
+
else
|
24
|
+
raise StandardError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def match(anEItem)
|
29
|
+
return false if range[0] != anEItem.origin
|
30
|
+
|
31
|
+
dotted = anEItem.dotted_item
|
32
|
+
(symbol == dotted.rule.lhs) && children.any? { |ch| ch.match(anEItem) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def partial?
|
36
|
+
# children.any?(&:nil?)
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
"OR: #{symbol.name} #{range}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
45
|
+
# @param aVisitor[ParseTreeVisitor] the visitor
|
46
|
+
def accept(aVisitor)
|
47
|
+
aVisitor.visit_or_node(self)
|
48
|
+
end
|
49
|
+
end # class
|
50
|
+
end # module
|
51
|
+
end # module
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dendroid
|
4
|
+
module Parsing
|
5
|
+
class ParseNode
|
6
|
+
# @return [Array<Integer>] The range of input tokens that match this node.
|
7
|
+
attr_reader :range
|
8
|
+
|
9
|
+
def initialize(lowerBound, upperBound)
|
10
|
+
@range = valid_range(lowerBound, upperBound)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"[#{range[0]}, #{range[1]}]"
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def valid_range(lowerBound, upperBound)
|
20
|
+
raise StandardError unless lowerBound.is_a?(Integer) && upperBound.is_a?(Integer)
|
21
|
+
|
22
|
+
[lowerBound, upperBound]
|
23
|
+
end
|
24
|
+
end # class
|
25
|
+
end # module
|
26
|
+
end # module
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ParseTreeVisitor
|
4
|
+
# Link to the result root node of the tree (forest)
|
5
|
+
attr_reader(:root)
|
6
|
+
|
7
|
+
# List of objects that subscribed to the visit event notification.
|
8
|
+
attr_reader(:subscribers)
|
9
|
+
|
10
|
+
# Indicates the kind of tree traversal to perform: :post_order, :pre-order
|
11
|
+
attr_reader(:traversal)
|
12
|
+
|
13
|
+
# Build a visitor for the given root.
|
14
|
+
# @param aParseTree [ParseTree] the parse tree to visit.
|
15
|
+
def initialize(aParseTree, aTraversalStrategy = :post_order)
|
16
|
+
raise StandardError if aParseTree.nil?
|
17
|
+
|
18
|
+
@root = aParseTree
|
19
|
+
@subscribers = []
|
20
|
+
@traversal = aTraversalStrategy
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add a subscriber for the visit event notifications.
|
24
|
+
# @param aSubscriber [Object]
|
25
|
+
def subscribe(aSubscriber)
|
26
|
+
subscribers << aSubscriber
|
27
|
+
end
|
28
|
+
|
29
|
+
# Remove the given object from the subscription list.
|
30
|
+
# The object won't be notified of visit events.
|
31
|
+
# @param aSubscriber [Object]
|
32
|
+
def unsubscribe(aSubscriber)
|
33
|
+
subscribers.delete_if { |entry| entry == aSubscriber }
|
34
|
+
end
|
35
|
+
|
36
|
+
# The signal to begin the visit of the parse tree.
|
37
|
+
def start
|
38
|
+
root.accept(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Visit event. The visitor is about to visit the root.
|
42
|
+
# @param aParseTree [ParseTree] the root to visit.
|
43
|
+
def start_visit_root(aParseTree)
|
44
|
+
broadcast(:before_root, aParseTree)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Visit event. The visitor is about to visit the given non terminal node.
|
48
|
+
# @param aNonTerminalNode [ANDNode] the node to visit.
|
49
|
+
def visit_and_node(aNonTerminalNode)
|
50
|
+
if @traversal == :post_order
|
51
|
+
broadcast(:before_and_node, aNonTerminalNode)
|
52
|
+
traverse_subnodes(aNonTerminalNode)
|
53
|
+
else
|
54
|
+
traverse_subnodes(aNonTerminalNode)
|
55
|
+
broadcast(:before_and_node, aNonTerminalNode)
|
56
|
+
end
|
57
|
+
broadcast(:after_and_node, aNonTerminalNode)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Visit event. The visitor is about to visit the given non terminal node.
|
61
|
+
# @param aNonTerminalNode [OrNode] the node to visit.
|
62
|
+
def visit_or_node(aNonTerminalNode)
|
63
|
+
if @traversal == :post_order
|
64
|
+
broadcast(:before_or_node, aNonTerminalNode)
|
65
|
+
traverse_subnodes(aNonTerminalNode)
|
66
|
+
else
|
67
|
+
traverse_subnodes(aNonTerminalNode)
|
68
|
+
broadcast(:before_or_node, aNonTerminalNode)
|
69
|
+
end
|
70
|
+
broadcast(:after_or_node, aNonTerminalNode)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Visit event. The visitor is visiting the
|
74
|
+
# given terminal node.
|
75
|
+
# @param anEmptyRuleNode [EmptyRuleNode] the node to visit.
|
76
|
+
def visit_empty_rule_node(anEmptyRuleNode)
|
77
|
+
broadcast(:before_empty_rule_node, anEmptyRuleNode)
|
78
|
+
broadcast(:after_empty_rule_node, anEmptyRuleNode)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Visit event. The visitor is visiting the
|
82
|
+
# given terminal node.
|
83
|
+
# @param aTerminalNode [TerminalNode] the terminal to visit.
|
84
|
+
def visit_terminal(aTerminalNode)
|
85
|
+
broadcast(:before_terminal, aTerminalNode)
|
86
|
+
broadcast(:after_terminal, aTerminalNode)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Visit event. The visitor has completed its visit of the given
|
90
|
+
# non-terminal node.
|
91
|
+
# @param aNonTerminalNode [NonTerminalNode] the node to visit.
|
92
|
+
def end_visit_nonterminal(aNonTerminalNode)
|
93
|
+
broadcast(:after_and_node, aNonTerminalNode)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Visit event. The visitor has completed the visit of the root.
|
97
|
+
# @param aParseTree [ParseTree] the root to visit.
|
98
|
+
def end_visit_root(aParseTree)
|
99
|
+
broadcast(:after_root, aParseTree)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Visit event. The visitor is about to visit the subnodes of a non
|
105
|
+
# terminal node.
|
106
|
+
# @param aParentNode [NonTeminalNode] the (non-terminal) parent node.
|
107
|
+
def traverse_subnodes(aParentNode)
|
108
|
+
subnodes = aParentNode.children
|
109
|
+
broadcast(:before_subnodes, aParentNode, subnodes)
|
110
|
+
|
111
|
+
# Let's proceed with the visit of subnodes
|
112
|
+
subnodes.each { |a_node| a_node.accept(self) }
|
113
|
+
|
114
|
+
broadcast(:after_subnodes, aParentNode, subnodes)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Send a notification to all subscribers.
|
118
|
+
# @param msg [Symbol] event to notify
|
119
|
+
# @param args [Array] arguments of the notification.
|
120
|
+
def broadcast(msg, *args)
|
121
|
+
subscribers.each do |subscr|
|
122
|
+
next unless subscr.respond_to?(msg) || subscr.respond_to?(:accept_all)
|
123
|
+
|
124
|
+
subscr.send(msg, *args)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end # class
|