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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/lib/dendroid/formatters/ascii_tree.rb +142 -0
  4. data/lib/dendroid/formatters/base_formatter.rb +25 -0
  5. data/lib/dendroid/formatters/bracket_notation.rb +50 -0
  6. data/lib/dendroid/grm_analysis/dotted_item.rb +46 -30
  7. data/lib/dendroid/grm_analysis/grm_analyzer.rb +2 -4
  8. data/lib/dendroid/grm_analysis/{choice_items.rb → rule_items.rb} +10 -10
  9. data/lib/dendroid/grm_dsl/base_grm_builder.rb +3 -4
  10. data/lib/dendroid/parsing/and_node.rb +56 -0
  11. data/lib/dendroid/parsing/chart_walker.rb +293 -0
  12. data/lib/dendroid/parsing/composite_parse_node.rb +21 -0
  13. data/lib/dendroid/parsing/empty_rule_node.rb +28 -0
  14. data/lib/dendroid/parsing/or_node.rb +51 -0
  15. data/lib/dendroid/parsing/parse_node.rb +26 -0
  16. data/lib/dendroid/parsing/parse_tree_visitor.rb +127 -0
  17. data/lib/dendroid/parsing/parser.rb +185 -0
  18. data/lib/dendroid/parsing/terminal_node.rb +32 -0
  19. data/lib/dendroid/parsing/walk_progress.rb +117 -0
  20. data/lib/dendroid/recognizer/chart.rb +8 -0
  21. data/lib/dendroid/recognizer/e_item.rb +21 -2
  22. data/lib/dendroid/recognizer/item_set.rb +7 -2
  23. data/lib/dendroid/recognizer/recognizer.rb +33 -20
  24. data/lib/dendroid/syntax/grammar.rb +1 -1
  25. data/lib/dendroid/syntax/rule.rb +71 -13
  26. data/spec/dendroid/grm_analysis/dotted_item_spec.rb +59 -47
  27. data/spec/dendroid/grm_analysis/{choice_items_spec.rb → rule_items_spec.rb} +5 -6
  28. data/spec/dendroid/parsing/chart_walker_spec.rb +223 -0
  29. data/spec/dendroid/parsing/terminal_node_spec.rb +36 -0
  30. data/spec/dendroid/recognizer/e_item_spec.rb +5 -5
  31. data/spec/dendroid/recognizer/item_set_spec.rb +16 -8
  32. data/spec/dendroid/recognizer/recognizer_spec.rb +57 -5
  33. data/spec/dendroid/support/sample_grammars.rb +2 -0
  34. data/spec/dendroid/syntax/grammar_spec.rb +16 -21
  35. data/spec/dendroid/syntax/rule_spec.rb +56 -7
  36. data/version.txt +1 -1
  37. metadata +20 -13
  38. data/lib/dendroid/grm_analysis/alternative_item.rb +0 -70
  39. data/lib/dendroid/grm_analysis/production_items.rb +0 -55
  40. data/lib/dendroid/syntax/choice.rb +0 -95
  41. data/lib/dendroid/syntax/production.rb +0 -82
  42. data/spec/dendroid/grm_analysis/alternative_item_spec.rb +0 -12
  43. data/spec/dendroid/grm_analysis/production_items_spec.rb +0 -68
  44. data/spec/dendroid/syntax/choice_spec.rb +0 -68
  45. 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