bud 0.0.8 → 0.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- 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})"
|