bud 0.9.4 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,9 @@ require 'rubygems'
4
4
  require 'bud'
5
5
  require 'abbrev'
6
6
  require 'tempfile'
7
- TABLE_TYPES = ["table", "scratch", "channel", "loopback", "periodic", "sync", "store"]
7
+
8
+ TABLE_TYPES = ["table", "scratch", "channel", "loopback", "periodic",
9
+ "sync", "store", "interface", "interfaces"]
8
10
 
9
11
  # The class to which rebl adds user-specified rules and declarations.
10
12
  class ReblBase
@@ -93,10 +95,13 @@ class ReblShell
93
95
 
94
96
  # One step of the rebl shell loop: processes one rebl shell line from stdin
95
97
  # and returns. May raise an Exception.
96
- def self.rebl_loop(lib,noreadline=false)
98
+ def self.rebl_loop(lib, noreadline=false)
97
99
  begin
98
- line = Readline::readline('rebl> ') unless noreadline
99
- line = gets if noreadline
100
+ if noreadline
101
+ line = gets
102
+ else
103
+ line = Readline::readline('rebl> ')
104
+ end
100
105
  do_exit if line.nil?
101
106
  line.strip!
102
107
  return if line.empty?
@@ -110,7 +115,7 @@ class ReblShell
110
115
  else
111
116
  puts "invalid command or ambiguous command prefix"
112
117
  end
113
- elsif TABLE_TYPES.include? split_line[0]
118
+ elsif is_collection? split_line[0]
114
119
  # Collection
115
120
  lib.add_collection(line)
116
121
  else
@@ -186,6 +191,12 @@ class ReblShell
186
191
  puts "\n" + @@exit_message
187
192
  exit!
188
193
  end
194
+
195
+ # Checks if a given string refers to a collection type (one of the builtin
196
+ # collection types or a wrapper_name for a lattice).
197
+ def self.is_collection?(c)
198
+ TABLE_TYPES.include?(c) || Bud::Lattice.lattice_kinds.has_key?(c.to_sym)
199
+ end
189
200
  end
190
201
 
191
202
 
@@ -196,8 +207,8 @@ class LibRebl
196
207
  attr_accessor :rules, :state
197
208
  attr_reader :ip, :port, :rebl_class_inst
198
209
  @@builtin_tables = [:stdio, :periodics_tbl, :halt, :localtick,
199
- :t_depends, :t_cycle, :t_provides, :t_rules,
200
- :t_stratum, :t_underspecified,
210
+ :t_depends, :t_cycle, :t_provides, :t_rule_stratum,
211
+ :t_rules, :t_stratum, :t_underspecified,
201
212
  :t_table_info, :t_table_schema, :rebl_breakpoint]
202
213
  @@classid = 0
203
214
 
@@ -233,11 +244,14 @@ class LibRebl
233
244
  def dump(c)
234
245
  if c.nil?
235
246
  puts "Error: dump must be passed a collection name"
236
- elsif not @rebl_class_inst.tables.has_key? c.to_sym
237
- puts "Error: non-existent collection \"#{c}\""
238
- else
247
+ elsif @rebl_class_inst.tables.has_key? c.to_sym
239
248
  tups = @rebl_class_inst.tables[c.to_sym].to_a.sort
240
249
  puts(tups.empty? ? "(empty)" : tups.sort.map{|t| "#{t}"}.join("\n"))
250
+ elsif @rebl_class_inst.lattices.has_key? c.to_sym
251
+ val = @rebl_class_inst.lattices[c.to_sym].current_value
252
+ puts val.inspect
253
+ else
254
+ puts "Error: non-existent collection \"#{c}\""
241
255
  end
242
256
  end
243
257
 
@@ -334,11 +348,15 @@ class LibRebl
334
348
  end)
335
349
  @rebl_class_inst.dbm_tables.merge! @old_inst.dbm_tables
336
350
  @rebl_class_inst.zk_tables.merge! @old_inst.zk_tables
351
+ @rebl_class_inst.lattices.merge! @old_inst.lattices
337
352
 
338
353
  # Fix the bud instance pointers from copied tables.
339
354
  @rebl_class_inst.tables.each_value do |v|
340
355
  v.bud_instance = @rebl_class_inst
341
356
  end
357
+ @rebl_class_inst.lattices.each_value do |v|
358
+ v.bud_instance = @rebl_class_inst
359
+ end
342
360
  end
343
361
 
344
362
  # Run lazily in background, shutting down old instance.
@@ -1,27 +1,26 @@
1
1
  require 'rubygems'
2
- require 'ruby2ruby'
3
- require 'set'
4
2
 
5
3
  class RuleRewriter < Ruby2Ruby # :nodoc: all
6
- attr_accessor :rule_indx, :rules, :depends
4
+ attr_accessor :rule_idx, :rules, :depends
7
5
 
8
- OP_LIST = Set.new([:<<, :<, :<=])
9
- TEMP_OP_LIST = Set.new([:-@, :~, :+@])
10
- MONOTONE_WHITELIST = Set.new([:==, :+, :<=, :-, :<, :>, :*, :~,
11
- :pairs, :matches, :combos, :flatten,
12
- :lefts, :rights, :map, :flat_map, :pro,
13
- :cols, :key_cols, :val_cols, :payloads, :lambda,
14
- :tabname, :ip_port, :port, :ip, :int_ip_port])
6
+ OP_LIST = [:<<, :<, :<=].to_set
7
+ TEMP_OP_LIST = [:-@, :~, :+@].to_set
8
+ MONOTONE_WHITELIST = [:==, :+, :<=, :-, :<, :>, :*, :~, :+@,
9
+ :pairs, :matches, :combos, :flatten, :new,
10
+ :lefts, :rights, :map, :flat_map, :pro, :merge,
11
+ :schema, :cols, :key_cols, :val_cols, :payloads, :lambda,
12
+ :tabname, :current_value].to_set
15
13
 
16
- def initialize(seed, bud_instance)
14
+ def initialize(bud_instance, rule_idx)
17
15
  @bud_instance = bud_instance
18
16
  @tables = {}
19
17
  @nm = false
20
- @rule_indx = seed
18
+ @rule_idx = rule_idx
21
19
  @collect = false
22
20
  @rules = []
23
21
  @depends = []
24
- @nm_funcs_called = false
22
+ @iter_stack = []
23
+ @refs_in_body = Set.new
25
24
  super()
26
25
  end
27
26
 
@@ -29,6 +28,7 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
29
28
  def resolve(obj, prefix, name)
30
29
  qn = prefix ? prefix + "." + name.to_s : name.to_s
31
30
  return [:collection, qn, obj.tables[name]] if obj.tables.has_key? name
31
+ return [:lattice, qn, obj.lattices[name]] if obj.lattices.has_key? name
32
32
 
33
33
  # does name refer to an import name?
34
34
  iobj = obj.import_instance name
@@ -38,12 +38,13 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
38
38
  end
39
39
 
40
40
  def exp_id_type(recv, name, args) # call only if sexp type is :call
41
- return $not_id unless args.size == 1
41
+ return $not_id unless args.empty?
42
42
  ty = $not_id
43
43
  if recv
44
44
  if recv.first == :call
45
- # possibly nested reference.
46
- rty, rqn, robj = exp_id_type(recv[1], recv[2], recv[3]) # rty, rqn, .. = receiver's type, qual name etc.
45
+ # possibly nested reference
46
+ # rty, rqn, .. = receiver's type, qual name etc.
47
+ rty, rqn, robj = exp_id_type(recv[1], recv[2], recv[3..-1])
47
48
  ty = resolve(robj, rqn, name) if rty == :import
48
49
  end
49
50
  else
@@ -56,27 +57,94 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
56
57
  def call_to_id(exp)
57
58
  # convert a series of nested calls, a sexp of the form
58
59
  # s(:call,
59
- # s(:call, s(:call, nil, :a, s(:arglist)), :b, s(:arglist)),
60
- # :bar ,
61
- # s(:arglist)))
60
+ # s(:call, s(:call, nil, :a), :b),
61
+ # :bar))
62
62
  # to the string "a.b.bar"
63
- raise "Malformed exp: #{exp}" unless (exp[0] == :call)
64
- _, recv, op, args = exp
63
+ raise Bud::CompileError, "malformed expression: #{exp}" unless exp.sexp_type == :call
64
+ _, recv, op = exp
65
65
  return recv.nil? ? op.to_s : call_to_id(recv) + "." + op.to_s
66
66
  end
67
67
 
68
+ # We want to distinguish between collection dependencies that occur in
69
+ # top-level expressions versus collections that are referenced inside rule
70
+ # bodies. We just want to set a flag when processing the :iter body, but
71
+ # annoyingly it seems that is hard to do without duplicating the
72
+ # implementation of process_iter().
73
+ #
74
+ # XXX: the whole RuleRewriter approach is wrong because it conflates
75
+ # converting ASTs to strings with doing analysis on ASTs. Those should be
76
+ # split into two separate passes.
77
+ def process_iter(exp)
78
+ # first field of exp is tag; shift it
79
+ exp.shift
80
+ iter = process exp.shift
81
+ args = exp.shift
82
+
83
+ @iter_stack.push(true)
84
+ body = exp.empty? ? nil : process(exp.shift)
85
+ @iter_stack.pop
86
+
87
+ do_process_iter(iter, args, body)
88
+ end
89
+
90
+ def do_process_iter(iter, args, body)
91
+ args = case args
92
+ when 0 then
93
+ " ||"
94
+ else
95
+ a = process(args)[1..-2]
96
+ a = " |#{a}|" unless a.empty?
97
+ a
98
+ end
99
+
100
+ b, e = if iter == "END" then
101
+ [ "{", "}" ]
102
+ else
103
+ [ "do", "end" ]
104
+ end
105
+
106
+ iter.sub!(/\(\)$/, '')
107
+
108
+ # REFACTOR: ugh
109
+ result = []
110
+ result << "#{iter} {"
111
+ result << args
112
+ if body then
113
+ result << " #{body.strip} "
114
+ else
115
+ result << ' '
116
+ end
117
+ result << "}"
118
+ result = result.join
119
+ return result if result !~ /\n/ and result.size < LINE_LENGTH
120
+
121
+ result = []
122
+ result << "#{iter} #{b}"
123
+ result << args
124
+ result << "\n"
125
+ if body then
126
+ result << indent(body.strip)
127
+ result << "\n"
128
+ end
129
+ result << e
130
+ result.join
131
+ end
132
+
68
133
  def process_call(exp)
69
- recv, op, args = exp
70
- if OP_LIST.include?(op) and @context[1] == :block and @context.length == 4
71
- # NB: context.length is 4 when see a method call at the top-level of a
134
+ tag, recv, op, *args = exp
135
+ if OP_LIST.include?(op) and @context[1] == :defn and @context.length == 2
136
+ # NB: context.length is 2 when see a method call at the top-level of a
72
137
  # :defn block -- this is where we expect Bloom statements to appear
73
138
  do_rule(exp)
74
139
  elsif op == :notin
75
140
  # Special case. In the rule "z <= x.notin(y)", z depends positively on x,
76
141
  # but negatively on y. See further explanation in the "else" section for
77
142
  # why this is a special case.
78
- notintab = call_to_id(args[1]) # args expected to be of the form (:arglist (:call nil :y ...))
79
- @tables[notintab.to_s] = true # "true" denotes non-monotonic dependency
143
+ if args.first.sexp_type != :call
144
+ raise Bud::CompileError, "illegal argument to notin: #{args.first}"
145
+ end
146
+ notintab = call_to_id(args[0]) # args expected to be of the form (:call nil :y ...)
147
+ @tables[notintab] = true # "true" denotes non-monotonic dependency
80
148
  super
81
149
  else
82
150
  # Parse a call of the form a.b.c.foo
@@ -88,15 +156,16 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
88
156
  # a.b.c.notin(d.e.f), we register a non-monotonic dependency of lhs on
89
157
  # "d.e.f", not with "a.b.c"
90
158
  ty, qn, _ = exp_id_type(recv, op, args) # qn = qualified name
91
- if ty == :collection
159
+ if ty == :collection or ty == :lattice
92
160
  (@tables[qn] = @nm if @collect) unless @tables[qn]
161
+ @refs_in_body << qn unless @iter_stack.empty?
93
162
  #elsif ty == :import .. do nothing
94
163
  elsif ty == :not_coll_id
95
164
  # Check if receiver is a collection, and further if the current exp
96
165
  # represents a field lookup
97
166
  op_is_field_name = false
98
167
  if recv and recv.first == :call
99
- rty, _, robj = exp_id_type(recv[1], recv[2], recv[3])
168
+ rty, _, robj = exp_id_type(recv[1], recv[2], recv[3..-1])
100
169
  if rty == :collection
101
170
  cols = robj.cols
102
171
  op_is_field_name = true if cols and cols.include?(op)
@@ -104,18 +173,14 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
104
173
  end
105
174
  # For CALM analysis, mark deletion rules as non-monotonic
106
175
  @nm = true if op == :-@
176
+
177
+ # Don't worry about monotone ops, table names, table.attr calls, or
178
+ # accessors of iterator variables
107
179
  if recv
108
- # Don't worry about monotone ops, table names, table.attr calls, or
109
- # accessors of iterator variables
110
180
  unless RuleRewriter.is_monotone(op) or op_is_field_name or
111
181
  recv.first == :lvar or op.to_s.start_with?("__")
112
182
  @nm = true
113
183
  end
114
- else
115
- # Function called (implicit receiver = Bud instance) in a user-defined
116
- # code block. Check if it is non-monotonic (like budtime, that
117
- # produces a new value every time it is called)
118
- @nm_funcs_called = true unless RuleRewriter.is_monotone(op)
119
184
  end
120
185
  end
121
186
  if TEMP_OP_LIST.include? op
@@ -126,25 +191,37 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
126
191
  end
127
192
 
128
193
  def self.is_monotone(op)
129
- MONOTONE_WHITELIST.include?(op)
194
+ MONOTONE_WHITELIST.include?(op) ||
195
+ is_morphism(op) ||
196
+ Bud::Lattice.global_mfuncs.include?(op)
197
+ end
198
+
199
+ def self.is_morphism(op)
200
+ Bud::Lattice.global_morphs.include?(op)
130
201
  end
131
202
 
132
- # Rewrite top-level rhs array literals to lambdas. During wiring, these are
133
- # turned into coll_expr collections.
203
+ # Rewrite top-level rhs literal expressions into lambdas. During wiring, these
204
+ # are turned into coll_expr collections. For normal relational Bloom, the only
205
+ # literal we expect to see is an array literal, but lattices can be
206
+ # initialized with other kinds of literals (e.g., integers for lmax).
134
207
  def lambda_rewrite(rhs)
135
208
  # the <= case
136
- if rhs[0] == :array
137
- return s(:iter, s(:call, nil, :lambda, s(:arglist)), nil, rhs)
209
+ if is_rhs_literal(rhs[0])
210
+ return s(:iter, s(:call, nil, :lambda), s(:args), rhs)
138
211
  # the superator case
139
212
  elsif rhs[0] == :call \
140
- and rhs[1] and rhs[1][0] and rhs[1][0] == :array \
213
+ and rhs[1] and rhs[1][0] and is_rhs_literal(rhs[1][0]) \
141
214
  and rhs[2] and (rhs[2] == :+@ or rhs[2] == :-@ or rhs[2] == :~@)
142
- return s(rhs[0], s(:iter, s(:call, nil, :lambda, s(:arglist)), nil, rhs[1]), rhs[2], rhs[3])
215
+ return s(rhs[0], s(:iter, s(:call, nil, :lambda), s(:args), rhs[1]), rhs[2], *rhs[3..-1])
143
216
  else
144
217
  return rhs
145
218
  end
146
219
  end
147
220
 
221
+ def is_rhs_literal(e)
222
+ [:array, :hash, :lit].include? e
223
+ end
224
+
148
225
  def collect_rhs(exp)
149
226
  exp = lambda_rewrite(exp)
150
227
 
@@ -155,13 +232,13 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
155
232
  end
156
233
 
157
234
  def reset_instance_vars
235
+ @refs_in_body = Set.new
158
236
  @tables = {}
159
237
  @nm = false
160
- @nm_funcs_called = false
161
238
  @temp_op = nil
162
239
  end
163
240
 
164
- def record_rule(lhs, op, rhs_pos, rhs)
241
+ def record_rule(lhs, op, rhs_pos, rhs, unsafe_funcs_called)
165
242
  rule_txt_orig = "#{lhs} #{op} (#{rhs})"
166
243
  rule_txt = "#{lhs} #{op} (#{rhs_pos})"
167
244
  if op == :<
@@ -170,27 +247,26 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
170
247
  op = op.to_s
171
248
  end
172
249
 
173
- @rules << [@bud_instance, @rule_indx, lhs, op, rule_txt, rule_txt_orig, @nm_funcs_called]
250
+ @rules << [@bud_instance, @rule_idx, lhs, op, rule_txt,
251
+ rule_txt_orig, unsafe_funcs_called]
174
252
  @tables.each_pair do |t, nm|
175
- @depends << [@bud_instance, @rule_indx, lhs, op, t, nm]
253
+ in_rule_body = @refs_in_body.include? t
254
+ @depends << [@bud_instance, @rule_idx, lhs, op, t, nm, in_rule_body]
176
255
  end
177
256
 
178
257
  reset_instance_vars
179
- @rule_indx += 1
258
+ @rule_idx += 1
180
259
  end
181
260
 
182
261
  def do_rule(exp)
183
- lhs = process exp[0]
184
- op = exp[1]
185
- rhs_ast = map2pro(exp[2])
186
-
187
- # Remove the outer s(:arglist) from the rhs AST. An AST subtree rooted with
188
- # s(:arglist) is not really sensible and it causes Ruby2Ruby < 1.3.1 to
189
- # misbehave (for example, s(:arglist, s(:hash, ...)) is misparsed.
190
- raise Bud::CompileError unless rhs_ast.sexp_type == :arglist
191
- rhs_ast = rhs_ast[1]
262
+ tag, lhs, op, rhs_ast = exp
263
+ lhs = process(lhs)
192
264
 
265
+ rhs_ast = MapRewriter.new.process(rhs_ast)
193
266
  rhs_ast = RenameRewriter.new(@bud_instance).process(rhs_ast)
267
+ rhs_ast = LatticeRefRewriter.new(@bud_instance).process(rhs_ast)
268
+ ufr = UnsafeFuncRewriter.new(@bud_instance)
269
+ rhs_ast = ufr.process(rhs_ast)
194
270
 
195
271
  if @bud_instance.options[:no_attr_rewrite]
196
272
  rhs = collect_rhs(rhs_ast)
@@ -203,35 +279,37 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
203
279
  reset_instance_vars
204
280
  rhs_pos = collect_rhs(AttrNameRewriter.new(@bud_instance).process(rhs_ast_dup))
205
281
  end
206
- record_rule(lhs, op, rhs_pos, rhs)
282
+ record_rule(lhs, op, rhs_pos, rhs, ufr.unsafe_func_called)
207
283
  drain(exp)
208
284
  end
209
285
 
210
- # We want to rewrite "map" calls on BudCollections to "pro" calls. It is hard
211
- # to do this precisely (issue #225), so we just replace map calls liberally
212
- # and define Enumerable#pro as an alias for "map".
213
- def map2pro(exp)
214
- # the non-superator case
215
- if exp[1] and exp[1][0] and exp[1][0] == :iter \
216
- and exp[1][1] and exp[1][1][1] and exp[1][1][1][0] == :call
217
- if exp[1][1][2] == :map
218
- exp[1][1][2] = :pro
219
- end
220
- # the superator case
221
- elsif exp[1] and exp[1][0] == :call and (exp[1][2] == :~@ or exp[1][2] == :+@ or exp[1][2] == :-@)
222
- if exp[1][1] and exp[1][1][1] and exp[1][1][1][2] == :map
223
- exp[1][1][1][2] = :pro
224
- end
225
- end
226
- exp
227
- end
228
-
229
286
  def drain(exp)
230
287
  exp.shift until exp.empty?
231
288
  return ""
232
289
  end
233
290
  end
234
291
 
292
+ # We want to rewrite "map" calls on BudCollections to "pro" calls. It is hard
293
+ # to do this precisely (issue #225), so we just replace map calls liberally
294
+ # and define Enumerable#pro as an alias for "map".
295
+ class MapRewriter < SexpProcessor
296
+ def initialize
297
+ super
298
+ self.require_empty = false
299
+ self.expected = Sexp
300
+ end
301
+
302
+ def process_call(exp)
303
+ tag, recv, op, *args = exp
304
+
305
+ if op == :map and args.empty?
306
+ op = :pro
307
+ end
308
+
309
+ s(tag, process(recv), op, *(args.map{|a| process(a)}))
310
+ end
311
+ end
312
+
235
313
  # Look for rename statements and define the necessary scratch collections
236
314
  class RenameRewriter < SexpProcessor
237
315
  def initialize(bud_instance)
@@ -252,14 +330,135 @@ class RenameRewriter < SexpProcessor
252
330
  end
253
331
 
254
332
  def process_call(exp)
255
- call, recv, op, args = exp
333
+ tag, recv, op, *args = exp
256
334
 
257
335
  if op == :rename
258
- arglist, namelit, schemahash = args
336
+ raise Bud::CompileError, "reduce takes two arguments" unless args.size == 2
337
+ namelit, schemahash = args
259
338
  register_scratch(namelit[1], schemahash)
260
339
  end
261
340
 
262
- return s(call, process(recv), op, process(args))
341
+ return s(tag, process(recv), op, *(args.map{|a| process(a)}))
342
+ end
343
+ end
344
+
345
+ # Check for whether the rule invokes any "unsafe" functions (functions that
346
+ # might return a different value every time they are called, e.g., budtime). The
347
+ # test for "unsafe" functions is pretty naive: any function call with a nil
348
+ # receiver is treated as unsafe unless it is belongs to a list of "safe"
349
+ # functions (below) or it denotes a lattice identifier. In the latter case, the
350
+ # rule is akin to an implicit join with the lattice, so we only rescan it on
351
+ # deltas to the lattice (see "rescan_on_merge" in LatticeWrapper).
352
+ #
353
+ # Although this is called a rewriter, it doesn't modify the input AST.
354
+ class UnsafeFuncRewriter < SexpProcessor
355
+ SAFE_FUNC_LIST = [:int_ip_port, :ip_port, :ip, :port].to_set
356
+
357
+ attr_reader :unsafe_func_called
358
+
359
+ def initialize(bud_instance)
360
+ super()
361
+ self.require_empty = false
362
+ self.expected = Sexp
363
+ @bud_instance = bud_instance
364
+ @unsafe_func_called = false
365
+ @elem_stack = []
366
+ end
367
+
368
+ def process_call(exp)
369
+ tag, recv, op, *args = exp
370
+
371
+ # We assume that unsafe funcs have a nil receiver (Bud instance is implicit
372
+ # receiver).
373
+ if recv.nil? and @elem_stack.size > 0
374
+ unless is_safe_func(op) || is_collection_name?(op)
375
+ @unsafe_func_called = true
376
+ end
377
+ end
378
+
379
+ return s(tag, process(recv), op, *(args.map{|a| process(a)}))
380
+ end
381
+
382
+ def process_iter(exp)
383
+ tag, recv, iter_args, body = exp
384
+ if (iter_args == 0)
385
+ iter_args = s(:args)
386
+ end
387
+ new_body = push_and_process(body)
388
+ return s(tag, process(recv), process(iter_args), new_body)
389
+ end
390
+
391
+ def push_and_process(exp)
392
+ obj_id = exp.object_id
393
+ @elem_stack.push(obj_id)
394
+ rv = process(exp)
395
+ raise Bud::Error unless @elem_stack.pop == obj_id
396
+ return rv
397
+ end
398
+
399
+ def is_collection_name?(op)
400
+ @bud_instance.tables.has_key?(op.to_sym) || @bud_instance.lattices.has_key?(op.to_sym)
401
+ end
402
+
403
+ def is_safe_func(op)
404
+ SAFE_FUNC_LIST.include? op
405
+ end
406
+ end
407
+
408
+ # Rewrite references to lattice identifiers that appear in rule bodies. A
409
+ # reference to a lattice identifier returns the associated lattice wrapper. When
410
+ # the identifier appears at the top-level of the rule RHS, that is fine (since
411
+ # we want the wrapper to do wiring). But for references that appear inside rule
412
+ # bodies, we want to instead fetch the current value associated with the lattice
413
+ # wrapper.
414
+ class LatticeRefRewriter < SexpProcessor
415
+ def initialize(bud_instance)
416
+ super()
417
+ self.require_empty = false
418
+ self.expected = Sexp
419
+ @bud_instance = bud_instance
420
+ @elem_stack = []
421
+ end
422
+
423
+ def process_iter(exp)
424
+ tag, recv, iter_args, body = exp
425
+ new_body = push_and_process(body)
426
+ if (iter_args == 0)
427
+ iter_args = s(:args)
428
+ end
429
+ return s(tag, process(recv), process(iter_args), new_body)
430
+ end
431
+
432
+ def process_array(exp)
433
+ new_body = exp.sexp_body.map {|t| push_and_process(t)}
434
+ return s(:array, *new_body)
435
+ end
436
+
437
+ def process_hash(exp)
438
+ new_body = exp.sexp_body.map {|t| push_and_process(t)}
439
+ return s(:hash, *new_body)
440
+ end
441
+
442
+ def process_call(exp)
443
+ tag, recv, op, *args = exp
444
+
445
+ if recv.nil? and args.empty? and is_lattice?(op) and @elem_stack.size > 0
446
+ return s(:call, exp, :current_value)
447
+ else
448
+ return s(tag, process(recv), op, *(args.map{|a| process(a)}))
449
+ end
450
+ end
451
+
452
+ def push_and_process(exp)
453
+ obj_id = exp.object_id
454
+ @elem_stack.push(obj_id)
455
+ rv = process(exp)
456
+ raise Bud::Error unless @elem_stack.pop == obj_id
457
+ return rv
458
+ end
459
+
460
+ def is_lattice?(op)
461
+ @bud_instance.lattices.has_key? op.to_sym
263
462
  end
264
463
  end
265
464
 
@@ -278,32 +477,43 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
278
477
  # iter vars
279
478
  def process_iter(exp)
280
479
  if exp[1] and exp[1][0] == :call
480
+ return exp unless exp[2]
281
481
  gather_collection_names(exp[1])
482
+ meth_name = exp[1][2]
282
483
 
283
484
  # now find iter vars and match up
284
- if exp[2] and exp[2][0] == :lasgn and @collnames.size == 1 #single-table iter
285
- raise Bud::CompileError, "nested redefinition of block variable \"#{exp[2][1]}\" not allowed" if @iterhash[exp[2][1]]
286
- @iterhash[exp[2][1]] = @collnames[0]
287
- elsif exp[2] and exp[2][0] == :lasgn and @collnames.size > 1 and exp[1] # join iter with lefts/rights
288
- case exp[1][2]
485
+ if exp[2][0] == :args and @collnames.size == 1 # single-table iter
486
+ if @iterhash[exp[2][1]]
487
+ raise Bud::CompileError, "redefinition of block variable \"#{exp[2][1]}\" not allowed"
488
+ end
489
+
490
+ # XXX: The BudChannel#payloads method assigns the correct schema to
491
+ # tuples that pass through it (i.e., it omits the location specifier);
492
+ # hence we don't want to apply the location rewrite to the code block
493
+ # that is passed to payloads(). This is a dirty hack.
494
+ unless meth_name == :payloads
495
+ @iterhash[exp[2][1]] = @collnames[0]
496
+ end
497
+ elsif exp[2][0] == :args and not @collnames.empty? # join iter with lefts/rights
498
+ case meth_name
289
499
  when :lefts
290
500
  @iterhash[exp[2][1]] = @collnames[0]
291
501
  when :rights
292
502
  @iterhash[exp[2][1]] = @collnames[1]
293
- else
294
- raise Bud::CompileError, "nested redefinition of block variable \"#{exp[2][1]}\" not allowed" if @iterhash[exp[2][1]]
295
- end
296
- elsif exp[2] and exp[2][0] == :masgn and not @collnames.empty? # join or reduce iter
297
- return unless exp[2][1] and exp[2][1][0] == :array
298
- if exp[1][2] == :reduce
503
+ when :reduce
299
504
  unless @collnames.length == 1
300
- raise Bud::Error, "reduce should only have one associated collection, but has #{@collnames.inspect}"
505
+ raise Bud::CompileError, "reduce should only have one associated collection, but has #{@collnames.inspect}"
301
506
  end
302
- @iterhash[exp[2][1][2][1]] = @collnames.first
303
- else #join
304
- @collnames.each_with_index do |c, i|
305
- next unless exp[2][1][i+1] and exp[2][1][i+1][0] == :lasgn
306
- @iterhash[exp[2][1][i+1][1]] = c
507
+ @iterhash[exp[2][1]] = @collnames[0]
508
+ else
509
+ # join
510
+ if @iterhash[exp[2][1]]
511
+ raise Bud::CompileError, "redefinition of block variable \"#{exp[2][1]}\" not allowed"
512
+ end
513
+
514
+ @collnames.each_with_index do |c,i|
515
+ next unless exp[2][i+1]
516
+ @iterhash[exp[2][i+1]] = c
307
517
  end
308
518
  end
309
519
  end
@@ -313,36 +523,43 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
313
523
  end
314
524
 
315
525
  def gather_collection_names(exp)
316
- if exp[0] == :call and exp[1].nil?
526
+ # We expect a reference to a collection name to look like a function call
527
+ # (nil receiver) with no arguments.
528
+ if exp.sexp_type == :call and exp[1].nil? and exp.length == 3
317
529
  @collnames << exp[2]
318
- elsif exp[2] and exp[2] == :rename
319
- arglist, namelit, schemahash = exp[3]
530
+ elsif exp.sexp_type == :call and exp[2] == :rename
531
+ namelit = exp[3]
320
532
  @collnames << namelit[1]
533
+ elsif exp.sexp_type == :call and [:group, :argagg].include?(exp[2])
534
+ # For grouping and argagg expressions, only look at the receiver (the
535
+ # collection we're grouping on); otherwise, we might mistakenly think some
536
+ # of the arguments to the grouping operation are collection names.
537
+ gather_collection_names(exp[1])
321
538
  else
322
- exp.each { |e| gather_collection_names(e) if e and e.class <= Sexp }
539
+ exp.each { |e| gather_collection_names(e) if e.class <= Sexp }
323
540
  end
324
541
  end
325
542
 
326
543
  def process_call(exp)
327
- call, recv, op, args = exp
544
+ call, recv, op, *args = exp
328
545
 
329
- if recv and recv.class == Sexp and recv.first == :lvar and recv[1] and @iterhash[recv[1]]
546
+ if recv.class == Sexp and recv.sexp_type == :lvar and @iterhash[recv[1]]
330
547
  if @bud_instance.respond_to?(@iterhash[recv[1]])
331
548
  if @bud_instance.send(@iterhash[recv[1]]).class <= Bud::BudCollection
332
549
  cols = @bud_instance.send(@iterhash[recv[1]]).cols
333
550
  if op != :[] and @bud_instance.send(@iterhash[recv[1]]).respond_to?(op)
334
- # if the op is an attribute name in the schema, col is its index
335
- col = cols.index(op) unless cols.nil?
336
- unless col.nil?
551
+ # if the op is an attribute name in the schema, col_idx is its index
552
+ col_idx = cols.index(op) unless cols.nil?
553
+ unless col_idx.nil?
337
554
  op = :[]
338
- args = s(:arglist, s(:lit, col))
555
+ args = [s(:lit, col_idx)]
339
556
  end
340
557
  end
341
558
  end
342
- return s(call, recv, op, args)
559
+ return s(call, recv, op, *args)
343
560
  end
344
561
  end
345
- return s(call, process(recv), op, process(args))
562
+ return s(call, process(recv), op, *(args.map{|a| process(a)}))
346
563
  end
347
564
  end
348
565
 
@@ -354,7 +571,7 @@ class TempExpander < SexpProcessor # :nodoc: all
354
571
  attr_reader :tmp_tables
355
572
  attr_accessor :did_work
356
573
 
357
- KEYWORD = :temp
574
+ TEMP_KEYWORD = :temp
358
575
 
359
576
  def initialize
360
577
  super()
@@ -365,71 +582,63 @@ class TempExpander < SexpProcessor # :nodoc: all
365
582
  end
366
583
 
367
584
  def process_defn(exp)
368
- tag, name, args, scope = exp
369
- if name.to_s =~ /^__bloom__.+/
370
- block = scope[1]
371
-
372
- block.each_with_index do |n,i|
373
- if i == 0
374
- raise Bud::CompileError if n != :block
375
- next
376
- end
377
-
378
- # temp declarations are misparsed if the RHS contains certain constructs
379
- # (e.g., group, "do |f| ... end" rather than "{|f| ... }"). Rewrite to
380
- # correct the misparsing.
381
- if n.sexp_type == :iter
382
- iter_body = n.sexp_body
383
- new_n = fix_temp_decl(iter_body)
384
- unless new_n.nil?
385
- block[i] = n = new_n
386
- @did_work = true
387
- end
388
- end
389
-
390
- _, recv, meth, meth_args = n
391
- if meth == KEYWORD and recv.nil?
392
- block[i] = rewrite_me(n)
585
+ tag, name, args, *body = exp
586
+ return exp unless name.to_s =~ /^__bloom__.+/
587
+
588
+ body.each_with_index do |n,i|
589
+ # temp declarations are misparsed if the RHS contains certain constructs
590
+ # (e.g., group, "do |f| ... end" rather than "{|f| ... }"). Rewrite to
591
+ # correct the misparsing.
592
+ if n.sexp_type == :iter
593
+ iter_body = n.sexp_body
594
+ new_n = fix_temp_decl(iter_body)
595
+ unless new_n.nil?
596
+ body[i] = n = new_n
393
597
  @did_work = true
394
598
  end
395
599
  end
600
+
601
+ _, recv, meth, meth_args = n
602
+ if meth == TEMP_KEYWORD and recv.nil?
603
+ body[i] = rewrite_temp(n)
604
+ @did_work = true
605
+ end
396
606
  end
397
- s(tag, name, args, scope)
607
+ s(tag, name, args, *body)
398
608
  end
399
609
 
400
610
  private
401
611
  def fix_temp_decl(iter_body)
402
612
  if iter_body.first.sexp_type == :call
403
613
  call_node = iter_body.first
614
+ _, recv, meth, *meth_args = call_node
404
615
 
405
- _, recv, meth, meth_args = call_node
406
- if meth == KEYWORD and recv.nil?
407
- _, lhs, op, rhs = meth_args.sexp_body.first
408
-
409
- old_rhs_body = rhs.sexp_body
410
- new_rhs_body = [:iter]
411
- new_rhs_body += old_rhs_body
412
- new_rhs_body += iter_body[1..-1]
413
- rhs[1] = Sexp.from_array(new_rhs_body)
616
+ if meth == TEMP_KEYWORD and recv.nil?
617
+ _, lhs, op, rhs = meth_args.first
618
+ new_rhs = s(:iter, rhs, *(iter_body[1..-1]))
619
+ meth_args.first[3] = new_rhs
414
620
  return call_node
415
621
  end
416
622
  end
417
623
  return nil
418
624
  end
419
625
 
420
- def rewrite_me(exp)
421
- _, recv, meth, args = exp
626
+ def rewrite_temp(exp)
627
+ _, recv, meth, *args = exp
422
628
 
423
- raise Bud::CompileError unless recv == nil
424
- nest_call = args.sexp_body.first
629
+ raise Bud::CompileError unless recv.nil?
630
+ nest_call = args.first
425
631
  raise Bud::CompileError unless nest_call.sexp_type == :call
426
632
 
427
- nest_recv, nest_op, nest_args = nest_call.sexp_body
428
- raise Bud::CompileError unless nest_recv.sexp_type == :lit
633
+ nest_recv, nest_op, *nest_args = nest_call.sexp_body
634
+ unless nest_recv.sexp_type == :lit
635
+ recv_src = Ruby2Ruby.new.process(Marshal.load(Marshal.dump(nest_recv)))
636
+ raise Bud::CompileError, "argument to temp must be a symbol: #{recv_src}"
637
+ end
429
638
 
430
639
  tmp_name = nest_recv.sexp_body.first
431
640
  @tmp_tables << tmp_name
432
- new_recv = s(:call, nil, tmp_name, s(:arglist))
433
- return s(:call, new_recv, nest_op, nest_args)
641
+ new_recv = s(:call, nil, tmp_name)
642
+ return s(:call, new_recv, nest_op, *nest_args)
434
643
  end
435
644
  end