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/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
- :== => 1, :+ => 1, :<= => 1, :- => 1, :< => 1, :> => 1, :~ => 1,
11
- :* => 1, :pairs => 1, :matches => 1, :combos => 1, :flatten => 1,
12
- :lefts => 1, :rights => 1, :map => 1, :flat_map => 1, :pro => 1,
13
- :schema => 1, :cols => 1, :key_cols => 1, :val_cols => 1,
14
- :payloads => 1, :tabname => 1, :+@ => 1
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
- def call_is_attr_deref?(recv, op)
27
- if recv.first == :call and @bud_instance.tables.has_key? recv[2]
28
- cols = @bud_instance.tables[recv[2]].cols
29
- return true if cols and cols.include? op
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
- return false
52
+ ty
32
53
  end
33
54
 
34
55
  def process_call(exp)
35
56
  recv, op, args = exp
36
- if recv.nil? and args == s(:arglist) and @collect
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
- if op == :notin
44
- # a <= b.m1.m2.notin(c, ... ) ..
45
- # b contributes positively to a, but c contributes negatively.
46
- notintab = args[1][2].to_s # args == (:arglist (:call, nil, :c, ...))
47
- @tables[notintab] = true
48
- elsif recv and recv.class == Sexp
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 @bud_instance.tables.has_key? op or call_is_attr_deref?(recv, op) or recv.first == :lvar
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
- pro_rules = map2pro(exp[2])
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(pro_rules)
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
- pro_rules2 = Marshal.load(Marshal.dump(pro_rules))
114
- rhs = collect_rhs(pro_rules)
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(pro_rules2))
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
- end
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
- next unless exp[2][1] and exp[2][1][0] == :array
173
- @collnames.each_with_index do |c, i|
174
- next unless exp[2][1][i+1] and exp[2][1][i+1][0] == :lasgn
175
- @iterhash[exp[2][1][i+1][1]] = c
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})"