bud 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +9 -0
- data/README +30 -0
- data/bin/budplot +134 -0
- data/bin/budvis +201 -0
- data/bin/rebl +4 -0
- data/docs/README.md +13 -0
- data/docs/bfs.md +379 -0
- data/docs/bfs.raw +251 -0
- data/docs/bfs_arch.png +0 -0
- data/docs/bloom-loop.png +0 -0
- data/docs/bust.md +83 -0
- data/docs/cheat.md +291 -0
- data/docs/deploy.md +96 -0
- data/docs/diffs +181 -0
- data/docs/getstarted.md +296 -0
- data/docs/intro.md +36 -0
- data/docs/modules.md +112 -0
- data/docs/operational.md +96 -0
- data/docs/rebl.md +99 -0
- data/docs/ruby_hooks.md +19 -0
- data/docs/visualizations.md +75 -0
- data/examples/README +1 -0
- data/examples/basics/hello.rb +12 -0
- data/examples/basics/out +1103 -0
- data/examples/basics/out.new +856 -0
- data/examples/basics/paths.rb +51 -0
- data/examples/bust/README.md +9 -0
- data/examples/bust/bustclient-example.rb +23 -0
- data/examples/bust/bustinspector.html +135 -0
- data/examples/bust/bustserver-example.rb +18 -0
- data/examples/chat/README.md +9 -0
- data/examples/chat/chat.rb +45 -0
- data/examples/chat/chat_protocol.rb +8 -0
- data/examples/chat/chat_server.rb +29 -0
- data/examples/deploy/tokenring-ec2.rb +26 -0
- data/examples/deploy/tokenring-local.rb +17 -0
- data/examples/deploy/tokenring.rb +39 -0
- data/lib/bud/aggs.rb +126 -0
- data/lib/bud/bud_meta.rb +185 -0
- data/lib/bud/bust/bust.rb +126 -0
- data/lib/bud/bust/client/idempotence.rb +10 -0
- data/lib/bud/bust/client/restclient.rb +49 -0
- data/lib/bud/collections.rb +937 -0
- data/lib/bud/depanalysis.rb +44 -0
- data/lib/bud/deploy/countatomicdelivery.rb +50 -0
- data/lib/bud/deploy/deployer.rb +67 -0
- data/lib/bud/deploy/ec2deploy.rb +200 -0
- data/lib/bud/deploy/localdeploy.rb +41 -0
- data/lib/bud/errors.rb +15 -0
- data/lib/bud/graphs.rb +405 -0
- data/lib/bud/joins.rb +300 -0
- data/lib/bud/rebl.rb +314 -0
- data/lib/bud/rewrite.rb +523 -0
- data/lib/bud/rtrace.rb +27 -0
- data/lib/bud/server.rb +43 -0
- data/lib/bud/state.rb +108 -0
- data/lib/bud/storage/tokyocabinet.rb +170 -0
- data/lib/bud/storage/zookeeper.rb +178 -0
- data/lib/bud/stratify.rb +83 -0
- data/lib/bud/viz.rb +65 -0
- data/lib/bud.rb +797 -0
- metadata +330 -0
data/lib/bud/rewrite.rb
ADDED
@@ -0,0 +1,523 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ruby2ruby'
|
3
|
+
|
4
|
+
class RuleRewriter < Ruby2Ruby #:nodoc: all
|
5
|
+
attr_accessor :rule_indx, :rules, :depends
|
6
|
+
|
7
|
+
def initialize(seed, bud_instance)
|
8
|
+
@bud_instance = bud_instance
|
9
|
+
@ops = {:<< => 1, :< => 1, :<= => 1}
|
10
|
+
@monotonic_whitelist = {
|
11
|
+
:== => 1, :+ => 1, :<= => 1, :- => 1, :< => 1, :> => 1,
|
12
|
+
:* => 1, :pairs => 1, :matches => 1, :flatten => 1,
|
13
|
+
:lefts => 1, :rights => 1, :map => 1, :pro => 1, :schema => 1
|
14
|
+
}
|
15
|
+
@temp_ops = {:-@ => 1, :~ => 1, :+@ => 1}
|
16
|
+
@tables = {}
|
17
|
+
@nm = false
|
18
|
+
@rule_indx = seed
|
19
|
+
@collect = false
|
20
|
+
@rules = []
|
21
|
+
@depends = []
|
22
|
+
super()
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_call(exp)
|
26
|
+
recv, op, args = exp
|
27
|
+
if recv.nil? and args == s(:arglist) and @collect
|
28
|
+
do_table(exp)
|
29
|
+
elsif @ops[op] and @context[1] == :block and @context.length == 4
|
30
|
+
# NB: context.length is 4 when see a method call at the top-level of a
|
31
|
+
# :defn block -- this is where we expect Bloom statements to appear
|
32
|
+
do_rule(exp)
|
33
|
+
else
|
34
|
+
if recv and recv.class == Sexp
|
35
|
+
# ignore accessors of iterator variables
|
36
|
+
unless recv.first == :lvar
|
37
|
+
@nm = true if op == :-@
|
38
|
+
@nm = true unless (@monotonic_whitelist[op] or @bud_instance.tables.has_key? op)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
if @temp_ops[op]
|
42
|
+
@temp_op = op.to_s.gsub("@", "")
|
43
|
+
end
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def collect_rhs(exp)
|
49
|
+
@collect = true
|
50
|
+
rhs = process exp
|
51
|
+
@collect = false
|
52
|
+
return rhs
|
53
|
+
end
|
54
|
+
|
55
|
+
def record_rule(lhs, op, rhs)
|
56
|
+
rule_txt = "#{lhs} #{op} (#{rhs})"
|
57
|
+
if op == :<
|
58
|
+
op = "<#{@temp_op}"
|
59
|
+
else
|
60
|
+
op = op.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
@rules << [@rule_indx, lhs, op, rule_txt]
|
64
|
+
@tables.each_pair do |t, non_monotonic|
|
65
|
+
@depends << [@rule_indx, lhs, op, t, non_monotonic]
|
66
|
+
end
|
67
|
+
|
68
|
+
@tables = {}
|
69
|
+
@nm = false
|
70
|
+
@temp_op = nil
|
71
|
+
@rule_indx += 1
|
72
|
+
end
|
73
|
+
|
74
|
+
def do_table(exp)
|
75
|
+
t = exp[1].to_s
|
76
|
+
# If we're called on a "table-like" part of the AST that doesn't correspond
|
77
|
+
# to an extant table, ignore it.
|
78
|
+
@tables[t] = @nm if @bud_instance.tables.has_key? t.to_sym
|
79
|
+
drain(exp)
|
80
|
+
return t
|
81
|
+
end
|
82
|
+
|
83
|
+
def do_rule(exp)
|
84
|
+
lhs = process exp[0]
|
85
|
+
op = exp[1]
|
86
|
+
rhs = collect_rhs(map2pro(exp[2]))
|
87
|
+
record_rule(lhs, op, rhs)
|
88
|
+
drain(exp)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Look for top-level map on a base-table on rhs, and rewrite to pro
|
92
|
+
def map2pro(exp)
|
93
|
+
if exp[1] and exp[1][0] and exp[1][0] == :iter \
|
94
|
+
and exp[1][1] and exp[1][1][1] == :call \
|
95
|
+
and exp[1][1][2] == :map
|
96
|
+
exp[1][1][2] = :pro
|
97
|
+
end
|
98
|
+
exp
|
99
|
+
end
|
100
|
+
|
101
|
+
def drain(exp)
|
102
|
+
exp.shift until exp.empty?
|
103
|
+
return ""
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Given a table of renames from x => y, replace all calls to "x" with calls to
|
108
|
+
# "y" instead. We don't try to handle shadowing due to block variables: if a
|
109
|
+
# block references a block variable that shadows an identifier in the rename
|
110
|
+
# tbl, it should appear as an :lvar node rather than a :call, so we should be
|
111
|
+
# okay.
|
112
|
+
class CallRewriter < SexpProcessor # :nodoc: all
|
113
|
+
def initialize(rename_tbl)
|
114
|
+
super()
|
115
|
+
self.require_empty = false
|
116
|
+
self.expected = Sexp
|
117
|
+
@rename_tbl = rename_tbl
|
118
|
+
end
|
119
|
+
|
120
|
+
def process_call(exp)
|
121
|
+
tag, recv, meth_name, args = exp
|
122
|
+
|
123
|
+
if @rename_tbl.has_key? meth_name
|
124
|
+
meth_name = @rename_tbl[meth_name] # No need to deep-copy Symbol
|
125
|
+
end
|
126
|
+
|
127
|
+
recv = process(recv)
|
128
|
+
args = process(args)
|
129
|
+
|
130
|
+
s(tag, recv, meth_name, args)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Rewrite qualified references to collections defined by an imported module. In
|
135
|
+
# the AST, this looks like a tree of :call nodes. For example, a.b.c looks like:
|
136
|
+
#
|
137
|
+
# (:call, (:call, (:call, nil, :a, args), :b, args), :c, args)
|
138
|
+
#
|
139
|
+
# If the import table contains [a][b], we want to rewrite this into a single
|
140
|
+
# call to a__b__c, which matches how the corresponding Bloom collection will
|
141
|
+
# be name-mangled. Note that we don't currently check that a__b__c (or a.b.c)
|
142
|
+
# corresponds to an extant Bloom collection.
|
143
|
+
class NestedRefRewriter < SexpProcessor # :nodoc: all
|
144
|
+
attr_accessor :did_work
|
145
|
+
|
146
|
+
def initialize(import_tbl)
|
147
|
+
super()
|
148
|
+
self.require_empty = false
|
149
|
+
self.expected = Sexp
|
150
|
+
@import_tbl = import_tbl
|
151
|
+
@did_work = false
|
152
|
+
end
|
153
|
+
|
154
|
+
def process_call(exp)
|
155
|
+
return exp if @import_tbl.empty?
|
156
|
+
tag, recv, meth_name, args = exp
|
157
|
+
|
158
|
+
catch :skip do
|
159
|
+
recv_stack = make_recv_stack(recv)
|
160
|
+
throw :skip unless recv_stack.length > 0
|
161
|
+
|
162
|
+
lookup_tbl = @import_tbl
|
163
|
+
new_meth_name = ""
|
164
|
+
until recv_stack.empty?
|
165
|
+
m = recv_stack.pop
|
166
|
+
throw :skip unless lookup_tbl.has_key? m
|
167
|
+
|
168
|
+
new_meth_name += "#{m}__"
|
169
|
+
lookup_tbl = lookup_tbl[m]
|
170
|
+
end
|
171
|
+
|
172
|
+
# Okay, apply the rewrite
|
173
|
+
@did_work = true
|
174
|
+
new_meth_name += meth_name.to_s
|
175
|
+
recv = nil
|
176
|
+
meth_name = new_meth_name.to_sym
|
177
|
+
end
|
178
|
+
|
179
|
+
recv = process(recv)
|
180
|
+
args = process(args)
|
181
|
+
|
182
|
+
s(tag, recv, meth_name, args)
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
def make_recv_stack(r)
|
187
|
+
rv = []
|
188
|
+
|
189
|
+
while true
|
190
|
+
break if r.nil?
|
191
|
+
# We can exit early if we see something unexpected
|
192
|
+
throw :skip unless r.sexp_type == :call
|
193
|
+
|
194
|
+
recv, meth_name, args = r.sexp_body
|
195
|
+
unless args.sexp_type == :arglist and args.sexp_body.length == 0
|
196
|
+
throw :skip
|
197
|
+
end
|
198
|
+
|
199
|
+
rv << meth_name
|
200
|
+
r = recv
|
201
|
+
end
|
202
|
+
|
203
|
+
return rv
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Look for temp declarations and remove the "temp" keyword, yielding code that
|
208
|
+
# we can safely eval. We also record the set of "temp" collections we've seen,
|
209
|
+
# and provide a helper method that returns the AST of a state block that
|
210
|
+
# contains declarations for all those temp tables.
|
211
|
+
class TempExpander < SexpProcessor # :nodoc: all
|
212
|
+
attr_reader :tmp_tables
|
213
|
+
attr_accessor :did_work
|
214
|
+
|
215
|
+
def initialize
|
216
|
+
super()
|
217
|
+
self.require_empty = false
|
218
|
+
self.expected = Sexp
|
219
|
+
|
220
|
+
@tmp_tables = []
|
221
|
+
@did_work = false
|
222
|
+
end
|
223
|
+
|
224
|
+
def process_defn(exp)
|
225
|
+
tag, name, args, scope = exp
|
226
|
+
|
227
|
+
if name.to_s =~ /^__bloom__.+/
|
228
|
+
block = scope[1]
|
229
|
+
|
230
|
+
block.each_with_index do |n,i|
|
231
|
+
if i == 0
|
232
|
+
raise Bud::CompileError if n != :block
|
233
|
+
next
|
234
|
+
end
|
235
|
+
|
236
|
+
# temp declarations are misparsed if the RHS contains certain constructs
|
237
|
+
# (e.g., old-style join syntax, group, "do |f| ... end" rather than
|
238
|
+
# "{|f| ... }"). Rewrite to correct the misparsing.
|
239
|
+
if n.sexp_type == :iter
|
240
|
+
iter_body = n.sexp_body
|
241
|
+
|
242
|
+
if iter_body.first.sexp_type == :call
|
243
|
+
call_node = iter_body.first
|
244
|
+
|
245
|
+
_, recv, meth, meth_args = call_node
|
246
|
+
if meth == :temp and recv.nil?
|
247
|
+
_, lhs, op, rhs = meth_args.sexp_body.first
|
248
|
+
|
249
|
+
old_rhs_body = rhs.sexp_body
|
250
|
+
rhs[1] = s(:iter)
|
251
|
+
rhs[1] += old_rhs_body
|
252
|
+
rhs[1] += iter_body[1..-1]
|
253
|
+
block[i] = n = call_node
|
254
|
+
@did_work = true
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
_, recv, meth, meth_args = n
|
260
|
+
if meth == :temp and recv.nil?
|
261
|
+
block[i] = rewrite_temp(n)
|
262
|
+
@did_work = true
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
s(tag, name, args, scope)
|
268
|
+
end
|
269
|
+
|
270
|
+
def get_state_meth(klass)
|
271
|
+
return if @tmp_tables.empty?
|
272
|
+
block = s(:block)
|
273
|
+
|
274
|
+
@tmp_tables.each do |t|
|
275
|
+
args = s(:arglist, s(:lit, t.to_sym))
|
276
|
+
block << s(:call, nil, :temp, args)
|
277
|
+
end
|
278
|
+
|
279
|
+
meth_name = Module.make_state_meth_name(klass).to_s + "__tmp"
|
280
|
+
return s(:defn, meth_name.to_sym, s(:args), s(:scope, block))
|
281
|
+
end
|
282
|
+
|
283
|
+
private
|
284
|
+
def rewrite_temp(exp)
|
285
|
+
_, recv, meth, args = exp
|
286
|
+
|
287
|
+
raise Bud::CompileError unless recv == nil
|
288
|
+
nest_call = args.sexp_body.first
|
289
|
+
raise Bud::CompileError unless nest_call.sexp_type == :call
|
290
|
+
|
291
|
+
nest_recv, nest_op, nest_args = nest_call.sexp_body
|
292
|
+
raise Bud::CompileError unless nest_recv.sexp_type == :lit
|
293
|
+
|
294
|
+
tmp_name = nest_recv.sexp_body.first
|
295
|
+
@tmp_tables << tmp_name
|
296
|
+
new_recv = s(:call, nil, tmp_name, s(:arglist))
|
297
|
+
return s(:call, new_recv, nest_op, nest_args)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class DefnRenamer < SexpProcessor # :nodoc: all
|
302
|
+
def initialize(old_mod_name, new_mod_name, local_name)
|
303
|
+
super()
|
304
|
+
self.require_empty = false
|
305
|
+
self.expected = Sexp
|
306
|
+
@old_mod_name = old_mod_name
|
307
|
+
@new_mod_name = new_mod_name
|
308
|
+
@local_name = local_name
|
309
|
+
end
|
310
|
+
|
311
|
+
def process_defn(exp)
|
312
|
+
tag, name, args, scope = exp
|
313
|
+
name_s = name.to_s
|
314
|
+
|
315
|
+
if name_s =~ /^__bootstrap__.+$/
|
316
|
+
name = name_s.sub(/^(__bootstrap__)(.+)$/, "\\1#{@local_name}__\\2").to_sym
|
317
|
+
elsif name_s =~ /^__state\d+__/
|
318
|
+
name = name_s.sub(/^(__state\d+__)(.*)$/, "\\1#{@local_name}__\\2").to_sym
|
319
|
+
elsif name_s =~ /^__bloom__.+$/
|
320
|
+
name = name_s.sub(/^(__bloom__)(.+)$/, "\\1#{@local_name}__\\2").to_sym
|
321
|
+
else
|
322
|
+
name = "#{@local_name}__#{name_s}".to_sym
|
323
|
+
end
|
324
|
+
|
325
|
+
# Note that we don't bother to recurse further into the AST: we're only
|
326
|
+
# interested in top-level :defn nodes.
|
327
|
+
s(tag, name, args, scope)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
module ModuleRewriter # :nodoc: all
|
332
|
+
# Do the heavy-lifting to import the Bloom module "mod" into the class/module
|
333
|
+
# "import_site", bound to "local_name" at the import site. We implement this
|
334
|
+
# by converting the imported module into an AST and rewriting the AST like so:
|
335
|
+
#
|
336
|
+
# (a) the module name is mangled to include the local bind name and the
|
337
|
+
# importer
|
338
|
+
# (b) instance method names are mangled to include the local bind name
|
339
|
+
# (c) state defined by the module is mangled to include the local bind name
|
340
|
+
# (d) statements in the module are rewritten to reference the mangled names
|
341
|
+
# (e) statements in the module that reference sub-modules are rewritten to
|
342
|
+
# reference the mangled name of the submodule.
|
343
|
+
#
|
344
|
+
# We then convert the rewritten AST back into Ruby source code using Ruby2Ruby
|
345
|
+
# and eval() it to define a new module. We return the name of that newly
|
346
|
+
# defined module; the caller can then use "include" to load the module into
|
347
|
+
# the import site. Note that additional rewrites are needed to ensure that
|
348
|
+
# code in the import site that accesses module contents does the right thing;
|
349
|
+
# see Bud#rewrite_local_methods.
|
350
|
+
def self.do_import(import_site, mod, local_name)
|
351
|
+
ast = get_module_ast(mod)
|
352
|
+
ast = ast_flatten_nested_refs(ast, mod.bud_import_table)
|
353
|
+
ast = ast_process_temps(ast, mod)
|
354
|
+
ast, new_mod_name = ast_rename_module(ast, import_site, mod, local_name)
|
355
|
+
rename_tbl = ast_rename_state(ast, local_name)
|
356
|
+
ast = ast_update_refs(ast, rename_tbl)
|
357
|
+
|
358
|
+
str = Ruby2Ruby.new.process(ast)
|
359
|
+
rv = import_site.module_eval str
|
360
|
+
raise Bud::BudError unless rv.nil?
|
361
|
+
|
362
|
+
return new_mod_name
|
363
|
+
end
|
364
|
+
|
365
|
+
def self.get_module_ast(mod)
|
366
|
+
raw_ast = get_raw_parse_tree(mod)
|
367
|
+
unless raw_ast.first == :module
|
368
|
+
raise Bud::BudError, "import must be used with a Module"
|
369
|
+
end
|
370
|
+
|
371
|
+
return Unifier.new.process(raw_ast)
|
372
|
+
end
|
373
|
+
|
374
|
+
# Returns the AST for the given module (as a tree of Sexps). ParseTree
|
375
|
+
# provides native support for doing this, but we choose to do it ourselves. In
|
376
|
+
# ParseTree <= 3.0.7, the support is buggy; in later versions of ParseTree,
|
377
|
+
# the AST is returned in a different format than we expect. In particular, we
|
378
|
+
# expect that the methods from any modules included in the target module will
|
379
|
+
# be "inlined" into the dumped AST; ParseTree > 3.0.7 adds an "include"
|
380
|
+
# statement to the AST instead. In the long run we should adapt the module
|
381
|
+
# rewrite system to work with ParseTree > 3.0.7 and get rid of this code, but
|
382
|
+
# that will require further changes.
|
383
|
+
def self.get_raw_parse_tree(klass)
|
384
|
+
pt = RawParseTree.new(false)
|
385
|
+
klassname = klass.name
|
386
|
+
klassname = klassname.to_sym
|
387
|
+
|
388
|
+
code = if Class === klass then
|
389
|
+
sc = klass.superclass
|
390
|
+
sc_name = ((sc.nil? or sc.name.empty?) ? "nil" : sc.name).intern
|
391
|
+
[:class, klassname, [:const, sc_name]]
|
392
|
+
else
|
393
|
+
[:module, klassname]
|
394
|
+
end
|
395
|
+
|
396
|
+
method_names = klass.private_instance_methods false
|
397
|
+
# protected methods are included in instance_methods, go figure!
|
398
|
+
|
399
|
+
# Get the set of classes/modules that define instance methods we want to
|
400
|
+
# include in the result
|
401
|
+
relatives = klass.modules + [klass]
|
402
|
+
relatives.each do |r|
|
403
|
+
method_names += r.instance_methods false
|
404
|
+
end
|
405
|
+
|
406
|
+
# For each distinct method name, use the implementation that appears the
|
407
|
+
# furthest down in the inheritance hierarchy.
|
408
|
+
relatives.reverse!
|
409
|
+
method_names.uniq.sort.each do |m|
|
410
|
+
relatives.each do |r|
|
411
|
+
t = pt.parse_tree_for_method(r, m.to_sym)
|
412
|
+
if t != [nil]
|
413
|
+
code << t
|
414
|
+
break
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
klass.singleton_methods(false).sort.each do |m|
|
420
|
+
code << pt.parse_tree_for_method(klass, m.to_sym, true)
|
421
|
+
end
|
422
|
+
|
423
|
+
return code
|
424
|
+
end
|
425
|
+
|
426
|
+
# If this module imports a submodule and binds it to :x, references to x.t1
|
427
|
+
# need to be flattened to the mangled name of x.t1.
|
428
|
+
def self.ast_flatten_nested_refs(ast, import_tbl)
|
429
|
+
NestedRefRewriter.new(import_tbl).process(ast)
|
430
|
+
end
|
431
|
+
|
432
|
+
# Handle temp collections defined in the module's Bloom blocks.
|
433
|
+
def self.ast_process_temps(ast, mod)
|
434
|
+
t = TempExpander.new
|
435
|
+
ast = t.process(ast)
|
436
|
+
|
437
|
+
new_meth = t.get_state_meth(mod)
|
438
|
+
if new_meth
|
439
|
+
# Insert the new extra state method into the module's AST
|
440
|
+
ast << new_meth
|
441
|
+
end
|
442
|
+
|
443
|
+
return ast
|
444
|
+
end
|
445
|
+
|
446
|
+
# Rename the given module's name to be a mangle of import site, imported
|
447
|
+
# module, and local bind name. We also need to rename special "state" and
|
448
|
+
# "bootstrap" methods. We also rename "bloom" methods, but we can just mangle
|
449
|
+
# with the local bind name for those.
|
450
|
+
def self.ast_rename_module(ast, importer, importee, local_name)
|
451
|
+
mod_name = ast.sexp_body.first
|
452
|
+
raise Bud::BudError if mod_name.to_s != importee.to_s
|
453
|
+
|
454
|
+
# If the importer or importee modules are nested inside an outer module,
|
455
|
+
# strip off the outer module name before using for name mangling purposes
|
456
|
+
importer_name = Module.get_class_name(importer)
|
457
|
+
importee_name = Module.get_class_name(importee)
|
458
|
+
new_name = "#{importer_name}__#{importee_name}__#{local_name}"
|
459
|
+
ast[1] = new_name.to_sym
|
460
|
+
|
461
|
+
dr = DefnRenamer.new(mod_name, new_name, local_name)
|
462
|
+
new_ast = dr.process(ast)
|
463
|
+
|
464
|
+
# XXX: it would be nice to return a Module, rather than a string containing
|
465
|
+
# the Module's name. Unfortunately, I can't see how to do that.
|
466
|
+
return [new_ast, new_name]
|
467
|
+
end
|
468
|
+
|
469
|
+
# Mangle the names of all the collections defined in state blocks found in the
|
470
|
+
# given module's AST. Returns a table mapping old => new names.
|
471
|
+
def self.ast_rename_state(ast, local_name)
|
472
|
+
# Find all the state blocks in the AST
|
473
|
+
raise Bud::BudError unless ast.sexp_type == :module
|
474
|
+
|
475
|
+
rename_tbl = {}
|
476
|
+
ast.sexp_body.each do |b|
|
477
|
+
next unless b.class <= Sexp
|
478
|
+
next if b.sexp_type != :defn
|
479
|
+
|
480
|
+
def_name, args, scope = b.sexp_body
|
481
|
+
next unless /^__state\d+__/.match def_name.to_s
|
482
|
+
|
483
|
+
raise Bud::BudError unless scope.sexp_type == :scope
|
484
|
+
state_block = scope.sexp_body.first
|
485
|
+
raise Bud::BudError unless state_block.sexp_type == :block
|
486
|
+
next unless state_block.sexp_body
|
487
|
+
|
488
|
+
# Look for collection definition statements inside the block
|
489
|
+
state_block.sexp_body.each do |e|
|
490
|
+
raise Bud::BudError unless e.sexp_type == :call
|
491
|
+
|
492
|
+
recv, meth_name, args = e.sexp_body
|
493
|
+
raise Bud::BudError unless args.sexp_type == :arglist
|
494
|
+
|
495
|
+
if meth_name == :interface
|
496
|
+
tbl_name_node = args.sexp_body[1]
|
497
|
+
else
|
498
|
+
tbl_name_node = args.sexp_body[0]
|
499
|
+
end
|
500
|
+
|
501
|
+
raise Bud::BudError unless tbl_name_node.sexp_type == :lit
|
502
|
+
tbl_name = tbl_name_node.sexp_body.first
|
503
|
+
|
504
|
+
new_tbl_name = "#{local_name}__#{tbl_name}".to_sym
|
505
|
+
rename_tbl[tbl_name] = new_tbl_name
|
506
|
+
|
507
|
+
tbl_name_node[1] = new_tbl_name
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
return rename_tbl
|
512
|
+
end
|
513
|
+
|
514
|
+
def self.ast_update_refs(ast, rename_tbl)
|
515
|
+
CallRewriter.new(rename_tbl).process(ast)
|
516
|
+
end
|
517
|
+
|
518
|
+
# Return a list of symbols containing the names of def blocks containing Bloom
|
519
|
+
# rules in the given module and all of its ancestors.
|
520
|
+
def self.get_rule_defs(mod)
|
521
|
+
mod.instance_methods.select {|m| m =~ /^__bloom__.+$/}
|
522
|
+
end
|
523
|
+
end
|
data/lib/bud/rtrace.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bud/state'
|
3
|
+
|
4
|
+
class RTrace #:nodoc: all
|
5
|
+
attr_reader :table_recv, :table_send, :table_sleep
|
6
|
+
|
7
|
+
def initialize(bud_instance)
|
8
|
+
@bud_instance = bud_instance
|
9
|
+
return if bud_instance.class == Stratification or
|
10
|
+
@bud_instance.class == DepAnalysis
|
11
|
+
@table_recv = Bud::BudTable.new(:t_recv_time, @bud_instance, [:pred, :tuple, :time])
|
12
|
+
@table_send = Bud::BudTable.new(:t_send_time, @bud_instance, [:pred, :tuple, :time])
|
13
|
+
@table_sleep = Bud::BudTable.new(:t_sleep_time, @bud_instance, [:time])
|
14
|
+
end
|
15
|
+
|
16
|
+
def send(pred, datum)
|
17
|
+
@table_send << [pred.to_s, datum, Time.now.to_f]
|
18
|
+
end
|
19
|
+
|
20
|
+
def recv(datum)
|
21
|
+
@table_recv << [datum[0].to_s, datum[1], Time.now.to_f]
|
22
|
+
end
|
23
|
+
|
24
|
+
def sleep
|
25
|
+
@table_sleep << [Time.now.to_f]
|
26
|
+
end
|
27
|
+
end
|
data/lib/bud/server.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Bud
|
4
|
+
class BudServer < EM::Connection #:nodoc: all
|
5
|
+
def initialize(bud)
|
6
|
+
@bud = bud
|
7
|
+
@pac = MessagePack::Unpacker.new
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def receive_data(data)
|
12
|
+
# Feed the received data to the deserializer
|
13
|
+
@pac.feed data
|
14
|
+
|
15
|
+
# streaming deserialize
|
16
|
+
@pac.each do |obj|
|
17
|
+
message_received(obj)
|
18
|
+
end
|
19
|
+
|
20
|
+
@bud.rtracer.sleep if @bud.options[:rtrace]
|
21
|
+
end
|
22
|
+
|
23
|
+
def message_received(obj)
|
24
|
+
# puts "#{@bud.ip_port} <= #{obj.inspect}"
|
25
|
+
unless (obj.class <= Array and obj.length == 2 and not
|
26
|
+
@bud.tables[obj[0].to_sym].nil? and obj[1].class <= Array)
|
27
|
+
raise BudError, "Bad inbound message of class #{obj.class}: #{obj.inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
@bud.rtracer.recv(obj) if @bud.options[:rtrace]
|
31
|
+
|
32
|
+
@bud.inbound << obj
|
33
|
+
begin
|
34
|
+
@bud.tick unless @bud.lazy
|
35
|
+
rescue Exception
|
36
|
+
# If we raise an exception here, EM dies, which causes problems (e.g.,
|
37
|
+
# other Bud instances in the same process will crash). Ignoring the
|
38
|
+
# error isn't best though -- we should do better (#74).
|
39
|
+
puts "Exception handling network message (channel '#{obj[0]}'): #{$!}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/bud/state.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
module Bud
|
2
|
+
######## methods for registering collection types
|
3
|
+
private
|
4
|
+
def define_collection(name, &block)
|
5
|
+
# Don't allow duplicate collection definitions
|
6
|
+
if @tables.has_key? name
|
7
|
+
raise Bud::CompileError, "collection already exists: #{name}"
|
8
|
+
end
|
9
|
+
|
10
|
+
# Rule out collection names that use reserved words, including
|
11
|
+
# previously-defined method names.
|
12
|
+
reserved = eval "defined?(#{name})"
|
13
|
+
unless reserved.nil?
|
14
|
+
raise Bud::CompileError, "symbol :#{name} reserved, cannot be used as table name"
|
15
|
+
end
|
16
|
+
self.singleton_class.send(:define_method, name) do |*args, &blk|
|
17
|
+
unless blk.nil? then
|
18
|
+
return @tables[name].pro(&blk)
|
19
|
+
else
|
20
|
+
return @tables[name]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
public
|
26
|
+
|
27
|
+
def input # :nodoc: all
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def output # :nodoc: all
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
# declare a scratch collection to be an input or output interface
|
36
|
+
def interface(mode, name, schema=nil)
|
37
|
+
t_provides << [name.to_s, mode]
|
38
|
+
scratch(name, schema)
|
39
|
+
end
|
40
|
+
|
41
|
+
# declare a persistent collection. default schema <tt>[:key] => [:val]</tt>
|
42
|
+
def table(name, schema=nil)
|
43
|
+
define_collection(name)
|
44
|
+
@tables[name] = Bud::BudTable.new(name, self, schema)
|
45
|
+
end
|
46
|
+
|
47
|
+
# declare a transient (1-timestep) collection. default schema <tt>[:key] => [:val]</tt>
|
48
|
+
def scratch(name, schema=nil)
|
49
|
+
define_collection(name)
|
50
|
+
@tables[name] = Bud::BudScratch.new(name, self, schema)
|
51
|
+
end
|
52
|
+
|
53
|
+
# declare a scratch in a bloom statement lhs. schema inferred from rhs.
|
54
|
+
def temp(name)
|
55
|
+
define_collection(name)
|
56
|
+
# defer schema definition until merge
|
57
|
+
@tables[name] = Bud::BudTemp.new(name, self, nil, true)
|
58
|
+
end
|
59
|
+
|
60
|
+
# declare a transient network collection. default schema <tt>[:address, :val] => []</tt>
|
61
|
+
def channel(name, schema=nil)
|
62
|
+
define_collection(name)
|
63
|
+
@tables[name] = Bud::BudChannel.new(name, self, schema)
|
64
|
+
@channels[name] = @tables[name].locspec_idx
|
65
|
+
end
|
66
|
+
|
67
|
+
# declare a collection to be read from +filename+. rhs of statements only
|
68
|
+
def file_reader(name, filename, delimiter='\n')
|
69
|
+
define_collection(name)
|
70
|
+
@tables[name] = Bud::BudFileReader.new(name, filename, delimiter, self)
|
71
|
+
end
|
72
|
+
|
73
|
+
# declare a collection to be auto-populated every +period+ seconds. schema <tt>[:key] => [:val]</tt>.
|
74
|
+
# rhs of statements only.
|
75
|
+
def periodic(name, period=1)
|
76
|
+
define_collection(name)
|
77
|
+
# stick with default schema -- [:key] => [:val]
|
78
|
+
@tables[name] = Bud::BudPeriodic.new(name, self)
|
79
|
+
raise BudError if @periodics.has_key? [name]
|
80
|
+
t = [name, gen_id, period]
|
81
|
+
@periodics << t
|
82
|
+
end
|
83
|
+
|
84
|
+
def terminal(name) # :nodoc: all
|
85
|
+
if defined?(@terminal) && @terminal != name
|
86
|
+
raise Bud::BudError, "can't register IO collection #{name} in addition to #{@terminal}"
|
87
|
+
else
|
88
|
+
@terminal = name
|
89
|
+
end
|
90
|
+
define_collection(name)
|
91
|
+
@channels[name] = nil
|
92
|
+
@tables[name] = Bud::BudTerminal.new(name, [:line], self)
|
93
|
+
end
|
94
|
+
|
95
|
+
# declare a TokyoCabinet table
|
96
|
+
def tctable(name, schema=nil)
|
97
|
+
define_collection(name)
|
98
|
+
@tables[name] = Bud::BudTcTable.new(name, self, schema)
|
99
|
+
@tc_tables[name] = @tables[name]
|
100
|
+
end
|
101
|
+
|
102
|
+
# declare an Apache ZooKeeper table
|
103
|
+
def zktable(name, path, addr="localhost:2181")
|
104
|
+
define_collection(name)
|
105
|
+
@tables[name] = Bud::BudZkTable.new(name, path, addr, self)
|
106
|
+
@zk_tables[name] = @tables[name]
|
107
|
+
end
|
108
|
+
end
|