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.
- data/BUGS +13 -0
- data/LICENSE +280 -0
- data/README +172 -0
- data/TODO +53 -0
- data/VERSION +1 -0
- data/lib/packrat/grammar.rb +537 -0
- data/lib/rockit/prettyprint/box.rb +60 -0
- data/lib/rockit/prettyprint/renderer.rb +41 -0
- data/lib/rockit/prettyprint/text_renderer.rb +47 -0
- data/lib/rockit/tree/base.rb +223 -0
- data/lib/rockit/tree/enter_leave_visitor.rb +12 -0
- data/lib/rockit/tree/graphviz.rb +69 -0
- data/lib/rockit/tree/visitor.rb +12 -0
- data/lib/util/array_alternatives.rb +20 -0
- data/lib/util/enter_leave_visitor.rb +69 -0
- data/lib/util/graphviz_dot.rb +182 -0
- data/lib/util/string_location.rb +42 -0
- data/lib/util/visitor.rb +49 -0
- data/lib/util/visitor_combinators.rb +14 -0
- data/rakefile +200 -0
- data/tests/acceptance/packrat/minibasic/atest_minibasic.rb +45 -0
- data/tests/acceptance/packrat/minibasic/minibasic.rb +137 -0
- data/tests/acceptance/rockit/dparser/atest_any_operator.rb +33 -0
- data/tests/acceptance/rockit/dparser/atest_arithmetic_grammar.rb +30 -0
- data/tests/acceptance/rockit/dparser/atest_list_operator.rb +57 -0
- data/tests/acceptance/rockit/dparser/atest_mult_operator.rb +60 -0
- data/tests/acceptance/rockit/dparser/atest_operator_grammar.rb +61 -0
- data/tests/acceptance/rockit/dparser/atest_plus_operator.rb +55 -0
- data/tests/acceptance/rockit/dparser/atest_samples_calculator.rb +14 -0
- data/tests/acceptance/rockit/dparser/atest_samples_minibasic.rb +20 -0
- data/tests/acceptance/rockit/dparser/atest_samples_multifunccalculator.rb +36 -0
- data/tests/acceptance/rockit/dparser/atest_simple_grammar.rb +34 -0
- data/tests/acceptance/rockit/dparser/atest_speculative_code_action.rb +128 -0
- data/tests/acceptance/rockit/dparser/calc_tests_common.rb +103 -0
- data/tests/unit/packrat/test_interpreting_parser.rb +296 -0
- data/tests/unit/parse/utest_ebnf_grammar.rb +50 -0
- data/tests/unit/parse/utest_expand_grammar.rb +23 -0
- data/tests/unit/parse/utest_grammar.rb +160 -0
- data/tests/unit/rockit/assembler/llvm/utest_instructions.rb +41 -0
- data/tests/unit/rockit/assembler/llvm/utest_module.rb +19 -0
- data/tests/unit/rockit/prettyprint/utest_box.rb +44 -0
- data/tests/unit/rockit/tree/utest_tree_base.rb +301 -0
- data/tests/unit/rockit/tree/utest_tree_enter_leave_visitor.rb +69 -0
- data/tests/unit/rockit/tree/utest_tree_visitor.rb +63 -0
- data/tests/unit/rockit/utest_grammar.rb +145 -0
- data/tests/unit/rockit/utest_grammar_symbol.rb +11 -0
- data/tests/unit/rockit/utest_maybe_operator.rb +12 -0
- data/tests/unit/rockit/utest_regexp_terminal.rb +45 -0
- data/tests/unit/rockit/utest_repetition_operators.rb +35 -0
- data/tests/unit/rockit/utest_rule.rb +23 -0
- data/tests/unit/rockit/utest_string_terminal.rb +40 -0
- data/tests/unit/util/utest_array_alternatives.rb +23 -0
- data/tests/unit/util/utest_enter_leave_visitor.rb +89 -0
- data/tests/unit/util/utest_string_location.rb +42 -0
- data/tests/unit/util/utest_visitor.rb +92 -0
- data/tests/unit/util/utest_visitor_combinators.rb +64 -0
- 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,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,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
|
data/lib/util/visitor.rb
ADDED
@@ -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
|