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/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})"