rockit 0.7.1

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 (57) hide show
  1. data/BUGS +13 -0
  2. data/LICENSE +280 -0
  3. data/README +172 -0
  4. data/TODO +53 -0
  5. data/VERSION +1 -0
  6. data/lib/packrat/grammar.rb +537 -0
  7. data/lib/rockit/prettyprint/box.rb +60 -0
  8. data/lib/rockit/prettyprint/renderer.rb +41 -0
  9. data/lib/rockit/prettyprint/text_renderer.rb +47 -0
  10. data/lib/rockit/tree/base.rb +223 -0
  11. data/lib/rockit/tree/enter_leave_visitor.rb +12 -0
  12. data/lib/rockit/tree/graphviz.rb +69 -0
  13. data/lib/rockit/tree/visitor.rb +12 -0
  14. data/lib/util/array_alternatives.rb +20 -0
  15. data/lib/util/enter_leave_visitor.rb +69 -0
  16. data/lib/util/graphviz_dot.rb +182 -0
  17. data/lib/util/string_location.rb +42 -0
  18. data/lib/util/visitor.rb +49 -0
  19. data/lib/util/visitor_combinators.rb +14 -0
  20. data/rakefile +200 -0
  21. data/tests/acceptance/packrat/minibasic/atest_minibasic.rb +45 -0
  22. data/tests/acceptance/packrat/minibasic/minibasic.rb +137 -0
  23. data/tests/acceptance/rockit/dparser/atest_any_operator.rb +33 -0
  24. data/tests/acceptance/rockit/dparser/atest_arithmetic_grammar.rb +30 -0
  25. data/tests/acceptance/rockit/dparser/atest_list_operator.rb +57 -0
  26. data/tests/acceptance/rockit/dparser/atest_mult_operator.rb +60 -0
  27. data/tests/acceptance/rockit/dparser/atest_operator_grammar.rb +61 -0
  28. data/tests/acceptance/rockit/dparser/atest_plus_operator.rb +55 -0
  29. data/tests/acceptance/rockit/dparser/atest_samples_calculator.rb +14 -0
  30. data/tests/acceptance/rockit/dparser/atest_samples_minibasic.rb +20 -0
  31. data/tests/acceptance/rockit/dparser/atest_samples_multifunccalculator.rb +36 -0
  32. data/tests/acceptance/rockit/dparser/atest_simple_grammar.rb +34 -0
  33. data/tests/acceptance/rockit/dparser/atest_speculative_code_action.rb +128 -0
  34. data/tests/acceptance/rockit/dparser/calc_tests_common.rb +103 -0
  35. data/tests/unit/packrat/test_interpreting_parser.rb +296 -0
  36. data/tests/unit/parse/utest_ebnf_grammar.rb +50 -0
  37. data/tests/unit/parse/utest_expand_grammar.rb +23 -0
  38. data/tests/unit/parse/utest_grammar.rb +160 -0
  39. data/tests/unit/rockit/assembler/llvm/utest_instructions.rb +41 -0
  40. data/tests/unit/rockit/assembler/llvm/utest_module.rb +19 -0
  41. data/tests/unit/rockit/prettyprint/utest_box.rb +44 -0
  42. data/tests/unit/rockit/tree/utest_tree_base.rb +301 -0
  43. data/tests/unit/rockit/tree/utest_tree_enter_leave_visitor.rb +69 -0
  44. data/tests/unit/rockit/tree/utest_tree_visitor.rb +63 -0
  45. data/tests/unit/rockit/utest_grammar.rb +145 -0
  46. data/tests/unit/rockit/utest_grammar_symbol.rb +11 -0
  47. data/tests/unit/rockit/utest_maybe_operator.rb +12 -0
  48. data/tests/unit/rockit/utest_regexp_terminal.rb +45 -0
  49. data/tests/unit/rockit/utest_repetition_operators.rb +35 -0
  50. data/tests/unit/rockit/utest_rule.rb +23 -0
  51. data/tests/unit/rockit/utest_string_terminal.rb +40 -0
  52. data/tests/unit/util/utest_array_alternatives.rb +23 -0
  53. data/tests/unit/util/utest_enter_leave_visitor.rb +89 -0
  54. data/tests/unit/util/utest_string_location.rb +42 -0
  55. data/tests/unit/util/utest_visitor.rb +92 -0
  56. data/tests/unit/util/utest_visitor_combinators.rb +64 -0
  57. metadata +112 -0
@@ -0,0 +1,223 @@
1
+ require 'pp'
2
+
3
+ # Make sure Rockit module exists if we are loaded separetely from rest
4
+ # of Rockit
5
+ module Rockit; end
6
+
7
+ # Rockit::Tree defines a superclass for syntax trees. The trees has methods
8
+ # for easily creating and pattern matching trees.
9
+ module Rockit::Tree
10
+ class Base
11
+ include Enumerable
12
+
13
+ def self.new_tree_class(name, children_names = [])
14
+ name_as_str = name.to_s
15
+ unless name_as_str =~ /^[A-Z]/
16
+ raise NameError, "identifier #{name} needs to be constant"
17
+ end
18
+ unless constants.include?(name_as_str)
19
+ s = <<-EOC
20
+ class #{name_as_str} < #{self.inspect}
21
+ #{child_accessor_codes(children_names)}
22
+ end
23
+ EOC
24
+ class_eval s
25
+ end
26
+ class_eval "#{name_as_str}"
27
+ end
28
+
29
+ def self.child_accessor_codes(children_names)
30
+ i, s = 0, ""
31
+ while i < children_names.length
32
+ s += child_accessor_code(children_names[i], i)
33
+ i += 1
34
+ end
35
+ s
36
+ end
37
+
38
+ def self.child_accessor_code(children_name, index)
39
+ name = children_name.to_s
40
+ s = <<-EOC
41
+ def #{name}
42
+ @children[#{index}]
43
+ end
44
+ def #{name}=(newValue)
45
+ @children[#{index}] = newValue
46
+ end
47
+ EOC
48
+ end
49
+
50
+ def self.[](*children)
51
+ new(*children)
52
+ end
53
+
54
+ attr_reader :children
55
+
56
+ def initialize(*children)
57
+ @children = children
58
+ end
59
+
60
+ def [](index)
61
+ self.children[index]
62
+ end
63
+
64
+ def num_children
65
+ self.children.length
66
+ end
67
+
68
+ def ==(other)
69
+ self.class == other.class && self.children == other.children
70
+ end
71
+
72
+ def inspect
73
+ class_name + "[" +
74
+ self.children.map {|c| c.inspect}.join(", ") +
75
+ "]"
76
+ end
77
+
78
+ def class_name
79
+ self.class.inspect.split("::").last
80
+ end
81
+
82
+ def each_child
83
+ children.each {|c| yield(c)}
84
+ end
85
+
86
+ alias each each_child
87
+
88
+ # Depth-first traversal of (sub)trees in tree
89
+ def each_tree(&block)
90
+ block.call(self)
91
+ self.children.each do |c|
92
+ if Base === c
93
+ c.each_tree(&block)
94
+ else
95
+ block.call(c)
96
+ end
97
+ end
98
+ end
99
+
100
+ # Assuming klass is a subclass of Base
101
+ def each_tree_of_class(klass, recursive = false, &block)
102
+ cs = self.children
103
+ if self.kind_of?(klass)
104
+ block.call(self)
105
+ cs = [] unless recursive
106
+ end
107
+ cs.each do |c|
108
+ if Base === c
109
+ c.each_tree_of_class(klass, recursive, &block)
110
+ elsif Array === c
111
+ c.each {|ac|
112
+ ac.each_tree_of_class(klass, recursive, &block) if Base === ac
113
+ }
114
+ end
115
+ end
116
+ end
117
+
118
+ def match_tree(tree, matches = Hash.new)
119
+ if self.class == tree.class &&
120
+ self.num_children == tree.num_children
121
+ self.each_with_index do |child, i|
122
+ unless child.match_tree(tree[i], matches)
123
+ matches = nil
124
+ break
125
+ end
126
+ end
127
+ matches
128
+ else
129
+ nil
130
+ end
131
+ end
132
+
133
+ def =~(patternTree)
134
+ pattern = patternTree.upcase_symbols_to_pattern_vars
135
+ pattern.match_tree(self)
136
+ end
137
+
138
+ def select_trees_of_class(klass, recursive = false)
139
+ ary = []
140
+ each_tree_of_class(klass, recursive) {|t| ary << t}
141
+ ary
142
+ end
143
+
144
+ # Return all subtrees in tree which match the pattern self.
145
+ def all_matching_trees(tree)
146
+ # Only the subtrees with a root node of the right type are candidates
147
+ candidates = tree.select_trees_of_class(self.class, true)
148
+ pattern = self.upcase_symbols_to_pattern_vars
149
+ candidates.map do |t|
150
+ res = pattern.match_tree(t)
151
+ res ? [res, t] : nil
152
+ end.compact
153
+ end
154
+
155
+ # Representing a variable in a patternTree
156
+ class PatternVar
157
+ attr_reader :name
158
+
159
+ def initialize(name)
160
+ @name = name
161
+ end
162
+
163
+ # A variable match anything and the binding is noted in the matches
164
+ # hash.
165
+ def match_tree(tree, matches = Hash.new)
166
+ matches[@name] = tree
167
+ matches
168
+ end
169
+
170
+ def inspect
171
+ "Var_" + @name.to_s
172
+ end
173
+
174
+ # No need to def upcase_symbols_to_pattern_vars here since it
175
+ # inherits the one defined for Object below
176
+ end
177
+
178
+ # Convert upper case symbols to pattern variables
179
+ def upcase_symbols_to_pattern_vars
180
+ new_children = Array.new
181
+ self.each {|child| new_children << child.upcase_symbols_to_pattern_vars}
182
+ self.class.new(*new_children)
183
+ end
184
+
185
+ def pretty_print(pp)
186
+ pp.text class_name
187
+ pp.group(1, '[', ']') do
188
+ pp.breakable ""
189
+ pp.seplist(self.children) {|c| pp.pp c}
190
+ end
191
+ end
192
+
193
+ # The nodes in a tree are not linked upwards by default but by calling
194
+ # the set_parents method on the root the parent attribute on each subtree
195
+ # is set and can then be accessed.
196
+ attr_accessor :parent
197
+
198
+ def set_parents(parent = nil)
199
+ self.parent = parent
200
+ each_child {|c| c.set_parents(self) if c.kind_of?(Base)}
201
+ end
202
+ end
203
+ end
204
+
205
+ class Object
206
+ def match_tree(tree, matches = Hash.new)
207
+ self == tree
208
+ end
209
+
210
+ def upcase_symbols_to_pattern_vars
211
+ self
212
+ end
213
+ end
214
+
215
+ class Symbol
216
+ def upcase_symbols_to_pattern_vars
217
+ if self.to_s =~ /^[A-Z]/
218
+ Rockit::Tree::Base::PatternVar.new(self)
219
+ else
220
+ self
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,12 @@
1
+ require 'util/enter_leave_visitor'
2
+ require 'rockit/tree/base'
3
+
4
+ module Rockit::Tree
5
+ class Base
6
+ include EnterLeaveVisitable
7
+
8
+ def accept_visitor(visitor)
9
+ visitor.visit_me_then_children(self, self.children)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,69 @@
1
+ require 'rockit/tree/base'
2
+
3
+ class Object
4
+ def to_graphviz_node_label; inspect; end
5
+ def to_graphviz_printer(printer); printer; end
6
+ end
7
+
8
+ class Array
9
+ def to_graphviz_node_label; "[]"; end
10
+ def to_graphviz_printer(printer)
11
+ label = printer.unique(self)
12
+ each do |child|
13
+ printer.add(label, child)
14
+ child.to_graphviz_printer(printer)
15
+ end
16
+ printer
17
+ end
18
+ end
19
+
20
+ class DotPrinter
21
+ def initialize
22
+ @links = []
23
+ @counts = Hash.new(0)
24
+ end
25
+
26
+ def unique(obj)
27
+ num = (@counts[obj.class] += 1)
28
+ obj.to_graphviz_node_label + " (#{num})"
29
+ end
30
+
31
+ def add(from, to)
32
+ @links << [from.to_graphviz_node_label, to.to_graphviz_node_label]
33
+ end
34
+
35
+ def to_dot_file
36
+ "digraph {" +
37
+ @links.uniq.map {|from, to| "#{from.inspect} -> #{to.inspect};"}.join("\n") +
38
+ "}"
39
+ end
40
+
41
+ def write_to_file(filename, format = "ps")
42
+ tmpfilename = "temp" + Time.now.strftime("%M%S_") + rand(1e10).inspect
43
+ File.open(tmpfilename, "w") do |fh|
44
+ fh.write self.to_dot_file
45
+ system "dot -T#{format} #{tmpfilename} > #{filename}"
46
+ end
47
+ # File.delete(tmpfilename)
48
+ end
49
+ end
50
+
51
+ module Rockit::Tree
52
+ class Base
53
+ def to_graphviz_printer(printer = DotPrinter.new)
54
+ children.each do |child|
55
+ printer.add(self, child)
56
+ child.to_graphviz_printer(printer)
57
+ end
58
+ printer
59
+ end
60
+
61
+ def to_graphviz_node_label
62
+ class_name_as_string
63
+ end
64
+
65
+ def class_name_as_string
66
+ self.class.inspect.split("::").last
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,12 @@
1
+ require 'util/visitor'
2
+ require 'rockit/tree/base'
3
+
4
+ module Rockit::Tree
5
+ class Base
6
+ include Visitable
7
+
8
+ def accept_visitor(visitor)
9
+ visitor.visit_me_and_my_children(self, self.children)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # Collect alternatives to an array by pushing them onto an internal
2
+ # instance variable @__alternatives. We can then assemble all the alternatives
3
+ # (including self) into an Array. This is used for specifying alternative
4
+ # RightHandSides in a Rule in a Grammar.
5
+ class Array
6
+ def __alternatives
7
+ @__alternatives ||= Array.new
8
+ end
9
+
10
+ def /(other)
11
+ self.__alternatives << other
12
+ self
13
+ end
14
+
15
+ def assemble_alternatives
16
+ alternatives = self.__alternatives
17
+ alternatives.unshift self
18
+ alternatives
19
+ end
20
+ end
@@ -0,0 +1,69 @@
1
+ require 'util/visitor'
2
+
3
+ # An EnterLeaveVisitor is a Visitor that visits objects both on the way down
4
+ # and on the way up when traversing an object structure/hierarchy.
5
+ # It will first call enter_X(x) if available, then visit_X(x) if avilable
6
+ # then traverse down into children objects and then call leave_X(x) if
7
+ # available.
8
+ # Objects use visit_all to indicate structure. The first argument to visit_all
9
+ # is a parent object and the rest are children objects.
10
+ # NOTE! If they simply call visit the leave method will never be called but
11
+ # the enter and visit methods will.
12
+ module EnterLeaveVisitor
13
+ include Visitor
14
+
15
+ def visit(obj)
16
+ visit_me_then_children(obj)
17
+ end
18
+
19
+ def enter(obj)
20
+ method = _enter_method(obj)
21
+ self.send(method, obj) if method
22
+ end
23
+
24
+ def leave(obj)
25
+ method = _leave_method(obj)
26
+ self.send(method, obj) if method
27
+ end
28
+
29
+ # Each children should be an array with children
30
+ def visit_me_then_children(parent, *children)
31
+ enter_method, visit_method, leave_method = _evl_methods(parent)
32
+ self.send(enter_method, parent) if enter_method
33
+ self.send(visit_method, parent) if visit_method
34
+ children.each {|cs| cs.each {|c| c.accept_visitor(self)}}
35
+ self.send(leave_method, parent) if leave_method
36
+ end
37
+
38
+ def _evl_methods(obj)
39
+ obj.class.ancestors.map {|c| _evl_methods_for_class(c)}.compact.first
40
+ end
41
+
42
+ def _evl_methods_for_class(klass)
43
+ # Memoized so not dynamic, maybe we should clear cache when a method is
44
+ # added?
45
+ (@_memoized_methods ||= Hash.new)[klass] ||= _methodnames_from_class(klass)
46
+ end
47
+
48
+ def _methodname_from_classname(klassName, prefix)
49
+ name = prefix + klassName
50
+ self.class.instance_methods.include?(name) ? name : nil
51
+ end
52
+
53
+ def _methodnames_from_class(klass)
54
+ class_name = klass.inspect.split("::").last
55
+ l = ["enter_", "visit_", "leave_"].map do |prefix|
56
+ _methodname_from_classname(class_name, prefix)
57
+ end
58
+ # Return nil unless we found at least one method to visit
59
+ (l.compact.length > 0) ? l : nil
60
+ end
61
+ end
62
+
63
+ module EnterLeaveVisitable
64
+ include Visitable
65
+ end
66
+
67
+ class Object
68
+ include EnterLeaveVisitable
69
+ end
@@ -0,0 +1,182 @@
1
+ class DotGraphPrinter
2
+ attr_accessor :orientation, :size, :color
3
+
4
+ # The following can be set to blocks of code that gives a default
5
+ # value for the node shapes, node labels and link labels, respectively.
6
+ attr_accessor :node_shaper, :node_labeler, :link_labeler
7
+
8
+ # A node shaper maps each node to a string describing its shape.
9
+ # Valid shapes are:
10
+ # "ellipse" (default)
11
+ # "box"
12
+ # "circle"
13
+ # "plaintext" (no outline)
14
+ # "doublecircle"
15
+ # "diamond"
16
+ # Not yet supported or untested once are:
17
+ # "polygon", "record", "epsf"
18
+ @@default_node_shaper = proc{|n| "box"}
19
+
20
+ @@default_node_labeler = proc{|n|
21
+ if Symbol===n
22
+ n.id2name
23
+ elsif String===n
24
+ n
25
+ else
26
+ n.inspect
27
+ end
28
+ }
29
+
30
+ @@default_link_labeler = proc{|info| info ? info.inspect : nil}
31
+
32
+ # links is either array of
33
+ # arrays [fromNode, toNode [, infoOnLink]], or
34
+ # objects with attributes :from, :to, :info
35
+ # nodes is array of node objects
36
+ # All nodes used in the links are used as nodes even if they are not
37
+ # in the "nodes" parameters.
38
+ def initialize(links = [], nodes = [])
39
+ @links, @nodes = links, add_nodes_in_links(links, nodes)
40
+ @node_attributes, @edge_attributes = Hash.new, Hash.new
41
+ set_default_values
42
+ end
43
+
44
+ def set_default_values
45
+ @color = "black"
46
+ @size = "9,11"
47
+ @orientation = "portrait"
48
+ @node_shaper = @@default_node_shaper
49
+ @node_labeler = @@default_node_labeler
50
+ @link_labeler = @@default_link_labeler
51
+ end
52
+
53
+ def write_to_file(filename, fileType = "ps")
54
+ dotfile = temp_filename(filename)
55
+ File.open(dotfile, "w") {|f| f.write to_dot_specification}
56
+ system "dot -T#{fileType} -o #{filename} #{dotfile}"
57
+ File.delete(dotfile)
58
+ end
59
+
60
+ def set_edge_attributes(anEdge, aHash)
61
+ # TODO check if attributes are valid dot edge attributes
62
+ edge = find_edge(anEdge)
63
+ set_attributes(edge, @edge_attributes, true, aHash)
64
+ end
65
+
66
+ def set_node_attributes(aNode, aHash)
67
+ # TODO check if attributes are valid dot node attributes
68
+ set_attributes(aNode, @node_attributes, true, aHash)
69
+ end
70
+
71
+ def to_dot_specification
72
+ set_edge_labels(@links)
73
+ set_node_labels_and_shape(@nodes)
74
+ "digraph G {\n" +
75
+ graph_parameters_to_dot_specification +
76
+ @nodes.uniq.map {|n| format_node(n)}.join(";\n") + ";\n" +
77
+ @links.uniq.map {|l| format_link(l)}.join(";\n") + ";\n" +
78
+ "}"
79
+ end
80
+
81
+ protected
82
+
83
+ def find_edge(anEdge)
84
+ @links.each do |link|
85
+ return link if source_and_dest(link) == source_and_dest(anEdge)
86
+ end
87
+ end
88
+
89
+ def set_attributes(key, hash, override, newAttributeHash)
90
+ h = hash[key] || Hash.new
91
+ newAttributeHash = all_keys_to_s(newAttributeHash)
92
+ newAttributeHash.each do |k, value|
93
+ h[k] = value unless h[k] and !override
94
+ end
95
+ hash[key] = h
96
+ end
97
+
98
+ def graph_parameters_to_dot_specification
99
+ "graph [\n" +
100
+ (self.size ? " size = #{@size.inspect},\n" : "") +
101
+ (self.orientation ? " orientation = #{@orientation},\n" : "") +
102
+ (self.color ? " color = #{@color}\n" : "") +
103
+ "]\n"
104
+ end
105
+
106
+ def each_node_in_links(links)
107
+ links.each do |l|
108
+ src, dest = source_and_dest(l)
109
+ yield src
110
+ yield dest
111
+ end
112
+ end
113
+
114
+ def add_nodes_in_links(links, nodes)
115
+ new_nodes = []
116
+ each_node_in_links(links) {|node| new_nodes.push node}
117
+ (nodes + new_nodes).uniq
118
+ end
119
+
120
+ def all_keys_to_s(aHash)
121
+ # MAYBE reuse existing hash?
122
+ Hash[*(aHash.map{|p| p[0] = p[0].to_s; p}.flatten)]
123
+ end
124
+
125
+ def set_edge_labels(edges)
126
+ edges.each do |edge|
127
+ src, dest, info = get_link_data(edge)
128
+ if info
129
+ label = @link_labeler.call(info)
130
+ set_attributes(edge, @edge_attributes, false, :label =>label) if label
131
+ end
132
+ end
133
+ end
134
+
135
+ def set_node_labels_and_shape(nodes)
136
+ nodes.each do |node|
137
+ set_attributes(node, @node_attributes, false,
138
+ :label => @node_labeler.call(node).inspect,
139
+ :shape => @node_shaper.call(node).inspect)
140
+ end
141
+ end
142
+
143
+ def get_link_data(link)
144
+ begin
145
+ return link.from, link.to, link.info
146
+ rescue Exception
147
+ return link[0], link[1], link[2]
148
+ end
149
+ end
150
+
151
+ def source_and_dest(link)
152
+ get_link_data(link)[0,2]
153
+ end
154
+
155
+ def format_attributes(attributes)
156
+ return "" unless attributes
157
+ strings = attributes.map {|a, v| "#{a}=#{v}"}
158
+ strings.length > 0 ? (" [" + strings.join(", ") + "]") : ("")
159
+ end
160
+
161
+ def mangle_node_name(node)
162
+ "n" + node.hash.abs.inspect
163
+ end
164
+
165
+ def format_link(link)
166
+ from, to, info = get_link_data(link)
167
+ mangle_node_name(from) + " -> " + mangle_node_name(to) +
168
+ format_attributes(@edge_attributes[link])
169
+ end
170
+
171
+ def format_node(node)
172
+ mangle_node_name(node) + format_attributes(@node_attributes[node])
173
+ end
174
+
175
+ def temp_filename(base = "tmp")
176
+ tmpfile = base + rand(100000).inspect
177
+ while test(?f, tmpfile)
178
+ tmpfile = base + rand(100000).inspect
179
+ end
180
+ tmpfile
181
+ end
182
+ end
@@ -0,0 +1,42 @@
1
+ # A class for calculating locations (line, column) in a string based
2
+ # on an offset.
3
+ class StringLocations
4
+ def initialize(string)
5
+ @string, @string_length = string, string.length
6
+ @line_lengths = @string.split(/\n/).map {|l| l.length}
7
+ @start_pos_of_line = [0]
8
+ @line_lengths.each do |l|
9
+ @start_pos_of_line << @start_pos_of_line.last + l + 1
10
+ end
11
+ end
12
+
13
+ def location(offset)
14
+ l = line(offset)
15
+ return l, column(offset, l)
16
+ end
17
+
18
+ def column(offset, l = nil)
19
+ l ||= line(offset)
20
+ offset - @start_pos_of_line[l-1]
21
+ end
22
+
23
+ def line(offset)
24
+ if offset >= @string_length
25
+ raise "#{offset} is larger than the string size #{@string_length}"
26
+ end
27
+ line_index(offset) + 1
28
+ end
29
+
30
+ # Find the index to the line where the given offset can be found. Assumes
31
+ # the start_pos_of_line array has been extended so that offset is covered.
32
+ def line_index(offset)
33
+ index = nil
34
+ for index in (0...(@start_pos_of_line.length))
35
+ if @start_pos_of_line[index] <= offset &&
36
+ @start_pos_of_line[index+1] > offset
37
+ break
38
+ end
39
+ end
40
+ index
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ module Visitor
2
+ def visit(obj)
3
+ method = _visitor_method(obj)
4
+ self.send(method, obj) if method
5
+ end
6
+
7
+ def visit_me_and_my_children(me, children)
8
+ visit(me)
9
+ children.each {|c| c.accept_visitor(self)}
10
+ end
11
+
12
+ def visit_me_then_children(obj, *children)
13
+ visit(obj)
14
+ children.each {|cs| cs.each {|c| c.accept_visitor(self)}}
15
+ end
16
+
17
+ # Find the visit_X method to use based on the inheritance tree. First try
18
+ # the class name then traverse the ancestors list.
19
+ def _visitor_method(obj)
20
+ obj.class.ancestors.map {|c| _visitor_method_for_class(c)}.compact.first
21
+ end
22
+
23
+ def _visitor_method_for_class(klass)
24
+ _visitor_methods[klass] ||= _methodname_from_class(klass)
25
+ end
26
+
27
+ def _visitor_methods
28
+ @_visitor_methods ||= Hash.new
29
+ end
30
+
31
+ def _methodname_from_class(klass)
32
+ name = "visit_" + klass.inspect.split("::").last
33
+ self.class.instance_methods.include?(name) ? name : nil
34
+ end
35
+
36
+ def visit_each(ary)
37
+ ary.each {|e| e.accept_visitor(self)}
38
+ end
39
+ end
40
+
41
+ module Visitable
42
+ def accept_visitor(visitor)
43
+ visitor.visit(self)
44
+ end
45
+ end
46
+
47
+ class Object
48
+ include Visitable
49
+ end
@@ -0,0 +1,14 @@
1
+ require 'util/visitor'
2
+
3
+ # Sequentially apply visitors
4
+ class SequenceVisitor
5
+ include Visitor
6
+
7
+ def initialize(*visitors)
8
+ @visitors = visitors
9
+ end
10
+
11
+ def visit(obj)
12
+ @visitors.each {|v| obj.accept_visitor(v)}
13
+ end
14
+ end