bud 0.9.4 → 0.9.9

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.
@@ -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