bud 0.0.8 → 0.1.0.pre1
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/README +4 -10
- data/bin/budplot +1 -2
- data/docs/cheat.md +2 -15
- data/examples/basics/paths.rb +7 -7
- data/lib/bud/aggs.rb +15 -19
- data/lib/bud/bud_meta.rb +165 -77
- data/lib/bud/bust/bust.rb +11 -4
- data/lib/bud/collections.rb +643 -280
- data/lib/bud/depanalysis.rb +50 -25
- data/lib/bud/executor/elements.rb +592 -0
- data/lib/bud/executor/group.rb +104 -0
- data/lib/bud/executor/join.rb +638 -0
- data/lib/bud/graphs.rb +12 -11
- data/lib/bud/joins.rb +2 -1
- data/lib/bud/meta_algebra.rb +5 -4
- data/lib/bud/metrics.rb +9 -3
- data/lib/bud/monkeypatch.rb +131 -23
- data/lib/bud/rebl.rb +41 -28
- data/lib/bud/rewrite.rb +112 -440
- data/lib/bud/server.rb +3 -2
- data/lib/bud/source.rb +109 -0
- data/lib/bud/state.rb +16 -9
- data/lib/bud/storage/dbm.rb +62 -16
- data/lib/bud/storage/zookeeper.rb +2 -2
- data/lib/bud/viz.rb +8 -4
- data/lib/bud/viz_util.rb +10 -9
- data/lib/bud.rb +413 -199
- metadata +40 -55
- data/examples/deploy/tokenring-ec2.rb +0 -26
- data/examples/deploy/tokenring-fork.rb +0 -15
- data/examples/deploy/tokenring-thread.rb +0 -15
- data/examples/deploy/tokenring.rb +0 -47
- data/lib/bud/deploy/deployer.rb +0 -67
- data/lib/bud/deploy/ec2deploy.rb +0 -199
- data/lib/bud/deploy/forkdeploy.rb +0 -90
- data/lib/bud/deploy/threaddeploy.rb +0 -38
- data/lib/bud/storage/tokyocabinet.rb +0 -190
- data/lib/bud/stratify.rb +0 -85
data/lib/bud/rewrite.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'ruby2ruby'
|
2
3
|
|
3
4
|
class RuleRewriter < Ruby2Ruby # :nodoc: all
|
4
5
|
attr_accessor :rule_indx, :rules, :depends
|
@@ -7,12 +8,12 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
7
8
|
@bud_instance = bud_instance
|
8
9
|
@ops = {:<< => 1, :< => 1, :<= => 1}
|
9
10
|
@monotonic_whitelist = {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
:== => 1, :+ => 1, :<= => 1, :- => 1, :< => 1, :> => 1,
|
12
|
+
:* => 1, :pairs => 1, :matches => 1, :combos => 1, :flatten => 1,
|
13
|
+
:lefts => 1, :rights => 1, :map => 1, :flat_map => 1, :pro => 1,
|
14
|
+
:cols => 1, :key_cols => 1, :val_cols => 1, :payloads => 1, :~ => 1,
|
15
|
+
:lambda => 1, :tabname => 1
|
16
|
+
}
|
16
17
|
@temp_ops = {:-@ => 1, :~ => 1, :+@ => 1}
|
17
18
|
@tables = {}
|
18
19
|
@nm = false
|
@@ -23,33 +24,60 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
23
24
|
super()
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
$not_id = [:not_coll_id]
|
28
|
+
def resolve(obj, prefix, name)
|
29
|
+
qn = prefix ? prefix + "." + name.to_s : name.to_s
|
30
|
+
return [:collection, qn, obj.tables[name]] if obj.tables.has_key? name
|
31
|
+
|
32
|
+
# does name refer to an import name?
|
33
|
+
iobj = obj.import_instance name
|
34
|
+
return [:import, qn, iobj] if iobj and iobj.respond_to? :tables
|
35
|
+
|
36
|
+
return $not_id
|
37
|
+
end
|
38
|
+
|
39
|
+
def exp_id_type(recv, name, args) # call only if sexp type is :call
|
40
|
+
return $not_id unless args.size == 1
|
41
|
+
ty = $not_id
|
42
|
+
if recv
|
43
|
+
if recv.first == :call
|
44
|
+
# possibly nested reference.
|
45
|
+
rty, rqn, robj = exp_id_type(recv[1], recv[2], recv[3]) # rty, rqn, .. = receiver's type, qual name etc.
|
46
|
+
ty = resolve(robj, rqn, name) if rty == :import
|
47
|
+
end
|
48
|
+
else
|
49
|
+
# plain, un-prefixed name. See if it refers to a collection or import spec
|
50
|
+
ty = resolve(@bud_instance, nil, name)
|
30
51
|
end
|
31
|
-
|
52
|
+
ty
|
32
53
|
end
|
33
54
|
|
34
55
|
def process_call(exp)
|
35
56
|
recv, op, args = exp
|
36
|
-
if
|
37
|
-
do_table(exp)
|
38
|
-
elsif @ops[op] and @context[1] == :block and @context.length == 4
|
57
|
+
if @ops[op] and @context[1] == :block and @context.length == 4
|
39
58
|
# NB: context.length is 4 when see a method call at the top-level of a
|
40
59
|
# :defn block -- this is where we expect Bloom statements to appear
|
41
60
|
do_rule(exp)
|
42
61
|
else
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
elsif
|
62
|
+
ty = :not_coll_id
|
63
|
+
ty, qn, obj = exp_id_type(recv, op, args) # qn = qualified name, obj is the corresponding object
|
64
|
+
if ty == :collection
|
65
|
+
@tables[qn] = @nm if @collect
|
66
|
+
#elsif ty == :import .. do nothing
|
67
|
+
elsif ty == :not_coll_id
|
68
|
+
# check if receiver is a collection, and further if the current exp represents a field lookup
|
69
|
+
op_is_field_name = false
|
70
|
+
if recv and recv.first == :call
|
71
|
+
rty, _, robj = exp_id_type(recv[1], recv[2], recv[3])
|
72
|
+
if rty == :collection
|
73
|
+
cols = robj.cols
|
74
|
+
op_is_field_name = true if cols and cols.include?(op)
|
75
|
+
end
|
76
|
+
end
|
49
77
|
# for CALM analysis, mark deletion rules as non-monotonic
|
50
78
|
@nm = true if op == :-@
|
51
79
|
# don't worry about monotone ops, table names, table.attr calls, or accessors of iterator variables
|
52
|
-
unless @monotonic_whitelist[op] or
|
80
|
+
unless @monotonic_whitelist[op] or op_is_field_name or (recv and recv.first == :lvar) or op.to_s.start_with?("__")
|
53
81
|
@nm = true
|
54
82
|
end
|
55
83
|
end
|
@@ -62,6 +90,18 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
62
90
|
|
63
91
|
def collect_rhs(exp)
|
64
92
|
@collect = true
|
93
|
+
# rewrite constant array expressions to lambdas
|
94
|
+
if exp[0] and exp[0] == :arglist
|
95
|
+
# the <= case
|
96
|
+
if exp[1] and exp[1][0] == :array
|
97
|
+
exp = s(exp[0], s(:iter, s(:call, nil, :lambda, s(:arglist)), nil, exp[1]))
|
98
|
+
# the superator case
|
99
|
+
elsif exp[1] and exp[1][0] == :call \
|
100
|
+
and exp[1][1] and exp[1][1][0] and exp[1][1][0] == :array \
|
101
|
+
and exp[1][2] and (exp[1][2] == :+@ or exp[1][2] == :-@ or exp[1][2] == :~@)
|
102
|
+
exp = s(exp[0], s(exp[1][0], s(:iter, s(:call, nil, :lambda, s(:arglist)), nil, exp[1][1]), exp[1][2], exp[1][3]))
|
103
|
+
end
|
104
|
+
end
|
65
105
|
rhs = process exp
|
66
106
|
@collect = false
|
67
107
|
return rhs
|
@@ -82,38 +122,36 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
82
122
|
op = op.to_s
|
83
123
|
end
|
84
124
|
|
85
|
-
@rules << [@rule_indx, lhs, op, rule_txt, rule_txt_orig]
|
125
|
+
@rules << [@bud_instance, @rule_indx, lhs, op, rule_txt, rule_txt_orig]
|
86
126
|
@tables.each_pair do |t, non_monotonic|
|
87
|
-
@depends << [@rule_indx, lhs, op, t, non_monotonic]
|
127
|
+
@depends << [@bud_instance, @rule_indx, lhs, op, t, non_monotonic]
|
88
128
|
end
|
89
129
|
|
90
130
|
reset_instance_vars
|
91
131
|
@rule_indx += 1
|
92
132
|
end
|
93
133
|
|
94
|
-
def do_table(exp)
|
95
|
-
t = exp[1].to_s
|
96
|
-
# If we're called on a "table-like" part of the AST that doesn't correspond
|
97
|
-
# to an extant table, ignore it.
|
98
|
-
@tables[t] = @nm if @bud_instance.tables.has_key? t.to_sym and not @tables[t]
|
99
|
-
drain(exp)
|
100
|
-
return t
|
101
|
-
end
|
102
|
-
|
103
134
|
def do_rule(exp)
|
104
135
|
lhs = process exp[0]
|
105
136
|
op = exp[1]
|
106
|
-
|
137
|
+
rhs_ast = map2pro(exp[2])
|
138
|
+
|
139
|
+
# Remove the outer s(:arglist) from the rhs AST. An AST subtree rooted with
|
140
|
+
# s(:arglist) is not really sensible and it causes Ruby2Ruby < 1.3.1 to
|
141
|
+
# misbehave (for example, s(:arglist, s(:hash, ...)) is misparsed.
|
142
|
+
raise Bud::CompileError unless rhs_ast.sexp_type == :arglist
|
143
|
+
#rhs_ast = rhs_ast[1]
|
144
|
+
|
107
145
|
if @bud_instance.options[:no_attr_rewrite]
|
108
|
-
rhs = collect_rhs(
|
146
|
+
rhs = collect_rhs(rhs_ast)
|
109
147
|
rhs_pos = rhs
|
110
148
|
else
|
111
149
|
# need a deep copy of the rules so we can keep a version without AttrName
|
112
150
|
# Rewrite
|
113
|
-
|
114
|
-
rhs = collect_rhs(
|
151
|
+
rhs_ast_dup = Marshal.load(Marshal.dump(rhs_ast))
|
152
|
+
rhs = collect_rhs(rhs_ast)
|
115
153
|
reset_instance_vars
|
116
|
-
rhs_pos = collect_rhs(AttrNameRewriter.new(@bud_instance).process(
|
154
|
+
rhs_pos = collect_rhs(AttrNameRewriter.new(@bud_instance).process(rhs_ast_dup))
|
117
155
|
end
|
118
156
|
record_rule(lhs, op, rhs_pos, rhs)
|
119
157
|
drain(exp)
|
@@ -123,12 +161,18 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
123
161
|
# to do this precisely (issue #225), so we just replace map calls liberally
|
124
162
|
# and define Enumerable#pro as an alias for "map".
|
125
163
|
def map2pro(exp)
|
164
|
+
# the non-superator case
|
126
165
|
if exp[1] and exp[1][0] and exp[1][0] == :iter \
|
127
166
|
and exp[1][1] and exp[1][1][1] and exp[1][1][1][0] == :call
|
128
167
|
if exp[1][1][2] == :map
|
129
168
|
exp[1][1][2] = :pro
|
130
169
|
end
|
131
|
-
|
170
|
+
# the superator case
|
171
|
+
elsif exp[1] and exp[1][0] == :call and (exp[1][2] == :~@ or exp[1][2] == :+@ or exp[1][2] == :-@)
|
172
|
+
if exp[1][1] and exp[1][1][1] and exp[1][1][1][2] == :map
|
173
|
+
exp[1][1][1][2] = :pro
|
174
|
+
end
|
175
|
+
end
|
132
176
|
exp
|
133
177
|
end
|
134
178
|
|
@@ -168,11 +212,18 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
|
|
168
212
|
else
|
169
213
|
raise Bud::CompileError, "nested redefinition of block variable \"#{exp[2][1]}\" not allowed" if @iterhash[exp[2][1]]
|
170
214
|
end
|
171
|
-
elsif exp[2] and exp[2][0] == :masgn and not @collnames.empty? # join iter
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
215
|
+
elsif exp[2] and exp[2][0] == :masgn and not @collnames.empty? # join or reduce iter
|
216
|
+
return unless exp[2][1] and exp[2][1][0] == :array
|
217
|
+
if exp[1][2] == :reduce
|
218
|
+
unless @collnames.length == 1
|
219
|
+
raise Bud::Error, "reduce should only one associated collection, but has #{@collnames.inspect}"
|
220
|
+
end
|
221
|
+
@iterhash[exp[2][1][2][1]] = @collnames.first
|
222
|
+
else #join
|
223
|
+
@collnames.each_with_index do |c, i|
|
224
|
+
next unless exp[2][1][i+1] and exp[2][1][i+1][0] == :lasgn
|
225
|
+
@iterhash[exp[2][1][i+1][1]] = c
|
226
|
+
end
|
176
227
|
end
|
177
228
|
end
|
178
229
|
end
|
@@ -180,9 +231,23 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
|
|
180
231
|
exp
|
181
232
|
end
|
182
233
|
|
234
|
+
def register_scratch(name, schemahash)
|
235
|
+
# define a scratch with the name and schema in this rename block
|
236
|
+
hash, key_array, val_array = schemahash
|
237
|
+
key_array ||= []
|
238
|
+
val_array ||= []
|
239
|
+
key_cols = key_array.map{|i| i[1] if i.class <= Sexp}.compact
|
240
|
+
val_cols = val_array.map{|i| i[1] if i.class <= Sexp}.compact
|
241
|
+
@bud_instance.scratch(name, key_cols=>val_cols)
|
242
|
+
end
|
243
|
+
|
183
244
|
def gather_collection_names(exp)
|
184
245
|
if exp[0] == :call and exp[1].nil?
|
185
246
|
@collnames << exp[2]
|
247
|
+
elsif exp[2] and exp[2] == :rename
|
248
|
+
arglist, namelit, schemahash = exp[3]
|
249
|
+
# and add name to @collnames
|
250
|
+
@collnames << namelit[1]
|
186
251
|
else
|
187
252
|
exp.each { |e| gather_collection_names(e) if e and e.class <= Sexp }
|
188
253
|
end
|
@@ -191,6 +256,10 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
|
|
191
256
|
def process_call(exp)
|
192
257
|
call, recv, op, args = exp
|
193
258
|
|
259
|
+
if op == :rename
|
260
|
+
arglist, namelit, schemahash = args
|
261
|
+
register_scratch(namelit[1], schemahash)
|
262
|
+
end
|
194
263
|
if recv and recv.class == Sexp and recv.first == :lvar and recv[1] and @iterhash[recv[1]]
|
195
264
|
if @bud_instance.respond_to?(@iterhash[recv[1]])
|
196
265
|
if @bud_instance.send(@iterhash[recv[1]]).class <= Bud::BudCollection
|
@@ -211,131 +280,6 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
|
|
211
280
|
end
|
212
281
|
end
|
213
282
|
|
214
|
-
# Given a table of renames from x => y, replace all calls to "x" with calls to
|
215
|
-
# "y" instead. We don't try to handle shadowing due to block variables: if a
|
216
|
-
# block references a block variable that shadows an identifier in the rename
|
217
|
-
# table, it should appear as an :lvar node rather than a :call, so we should be
|
218
|
-
# okay.
|
219
|
-
class CallRewriter < SexpProcessor # :nodoc: all
|
220
|
-
def initialize(rename_tbl)
|
221
|
-
super()
|
222
|
-
self.require_empty = false
|
223
|
-
self.expected = Sexp
|
224
|
-
@rename_tbl = rename_tbl
|
225
|
-
end
|
226
|
-
|
227
|
-
def process_call(exp)
|
228
|
-
tag, recv, meth_name, args = exp
|
229
|
-
|
230
|
-
if @rename_tbl.has_key? meth_name
|
231
|
-
meth_name = @rename_tbl[meth_name] # No need to deep-copy Symbol
|
232
|
-
end
|
233
|
-
|
234
|
-
recv = process(recv)
|
235
|
-
args = process(args)
|
236
|
-
|
237
|
-
s(tag, recv, meth_name, args)
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
# Rewrite qualified references to collections defined by an imported module. In
|
242
|
-
# the AST, this looks like a tree of :call nodes. For example, a.b.c looks like:
|
243
|
-
#
|
244
|
-
# (:call, (:call, (:call, nil, :a, args), :b, args), :c, args)
|
245
|
-
#
|
246
|
-
# If the import table contains [a][b], we want to rewrite this into a single
|
247
|
-
# call to a__b__c, which matches how the corresponding Bloom collection will
|
248
|
-
# be name-mangled. Note that we don't currently check that a__b__c (or a.b.c)
|
249
|
-
# corresponds to an extant Bloom collection.
|
250
|
-
class NestedRefRewriter < SexpProcessor # :nodoc: all
|
251
|
-
attr_accessor :did_work
|
252
|
-
|
253
|
-
def initialize(mod)
|
254
|
-
super()
|
255
|
-
self.require_empty = false
|
256
|
-
self.expected = Sexp
|
257
|
-
|
258
|
-
@import_tbl = NestedRefRewriter.build_import_table(mod)
|
259
|
-
@did_work = false
|
260
|
-
end
|
261
|
-
|
262
|
-
# If module Y imports Z as "z" and X includes Y, X can contain a reference
|
263
|
-
# to "z.foo". Hence, when expanding nested references in X, we want to merge
|
264
|
-
# the import tables of X and any modules that X includes; however, we can
|
265
|
-
# skip the Bud module, as well as any modules generated via the import
|
266
|
-
# system.
|
267
|
-
def self.build_import_table(mod)
|
268
|
-
child_tbl = mod.bud_import_table.clone
|
269
|
-
mod.modules.each do |m|
|
270
|
-
next if m == Bud
|
271
|
-
next if m.instance_variable_get('@bud_imported_module')
|
272
|
-
|
273
|
-
child_tbl = NestedRefRewriter.merge_import_table(child_tbl,
|
274
|
-
m.bud_import_table)
|
275
|
-
end
|
276
|
-
child_tbl
|
277
|
-
end
|
278
|
-
|
279
|
-
def self.merge_import_table(old, new)
|
280
|
-
old ||= {}
|
281
|
-
old.merge(new) do |key, old_val, new_val|
|
282
|
-
NestedRefRewriter.merge_import_table(old_val, new_val)
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
def process_call(exp)
|
287
|
-
return exp if @import_tbl.empty?
|
288
|
-
tag, recv, meth_name, args = exp
|
289
|
-
|
290
|
-
catch :skip do
|
291
|
-
recv_stack = make_recv_stack(recv)
|
292
|
-
throw :skip unless recv_stack.length > 0
|
293
|
-
|
294
|
-
lookup_tbl = @import_tbl
|
295
|
-
new_meth_name = ""
|
296
|
-
until recv_stack.empty?
|
297
|
-
m = recv_stack.pop
|
298
|
-
throw :skip unless lookup_tbl.has_key? m
|
299
|
-
|
300
|
-
new_meth_name += "#{m}__"
|
301
|
-
lookup_tbl = lookup_tbl[m]
|
302
|
-
end
|
303
|
-
|
304
|
-
# Okay, apply the rewrite
|
305
|
-
@did_work = true
|
306
|
-
new_meth_name += meth_name.to_s
|
307
|
-
recv = nil
|
308
|
-
meth_name = new_meth_name.to_sym
|
309
|
-
end
|
310
|
-
|
311
|
-
recv = process(recv)
|
312
|
-
args = process(args)
|
313
|
-
|
314
|
-
s(tag, recv, meth_name, args)
|
315
|
-
end
|
316
|
-
|
317
|
-
private
|
318
|
-
def make_recv_stack(r)
|
319
|
-
rv = []
|
320
|
-
|
321
|
-
while true
|
322
|
-
break if r.nil?
|
323
|
-
# We can exit early if we see something unexpected
|
324
|
-
throw :skip unless r.sexp_type == :call
|
325
|
-
|
326
|
-
recv, meth_name, args = r.sexp_body
|
327
|
-
unless args.sexp_type == :arglist and args.sexp_body.length == 0
|
328
|
-
throw :skip
|
329
|
-
end
|
330
|
-
|
331
|
-
rv << meth_name
|
332
|
-
r = recv
|
333
|
-
end
|
334
|
-
|
335
|
-
return rv
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
283
|
# Look for temp declarations and remove the "temp" keyword, yielding code that
|
340
284
|
# we can safely eval. We also record the set of "temp" collections we've seen,
|
341
285
|
# and provide a helper method that returns the AST of a state block that
|
@@ -570,275 +514,3 @@ class DefnRenamer < SexpProcessor # :nodoc: all
|
|
570
514
|
end
|
571
515
|
end
|
572
516
|
|
573
|
-
module ModuleRewriter # :nodoc: all
|
574
|
-
# Do the heavy-lifting to import the Bloom module "mod" into the class/module
|
575
|
-
# "import_site", bound to "local_name" at the import site. We implement this
|
576
|
-
# by converting the imported module into an AST and rewriting the AST like so:
|
577
|
-
#
|
578
|
-
# (a) statements in the module that reference sub-modules are rewritten to
|
579
|
-
# reference the mangled name of the submodule
|
580
|
-
# (b) the module name is mangled to include the local bind name and the
|
581
|
-
# import site
|
582
|
-
# (c) instance method names are mangled to include the local bind name
|
583
|
-
# (d) collection names are mangled to include the local bind name
|
584
|
-
# (e) statements in the module are rewritten to reference the mangled names
|
585
|
-
#
|
586
|
-
# We then convert the rewritten AST back into Ruby source code using Ruby2Ruby
|
587
|
-
# and eval() it to define a new module. We return the name of that newly
|
588
|
-
# defined module; the caller can then use "include" to load the module into
|
589
|
-
# the import site. Note that additional rewrites are needed to ensure that
|
590
|
-
# code in the import site that accesses module contents does the right thing;
|
591
|
-
# see Bud#rewrite_local_methods.
|
592
|
-
|
593
|
-
@@with_id = 0 # upon initialize
|
594
|
-
def self.with_id
|
595
|
-
@@with_id
|
596
|
-
end
|
597
|
-
|
598
|
-
def self.incr_with_id
|
599
|
-
@@with_id += 1
|
600
|
-
end
|
601
|
-
|
602
|
-
def self.do_import(import_site, mod, local_name)
|
603
|
-
# ast_process_withs modifies its argument as a side-effect
|
604
|
-
# and returns a matching ast.
|
605
|
-
# hence we run it before the other rewrites.
|
606
|
-
ast = ast_process_withs(mod)
|
607
|
-
ast = ast_flatten_nested_refs(ast, mod)
|
608
|
-
ast = ast_process_temps(ast, mod)
|
609
|
-
|
610
|
-
ast, new_mod_name = ast_rename_module(ast, import_site, mod, local_name)
|
611
|
-
rename_tbl = {}
|
612
|
-
ast = ast_rename_methods(ast, local_name, rename_tbl)
|
613
|
-
ast = ast_rename_state(ast, local_name, rename_tbl)
|
614
|
-
ast = ast_update_refs(ast, rename_tbl)
|
615
|
-
|
616
|
-
str = Ruby2Ruby.new.process(ast)
|
617
|
-
rv = import_site.module_eval str
|
618
|
-
raise Bud::CompileError unless rv.nil?
|
619
|
-
|
620
|
-
# Set an instance variable to allow modules produced by the import/rewrite
|
621
|
-
# process to be distinguished from "normal" Ruby modules.
|
622
|
-
mod = import_site.module_eval new_mod_name
|
623
|
-
raise Bud::CompileError unless mod.class == Module
|
624
|
-
mod.instance_variable_set("@bud_imported_module", true)
|
625
|
-
|
626
|
-
return new_mod_name
|
627
|
-
end
|
628
|
-
|
629
|
-
def self.get_module_ast(mod)
|
630
|
-
raw_ast = get_raw_parse_tree(mod)
|
631
|
-
unless raw_ast.first == :module
|
632
|
-
raise Bud::CompileError, "import must be used with a Module"
|
633
|
-
end
|
634
|
-
|
635
|
-
return Unifier.new.process(raw_ast)
|
636
|
-
end
|
637
|
-
|
638
|
-
# Returns the AST for the given module (as a tree of Sexps). ParseTree
|
639
|
-
# provides native support for doing this, but we choose to do it ourselves. In
|
640
|
-
# ParseTree <= 3.0.7, the support is buggy; in later versions of ParseTree,
|
641
|
-
# the AST is returned in a different format than we expect. In particular, we
|
642
|
-
# expect that the methods from any modules included in the target module will
|
643
|
-
# be "inlined" into the dumped AST; ParseTree > 3.0.7 adds an "include"
|
644
|
-
# statement to the AST instead. In the long run we should probably adapt the
|
645
|
-
# module rewrite system to work with ParseTree > 3.0.7 and get rid of this
|
646
|
-
# code, but that will require further changes.
|
647
|
-
def self.get_raw_parse_tree(klass)
|
648
|
-
pt = RawParseTree.new(false)
|
649
|
-
klassname = klass.name
|
650
|
-
klassname = klass.to_s if klassname.empty? #("anon_" + Process.pid.to_s + "_" + klass.object_id.to_s) if klassname.empty
|
651
|
-
klassname = klassname.to_sym
|
652
|
-
|
653
|
-
code = if Class === klass then
|
654
|
-
sc = klass.superclass
|
655
|
-
sc_name = ((sc.nil? or sc.name.empty?) ? "nil" : sc.name).intern
|
656
|
-
[:class, klassname, [:const, sc_name]]
|
657
|
-
else
|
658
|
-
[:module, klassname]
|
659
|
-
end
|
660
|
-
|
661
|
-
method_names = klass.private_instance_methods false
|
662
|
-
# protected methods are included in instance_methods, go figure!
|
663
|
-
|
664
|
-
# Get the set of classes/modules that define instance methods we want to
|
665
|
-
# include in the result
|
666
|
-
relatives = klass.modules + [klass]
|
667
|
-
relatives.each do |r|
|
668
|
-
method_names += r.instance_methods false
|
669
|
-
end
|
670
|
-
|
671
|
-
# For each distinct method name, use the implementation that appears the
|
672
|
-
# furthest down in the inheritance hierarchy.
|
673
|
-
relatives.reverse!
|
674
|
-
method_names.uniq.sort.each do |m|
|
675
|
-
relatives.each do |r|
|
676
|
-
t = pt.parse_tree_for_method(r, m.to_sym)
|
677
|
-
if t != [nil]
|
678
|
-
code << t
|
679
|
-
break
|
680
|
-
end
|
681
|
-
end
|
682
|
-
end
|
683
|
-
|
684
|
-
klass.singleton_methods(false).sort.each do |m|
|
685
|
-
code << pt.parse_tree_for_method(klass, m.to_sym, true)
|
686
|
-
end
|
687
|
-
|
688
|
-
return code
|
689
|
-
end
|
690
|
-
|
691
|
-
# If this module imports a submodule and binds it to :x, references to x.t1
|
692
|
-
# need to be flattened to the mangled name of x.t1.
|
693
|
-
def self.ast_flatten_nested_refs(ast, mod)
|
694
|
-
NestedRefRewriter.new(mod).process(ast)
|
695
|
-
end
|
696
|
-
|
697
|
-
# Handle temp collections defined in the module's Bloom blocks.
|
698
|
-
def self.ast_process_temps(ast, mod)
|
699
|
-
t = TempExpander.new
|
700
|
-
ast = t.process(ast)
|
701
|
-
|
702
|
-
new_meth = t.get_state_meth(mod)
|
703
|
-
if new_meth
|
704
|
-
# Insert the new extra state method into the module's AST
|
705
|
-
ast << new_meth
|
706
|
-
end
|
707
|
-
return ast
|
708
|
-
end
|
709
|
-
|
710
|
-
def self.ast_mangle_with(w,klass)
|
711
|
-
r2r = Ruby2Ruby.new
|
712
|
-
|
713
|
-
while st = w.get_state_meth(klass)
|
714
|
-
# generate the module
|
715
|
-
tmpmod = Module.new
|
716
|
-
|
717
|
-
# add a state block to define a temp for the collection name
|
718
|
-
state_src = r2r.process(st)
|
719
|
-
tmpmod.module_eval(state_src)
|
720
|
-
|
721
|
-
# add a bloom block
|
722
|
-
bloom_blk = s(:defn, :__bloom__rules, s(:args), s(:scope, s(:block)))
|
723
|
-
inblk = bloom_blk[3][1]
|
724
|
-
|
725
|
-
# add in the rule that was in the "with" definition
|
726
|
-
newdefn = w.with_defns.pop
|
727
|
-
inblk << newdefn unless newdefn.nil?
|
728
|
-
|
729
|
-
# add in all the rules from the body of the "with" block
|
730
|
-
newrules = w.with_rules.pop
|
731
|
-
newrules.each_with_index do |ast, i|
|
732
|
-
inblk << ast unless i == 0
|
733
|
-
end
|
734
|
-
bloom_src = r2r.process(bloom_blk)
|
735
|
-
|
736
|
-
# eval all that Ruby we generated and import new Module into our code
|
737
|
-
tmpmod.module_eval(bloom_src)
|
738
|
-
modname = "with__#{ModuleRewriter.with_id.to_s}"
|
739
|
-
klass.import tmpmod => modname.to_sym
|
740
|
-
|
741
|
-
ModuleRewriter.incr_with_id
|
742
|
-
end
|
743
|
-
end
|
744
|
-
|
745
|
-
def self.ast_process_withs(mod)
|
746
|
-
# strategy to handle withs:
|
747
|
-
# 1) run WithExpander#process to delete the "with" blocks and extract their contents
|
748
|
-
# 2) get the state and rules mangled appropriately into modules
|
749
|
-
# 3) run mod.import on each
|
750
|
-
# 4) call self.get_raw_parse_tree on the result to generate an AST
|
751
|
-
|
752
|
-
ast = get_module_ast(mod)
|
753
|
-
w = WithExpander.new
|
754
|
-
ast = w.process(ast)
|
755
|
-
mod_s, name_s, blocks = ast[0], ast[1], ast[2..-1]
|
756
|
-
tag, name, args, scope = blocks[0]
|
757
|
-
|
758
|
-
self.ast_mangle_with(w, mod)
|
759
|
-
|
760
|
-
retval = Unifier.new.process(self.get_raw_parse_tree(mod))
|
761
|
-
return retval
|
762
|
-
# return s(mod_s, name_s, *blocks)
|
763
|
-
end
|
764
|
-
|
765
|
-
# Rename the given module's name to be a mangle of import site, imported
|
766
|
-
# module, and local bind name. We also rename all the instance methods defined
|
767
|
-
# in the module to include the local bind name (including the special "state",
|
768
|
-
# "bootstrap", and "bloom" methods).
|
769
|
-
def self.ast_rename_module(ast, importer, importee, local_name)
|
770
|
-
mod_name = ast.sexp_body.first
|
771
|
-
raise Bud::CompileError if mod_name.to_s != importee.to_s
|
772
|
-
|
773
|
-
# If the importer or importee modules are nested inside an outer module,
|
774
|
-
# strip off the outer module name before using for name mangling purposes
|
775
|
-
importer_name = Module.get_class_name(importer)
|
776
|
-
importee_name = Module.get_class_name(importee)
|
777
|
-
new_name = "#{importer_name}__#{importee_name}__#{local_name}"
|
778
|
-
ast[1] = new_name.to_sym
|
779
|
-
|
780
|
-
# XXX: it would be nice to return a Module, rather than a string containing
|
781
|
-
# the Module's name. Unfortunately, I can't see how to do that.
|
782
|
-
return [ast, new_name]
|
783
|
-
end
|
784
|
-
|
785
|
-
def self.ast_rename_methods(ast, local_name, rename_tbl)
|
786
|
-
DefnRenamer.new(local_name, rename_tbl).process(ast)
|
787
|
-
end
|
788
|
-
|
789
|
-
# Mangle the names of all the collections defined in state blocks found in the
|
790
|
-
# given module's AST. Returns a table mapping old => new names.
|
791
|
-
def self.ast_rename_state(ast, local_name, rename_tbl)
|
792
|
-
# Find all the state blocks in the AST
|
793
|
-
raise Bud::CompileError unless ast.sexp_type == :module
|
794
|
-
|
795
|
-
ast.sexp_body.each do |b|
|
796
|
-
next unless b.class <= Sexp
|
797
|
-
next if b.sexp_type != :defn
|
798
|
-
|
799
|
-
def_name, args, scope = b.sexp_body
|
800
|
-
next unless /^__state\d+__/.match def_name.to_s
|
801
|
-
|
802
|
-
raise Bud::CompileError unless scope.sexp_type == :scope
|
803
|
-
state_block = scope.sexp_body.first
|
804
|
-
raise Bud::CompileError unless state_block.sexp_type == :block
|
805
|
-
next unless state_block.sexp_body
|
806
|
-
|
807
|
-
# Look for collection definition statements inside the block
|
808
|
-
state_block.sexp_body.each do |e|
|
809
|
-
raise Bud::CompileError unless e.sexp_type == :call
|
810
|
-
|
811
|
-
recv, meth_name, args = e.sexp_body
|
812
|
-
raise Bud::CompileError unless args.sexp_type == :arglist
|
813
|
-
|
814
|
-
if meth_name == :interface
|
815
|
-
tbl_name_node = args.sexp_body[1]
|
816
|
-
else
|
817
|
-
tbl_name_node = args.sexp_body[0]
|
818
|
-
end
|
819
|
-
|
820
|
-
if tbl_name_node.nil? or tbl_name_node.sexp_type != :lit
|
821
|
-
raise Bud::CompileError, "syntax error in state block"
|
822
|
-
end
|
823
|
-
tbl_name = tbl_name_node.sexp_body.first
|
824
|
-
|
825
|
-
new_tbl_name = "#{local_name}__#{tbl_name}".to_sym
|
826
|
-
rename_tbl[tbl_name] = new_tbl_name
|
827
|
-
|
828
|
-
tbl_name_node[1] = new_tbl_name
|
829
|
-
end
|
830
|
-
end
|
831
|
-
|
832
|
-
return ast
|
833
|
-
end
|
834
|
-
|
835
|
-
def self.ast_update_refs(ast, rename_tbl)
|
836
|
-
CallRewriter.new(rename_tbl).process(ast)
|
837
|
-
end
|
838
|
-
|
839
|
-
# Return a list of symbols containing the names of def blocks containing Bloom
|
840
|
-
# rules in the given module and all of its ancestors.
|
841
|
-
def self.get_rule_defs(mod)
|
842
|
-
mod.instance_methods.select {|m| m =~ /^__bloom__.+$/}
|
843
|
-
end
|
844
|
-
end
|
data/lib/bud/server.rb
CHANGED
@@ -38,11 +38,12 @@ class Bud::BudServer < EM::Connection #:nodoc: all
|
|
38
38
|
|
39
39
|
begin
|
40
40
|
@bud.tick_internal if @bud.running_async
|
41
|
-
rescue Exception
|
41
|
+
rescue Exception => e
|
42
42
|
# If we raise an exception here, EM dies, which causes problems (e.g.,
|
43
43
|
# other Bud instances in the same process will crash). Ignoring the
|
44
44
|
# error isn't best though -- we should do better (#74).
|
45
|
-
puts "Exception handling network messages: #{
|
45
|
+
puts "Exception handling network messages: #{e}"
|
46
|
+
puts e.backtrace
|
46
47
|
puts "Inbound messages:"
|
47
48
|
@bud.inbound.each do |chn_name, t|
|
48
49
|
puts " #{t.inspect} (channel: #{chn_name})"
|