rockit 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
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