bud 0.9.4 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +23 -0
- data/bin/budlabel +63 -0
- data/bin/budtimelines +1 -1
- data/docs/cheat.md +1 -1
- data/docs/getstarted.md +8 -8
- data/examples/chat/README.md +2 -0
- data/examples/chat/chat.rb +3 -2
- data/examples/chat/chat_protocol.rb +1 -1
- data/examples/chat/chat_server.rb +3 -2
- data/lib/bud/aggs.rb +16 -2
- data/lib/bud/bud_meta.rb +19 -28
- data/lib/bud/collections.rb +157 -39
- data/lib/bud/depanalysis.rb +3 -4
- data/lib/bud/executor/elements.rb +62 -57
- data/lib/bud/executor/group.rb +35 -32
- data/lib/bud/executor/join.rb +0 -11
- data/lib/bud/graphs.rb +1 -1
- data/lib/bud/labeling/bloomgraph.rb +47 -0
- data/lib/bud/labeling/budplot_style.rb +53 -0
- data/lib/bud/labeling/labeling.rb +288 -0
- data/lib/bud/lattice-core.rb +563 -0
- data/lib/bud/lattice-lib.rb +367 -0
- data/lib/bud/monkeypatch.rb +18 -8
- data/lib/bud/rewrite.rb +314 -139
- data/lib/bud/server.rb +13 -2
- data/lib/bud/source.rb +34 -18
- data/lib/bud/state.rb +90 -1
- data/lib/bud/storage/zookeeper.rb +38 -33
- data/lib/bud/viz.rb +0 -1
- data/lib/bud.rb +55 -15
- metadata +15 -8
@@ -0,0 +1,367 @@
|
|
1
|
+
require 'bud/lattice-core'
|
2
|
+
|
3
|
+
class Bud::MaxLattice < Bud::Lattice
|
4
|
+
wrapper_name :lmax
|
5
|
+
|
6
|
+
def initialize(i=nil)
|
7
|
+
unless i.nil? || i.class <= Comparable
|
8
|
+
reject_input(i)
|
9
|
+
end
|
10
|
+
@v = i
|
11
|
+
end
|
12
|
+
|
13
|
+
def merge(i)
|
14
|
+
i_val = i.reveal
|
15
|
+
(@v.nil? || (i_val != nil && i_val > @v)) ? i : self
|
16
|
+
end
|
17
|
+
|
18
|
+
morph :gt do |k|
|
19
|
+
Bud::BoolLattice.new(!!(@v && @v > k))
|
20
|
+
end
|
21
|
+
|
22
|
+
morph :gt_eq do |k|
|
23
|
+
Bud::BoolLattice.new(!!(@v && @v >= k))
|
24
|
+
end
|
25
|
+
|
26
|
+
# XXX: support MaxLattice input?
|
27
|
+
morph :+ do |i|
|
28
|
+
# Since bottom of lmax is negative infinity, + is a no-op
|
29
|
+
return self if @v.nil?
|
30
|
+
reject_input(i, "+") unless i.class <= Numeric
|
31
|
+
self.class.new(@v + i)
|
32
|
+
end
|
33
|
+
|
34
|
+
morph :min_of do |i|
|
35
|
+
reject_input(i, "min_of") unless i.class <= Numeric
|
36
|
+
(@v.nil? || i < @v) ? self.class.new(i) : self
|
37
|
+
end
|
38
|
+
|
39
|
+
def lt_eq(k)
|
40
|
+
Bud::BoolLattice.new(!!(@v && @v <= k))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Bud::MinLattice < Bud::Lattice
|
45
|
+
wrapper_name :lmin
|
46
|
+
|
47
|
+
def initialize(i=nil)
|
48
|
+
unless i.nil? || i.class <= Comparable
|
49
|
+
reject_input(i)
|
50
|
+
end
|
51
|
+
@v = i
|
52
|
+
end
|
53
|
+
|
54
|
+
def merge(i)
|
55
|
+
i_val = i.reveal
|
56
|
+
(@v.nil? || (i_val != nil && i_val < @v)) ? i : self
|
57
|
+
end
|
58
|
+
|
59
|
+
morph :lt do |k|
|
60
|
+
Bud::BoolLattice.new(!!(@v && @v < k))
|
61
|
+
end
|
62
|
+
|
63
|
+
# XXX: support MinLattice input
|
64
|
+
morph :+ do |i|
|
65
|
+
# Since bottom of lmin is infinity, + is a no-op
|
66
|
+
return self if @v.nil?
|
67
|
+
reject_input(i, "+") unless i.class <= Numeric
|
68
|
+
self.class.new(@v + i)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# XXX: consider creating two fixed ("interned") values for true and false.
|
73
|
+
class Bud::BoolLattice < Bud::Lattice
|
74
|
+
wrapper_name :lbool
|
75
|
+
|
76
|
+
def initialize(i=false)
|
77
|
+
reject_input(i) unless [true, false].include? i
|
78
|
+
@v = i
|
79
|
+
end
|
80
|
+
|
81
|
+
def merge(i)
|
82
|
+
self.class.new(@v || i.reveal)
|
83
|
+
end
|
84
|
+
|
85
|
+
# XXX: ugly syntax
|
86
|
+
morph :when_true do |&blk|
|
87
|
+
blk.call if @v
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class Bud::MapLattice < Bud::Lattice
|
92
|
+
wrapper_name :lmap
|
93
|
+
|
94
|
+
def initialize(i={})
|
95
|
+
reject_input(i) unless i.class == Hash
|
96
|
+
i.each_pair do |k,val|
|
97
|
+
reject_input(i) if k.class <= Bud::Lattice
|
98
|
+
reject_input(i) unless val.class <= Bud::Lattice
|
99
|
+
end
|
100
|
+
@v = i
|
101
|
+
end
|
102
|
+
|
103
|
+
def merge(i)
|
104
|
+
rv = @v.merge(i.reveal) do |k, lhs_v, rhs_v|
|
105
|
+
lhs_v.merge(rhs_v)
|
106
|
+
end
|
107
|
+
wrap_unsafe(rv)
|
108
|
+
end
|
109
|
+
|
110
|
+
def inspect
|
111
|
+
"<#{self.class.wrapper}: #{@v.inspect}>"
|
112
|
+
end
|
113
|
+
|
114
|
+
# XXX: If the key is not in the map, we would like to return some generic
|
115
|
+
# "bottom" value that is shared by all lattice types. Unfortunately, such a
|
116
|
+
# value does not exist, so we need the caller to tell us which class to use as
|
117
|
+
# an optional second argument (if omitted, fetching a non-existent key yields
|
118
|
+
# a runtime exception). Another alternative would be to specify the type of
|
119
|
+
# the map's values when the lmap is declared, but that hinders code reuse.
|
120
|
+
morph :at do |k, *args|
|
121
|
+
if @v.has_key? k
|
122
|
+
@v[k]
|
123
|
+
else
|
124
|
+
raise Bud::Error if args.empty?
|
125
|
+
args.first.new
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
morph :apply_morph do |sym, *args|
|
130
|
+
raise Bud::Error unless Bud::Lattice.global_morphs.include? sym
|
131
|
+
do_apply(sym, args)
|
132
|
+
end
|
133
|
+
|
134
|
+
monotone :apply_monotone do |sym, *args|
|
135
|
+
raise Bud::Error unless Bud::Lattice.global_mfuncs.include? sym
|
136
|
+
do_apply(sym, args)
|
137
|
+
end
|
138
|
+
|
139
|
+
def do_apply(sym, args)
|
140
|
+
rv = {}
|
141
|
+
@v.each_pair do |k, val|
|
142
|
+
res = val.send(sym, *args)
|
143
|
+
raise Bud::Error unless res.kind_of? Bud::Lattice
|
144
|
+
rv[k] = res
|
145
|
+
end
|
146
|
+
wrap_unsafe(rv)
|
147
|
+
end
|
148
|
+
|
149
|
+
morph :key? do |k|
|
150
|
+
Bud::BoolLattice.new(@v.has_key? k)
|
151
|
+
end
|
152
|
+
|
153
|
+
morph :key_set do
|
154
|
+
Bud::SetLattice.new(@v.keys)
|
155
|
+
end
|
156
|
+
|
157
|
+
monotone :size do
|
158
|
+
Bud::MaxLattice.new(@v.size)
|
159
|
+
end
|
160
|
+
|
161
|
+
morph :intersect do |i|
|
162
|
+
i_tbl = i.reveal
|
163
|
+
# Scan the smaller map, probe the larger one
|
164
|
+
scan, probe = (@v.size < i_tbl.size ? [@v, i_tbl] : [i_tbl, @v])
|
165
|
+
rv = {}
|
166
|
+
scan.each do |k,val|
|
167
|
+
rv[k] = val.merge(probe[k]) if probe.has_key? k
|
168
|
+
end
|
169
|
+
wrap_unsafe(rv)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Produce a Bloom collection (array of tuples) from this lmap, optionally
|
173
|
+
# applying a user-provided code block to each (k,v) pair in turn. Note that
|
174
|
+
# this is slightly different from how projection over an lmap would work: we
|
175
|
+
# return an array, whereas projection would return an lmap.
|
176
|
+
morph :to_collection do |&blk|
|
177
|
+
@v.map(&blk)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Return true if this map is strictly smaller than or equal to the given
|
181
|
+
# map. "x" is strictly smaller than or equal to "y" if:
|
182
|
+
# (a) every key in "x" also appears in "y"
|
183
|
+
# (b) for every key k in "x", x[k] <= y[k]
|
184
|
+
#
|
185
|
+
# NB: For this to be a morphism, we require that (a) "self" is deflationary
|
186
|
+
# (or fixed) (b) the input lattice value is inflationary (or fixed). We
|
187
|
+
# currently don't have a way to express (a) in the type system.
|
188
|
+
def lt_eq(i)
|
189
|
+
reject_input(i, "lt_eq") unless i.class <= self.class
|
190
|
+
|
191
|
+
@v.each do |k, v|
|
192
|
+
unless i.key?(k).reveal == true
|
193
|
+
return Bud::BoolLattice.new(false)
|
194
|
+
end
|
195
|
+
unless v.lt_eq(i.at(k).reveal).reveal == true
|
196
|
+
return Bud::BoolLattice.new(false)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
return Bud::BoolLattice.new(true)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# A set lattice contains zero or more primitive (non-lattice) values.
|
205
|
+
class Bud::SetLattice < Bud::Lattice
|
206
|
+
wrapper_name :lset
|
207
|
+
|
208
|
+
def initialize(i=[])
|
209
|
+
reject_input(i) if i.any? {|e| e.kind_of? Bud::Lattice}
|
210
|
+
|
211
|
+
i = Set.new(i) unless i.kind_of? Set
|
212
|
+
@v = i
|
213
|
+
end
|
214
|
+
|
215
|
+
def merge(i)
|
216
|
+
wrap_unsafe(@v | i.reveal)
|
217
|
+
end
|
218
|
+
|
219
|
+
morph :intersect do |i|
|
220
|
+
wrap_unsafe(@v & i.reveal)
|
221
|
+
end
|
222
|
+
|
223
|
+
morph :product do |i, &blk|
|
224
|
+
rv = Set.new
|
225
|
+
@v.each do |a|
|
226
|
+
if blk.nil?
|
227
|
+
t = i.pro {|b| [a,b]}
|
228
|
+
else
|
229
|
+
t = i.pro {|b| blk.call(a, b)}
|
230
|
+
end
|
231
|
+
rv.merge(t.reveal)
|
232
|
+
end
|
233
|
+
wrap_unsafe(rv)
|
234
|
+
end
|
235
|
+
|
236
|
+
morph :contains? do |i|
|
237
|
+
Bud::BoolLattice.new(@v.member? i)
|
238
|
+
end
|
239
|
+
|
240
|
+
morph :pro do |&blk|
|
241
|
+
# We don't use Set#map, since it returns an Array (ugh).
|
242
|
+
rv = Set.new
|
243
|
+
@v.each do |t|
|
244
|
+
val = blk.call(t)
|
245
|
+
rv << val unless val.nil?
|
246
|
+
end
|
247
|
+
wrap_unsafe(rv)
|
248
|
+
end
|
249
|
+
|
250
|
+
monotone :size do
|
251
|
+
Bud::MaxLattice.new(@v.size)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Assuming that this set contains tuples (arrays) as elements, this performs
|
255
|
+
# an equijoin between the current lattice and i. The join predicate is
|
256
|
+
# "self_t[lhs_idx] == i_t[rhs_idx]", for all tuples self_t and i_t in self and
|
257
|
+
# i, respectively. The return value is the result of passing pairs of join
|
258
|
+
# tuples to the user-supplied block.
|
259
|
+
morph :eqjoin do |i, lhs_idx, rhs_idx, &blk|
|
260
|
+
rv = Set.new
|
261
|
+
@v.each do |a|
|
262
|
+
i.probe(rhs_idx, a[lhs_idx]).each do |b|
|
263
|
+
rv << blk.call(a, b)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
wrap_unsafe(rv)
|
267
|
+
end
|
268
|
+
|
269
|
+
# Assuming that this set contains tuples (arrays), this returns a list of
|
270
|
+
# tuples (possibly empty) whose idx'th column has the value "v".
|
271
|
+
# XXX: we assume probe(idx, v) will only be called for a single value of idx!
|
272
|
+
def probe(idx, v)
|
273
|
+
@ht ||= build_ht(idx)
|
274
|
+
return @ht[v] || []
|
275
|
+
end
|
276
|
+
|
277
|
+
private
|
278
|
+
def build_ht(idx)
|
279
|
+
rv = {}
|
280
|
+
@v.each do |i|
|
281
|
+
field = i[idx]
|
282
|
+
rv[field] ||= []
|
283
|
+
rv[field] << i
|
284
|
+
end
|
285
|
+
rv
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# A set that admits only non-negative numbers. This allows "sum" to be an
|
290
|
+
# order-preserving map. Note that this does duplicate elimination on its input,
|
291
|
+
# so it actually computes "SUM(DISTINCT ...)" in SQL.
|
292
|
+
#
|
293
|
+
# XXX: for methods that take a user-provided code block, we need to ensure that
|
294
|
+
# the set continues to contain only positive numbers.
|
295
|
+
class Bud::PositiveSetLattice < Bud::SetLattice
|
296
|
+
wrapper_name :lpset
|
297
|
+
|
298
|
+
def initialize(i=[])
|
299
|
+
super
|
300
|
+
@v.each do |n|
|
301
|
+
reject_input(i) unless n.class <= Numeric
|
302
|
+
reject_input(i) if n < 0
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
monotone :pos_sum do
|
307
|
+
@sum = @v.reduce(:+) if @sum.nil?
|
308
|
+
Bud::MaxLattice.new(@sum)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# XXX: Should this be just syntax sugar for a map lattice instead?
|
313
|
+
class Bud::BagLattice < Bud::Lattice
|
314
|
+
wrapper_name :lbag
|
315
|
+
|
316
|
+
def initialize(i={})
|
317
|
+
reject_input(i) unless i.class <= Hash
|
318
|
+
i.each do |k, mult|
|
319
|
+
reject_input(i) if k.class <= Bud::Lattice
|
320
|
+
reject_input(i) unless (mult.class <= Integer && mult > 0)
|
321
|
+
end
|
322
|
+
@v = i
|
323
|
+
end
|
324
|
+
|
325
|
+
# Note that for merge to be idempotent, we need to use the traditional
|
326
|
+
# definition of multiset union (per-element max of multiplicities, rather than
|
327
|
+
# sum of multiplicities).
|
328
|
+
def merge(i)
|
329
|
+
rv = @v.merge(i.reveal) do |k, lhs_v, rhs_v|
|
330
|
+
[lhs_v, rhs_v].max
|
331
|
+
end
|
332
|
+
wrap_unsafe(rv)
|
333
|
+
end
|
334
|
+
|
335
|
+
morph :intersect do |i|
|
336
|
+
i_tbl = i.reveal
|
337
|
+
# Scan the smaller one, probe the larger one
|
338
|
+
scan, probe = (@v.size < i_tbl.size ? [@v, i_tbl] : [i_tbl, @v])
|
339
|
+
rv = {}
|
340
|
+
scan.each do |k,val|
|
341
|
+
rv[k] = [val, probe[k]].min if probe.has_key? k
|
342
|
+
end
|
343
|
+
wrap_unsafe(rv)
|
344
|
+
end
|
345
|
+
|
346
|
+
morph :multiplicity do |k|
|
347
|
+
rv = @v[k]
|
348
|
+
rv ||= 0
|
349
|
+
Bud::MaxLattice.new(rv)
|
350
|
+
end
|
351
|
+
|
352
|
+
morph :+ do |i|
|
353
|
+
rv = @v.merge(i.reveal) do |k, lhs_v, rhs_v|
|
354
|
+
lhs_v + rhs_v
|
355
|
+
end
|
356
|
+
self.class.new(rv)
|
357
|
+
end
|
358
|
+
|
359
|
+
morph :contains? do |i|
|
360
|
+
Bud::BoolLattice.new(@v.has_key? i)
|
361
|
+
end
|
362
|
+
|
363
|
+
monotone :size do
|
364
|
+
@size = @v.values.reduce(:+) if @size.nil?
|
365
|
+
Bud::MaxLattice.new(@size)
|
366
|
+
end
|
367
|
+
end
|
data/lib/bud/monkeypatch.rb
CHANGED
@@ -11,6 +11,7 @@ class Class
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# FIXME: Use a subclass of Struct.
|
14
|
+
# FIXME: Should likely override eql? as well
|
14
15
|
class Struct
|
15
16
|
def <=>(o)
|
16
17
|
if o.class == self.class
|
@@ -139,7 +140,7 @@ class Module
|
|
139
140
|
end
|
140
141
|
|
141
142
|
# bloom statements to be registered with Bud runtime. optional +block_name+
|
142
|
-
# allows for multiple bloom blocks per module
|
143
|
+
# allows for multiple bloom blocks per module and method overriding
|
143
144
|
def bloom(block_name=nil, &block)
|
144
145
|
# If no block name was specified, generate a unique name
|
145
146
|
if block_name.nil?
|
@@ -148,7 +149,7 @@ class Module
|
|
148
149
|
@block_id += 1
|
149
150
|
else
|
150
151
|
unless block_name.class <= Symbol
|
151
|
-
raise Bud::CompileError, "
|
152
|
+
raise Bud::CompileError, "block name must be a symbol: #{block_name}"
|
152
153
|
end
|
153
154
|
end
|
154
155
|
|
@@ -161,15 +162,24 @@ class Module
|
|
161
162
|
# module; this indicates a likely programmer error.
|
162
163
|
if instance_methods(false).include?(meth_name) ||
|
163
164
|
instance_methods(false).include?(meth_name.to_sym)
|
164
|
-
raise Bud::CompileError, "duplicate
|
165
|
+
raise Bud::CompileError, "duplicate block name: '#{block_name}' in #{self}"
|
165
166
|
end
|
166
167
|
ast = Source.read_block(caller[0]) # pass in caller's location via backtrace
|
168
|
+
|
167
169
|
# ast corresponds only to the statements of the block. Wrap it in a method
|
168
170
|
# definition for backward compatibility for now.
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
171
|
+
|
172
|
+
# If the block contained multiple statements, the AST will have a top-level
|
173
|
+
# :block node. Since ruby_parser ASTs for method definitions don't contain
|
174
|
+
# such a node, remove it.
|
175
|
+
if ast.nil?
|
176
|
+
ast = []
|
177
|
+
elsif ast.sexp_type == :block
|
178
|
+
ast = ast.sexp_body
|
179
|
+
else
|
180
|
+
ast = [ast]
|
181
|
+
end
|
182
|
+
ast = s(:defn, meth_name.to_sym, s(:args), *ast)
|
173
183
|
unless self.respond_to? :__bloom_asts__
|
174
184
|
def self.__bloom_asts__
|
175
185
|
@__bloom_asts__ ||= {}
|
@@ -180,11 +190,11 @@ class Module
|
|
180
190
|
define_method(meth_name.to_sym, &block)
|
181
191
|
end
|
182
192
|
|
183
|
-
private
|
184
193
|
# Return a string with a version of the class name appropriate for embedding
|
185
194
|
# into a method name. Annoyingly, if you define class X nested inside
|
186
195
|
# class/module Y, X's class name is the string "Y::X". We don't want to define
|
187
196
|
# method names with semicolons in them, so just return "X" instead.
|
197
|
+
private
|
188
198
|
def self.get_class_name(klass)
|
189
199
|
(klass.name.nil? or klass.name == "") \
|
190
200
|
? "Anon#{klass.object_id}" \
|
data/lib/bud/rewrite.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'ruby2ruby'
|
3
|
-
require 'set'
|
4
2
|
|
5
3
|
class RuleRewriter < Ruby2Ruby # :nodoc: all
|
6
4
|
attr_accessor :rule_indx, :rules, :depends
|
@@ -8,10 +6,11 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
8
6
|
OP_LIST = Set.new([:<<, :<, :<=])
|
9
7
|
TEMP_OP_LIST = Set.new([:-@, :~, :+@])
|
10
8
|
MONOTONE_WHITELIST = Set.new([:==, :+, :<=, :-, :<, :>, :*, :~,
|
11
|
-
:pairs, :matches, :combos, :flatten,
|
12
|
-
:lefts, :rights, :map, :flat_map, :pro,
|
9
|
+
:pairs, :matches, :combos, :flatten, :new,
|
10
|
+
:lefts, :rights, :map, :flat_map, :pro, :merge,
|
13
11
|
:cols, :key_cols, :val_cols, :payloads, :lambda,
|
14
|
-
:tabname, :ip_port, :port, :ip, :int_ip_port
|
12
|
+
:tabname, :ip_port, :port, :ip, :int_ip_port,
|
13
|
+
:current_value])
|
15
14
|
|
16
15
|
def initialize(seed, bud_instance)
|
17
16
|
@bud_instance = bud_instance
|
@@ -21,7 +20,8 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
21
20
|
@collect = false
|
22
21
|
@rules = []
|
23
22
|
@depends = []
|
24
|
-
@
|
23
|
+
@iter_stack = []
|
24
|
+
@refs_in_body = Set.new
|
25
25
|
super()
|
26
26
|
end
|
27
27
|
|
@@ -29,6 +29,7 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
29
29
|
def resolve(obj, prefix, name)
|
30
30
|
qn = prefix ? prefix + "." + name.to_s : name.to_s
|
31
31
|
return [:collection, qn, obj.tables[name]] if obj.tables.has_key? name
|
32
|
+
return [:lattice, qn, obj.lattices[name]] if obj.lattices.has_key? name
|
32
33
|
|
33
34
|
# does name refer to an import name?
|
34
35
|
iobj = obj.import_instance name
|
@@ -38,12 +39,13 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
38
39
|
end
|
39
40
|
|
40
41
|
def exp_id_type(recv, name, args) # call only if sexp type is :call
|
41
|
-
return $not_id unless args.
|
42
|
+
return $not_id unless args.empty?
|
42
43
|
ty = $not_id
|
43
44
|
if recv
|
44
45
|
if recv.first == :call
|
45
|
-
# possibly nested reference
|
46
|
-
|
46
|
+
# possibly nested reference
|
47
|
+
# rty, rqn, .. = receiver's type, qual name etc.
|
48
|
+
rty, rqn, robj = exp_id_type(recv[1], recv[2], recv[3..-1])
|
47
49
|
ty = resolve(robj, rqn, name) if rty == :import
|
48
50
|
end
|
49
51
|
else
|
@@ -56,26 +58,88 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
56
58
|
def call_to_id(exp)
|
57
59
|
# convert a series of nested calls, a sexp of the form
|
58
60
|
# s(:call,
|
59
|
-
# s(:call, s(:call, nil, :a
|
60
|
-
# :bar
|
61
|
-
# s(:arglist)))
|
61
|
+
# s(:call, s(:call, nil, :a), :b),
|
62
|
+
# :bar))
|
62
63
|
# to the string "a.b.bar"
|
63
|
-
raise "
|
64
|
-
_, recv, op
|
64
|
+
raise Bud::CompileError, "malformed exp: #{exp}" unless exp.sexp_type == :call
|
65
|
+
_, recv, op = exp
|
65
66
|
return recv.nil? ? op.to_s : call_to_id(recv) + "." + op.to_s
|
66
67
|
end
|
67
68
|
|
69
|
+
# We want to distinguish between collection dependencies that occur in
|
70
|
+
# top-level expressions versus collections that are referenced inside rule
|
71
|
+
# bodies. We just want to set a flag when processing the :iter body, but
|
72
|
+
# annoyingly it seems that is hard to do without duplicating the
|
73
|
+
# implementation of process_iter().
|
74
|
+
#
|
75
|
+
# XXX: the whole RuleRewriter approach is wrong because it conflates
|
76
|
+
# converting ASTs to strings with doing analysis on ASTs. Those should be
|
77
|
+
# split into two separate passes.
|
78
|
+
def process_iter(exp)
|
79
|
+
iter = process exp.shift
|
80
|
+
args = exp.shift
|
81
|
+
|
82
|
+
@iter_stack.push(true)
|
83
|
+
body = exp.empty? ? nil : process(exp.shift)
|
84
|
+
@iter_stack.pop
|
85
|
+
|
86
|
+
do_process_iter(iter, args, body)
|
87
|
+
end
|
88
|
+
|
89
|
+
def do_process_iter(iter, args, body)
|
90
|
+
args = case args
|
91
|
+
when 0 then
|
92
|
+
" ||"
|
93
|
+
else
|
94
|
+
a = process(args)[1..-2]
|
95
|
+
a = " |#{a}|" unless a.empty?
|
96
|
+
a
|
97
|
+
end
|
98
|
+
|
99
|
+
b, e = if iter == "END" then
|
100
|
+
[ "{", "}" ]
|
101
|
+
else
|
102
|
+
[ "do", "end" ]
|
103
|
+
end
|
104
|
+
|
105
|
+
iter.sub!(/\(\)$/, '')
|
106
|
+
|
107
|
+
# REFACTOR: ugh
|
108
|
+
result = []
|
109
|
+
result << "#{iter} {"
|
110
|
+
result << args
|
111
|
+
if body then
|
112
|
+
result << " #{body.strip} "
|
113
|
+
else
|
114
|
+
result << ' '
|
115
|
+
end
|
116
|
+
result << "}"
|
117
|
+
result = result.join
|
118
|
+
return result if result !~ /\n/ and result.size < LINE_LENGTH
|
119
|
+
|
120
|
+
result = []
|
121
|
+
result << "#{iter} #{b}"
|
122
|
+
result << args
|
123
|
+
result << "\n"
|
124
|
+
if body then
|
125
|
+
result << indent(body.strip)
|
126
|
+
result << "\n"
|
127
|
+
end
|
128
|
+
result << e
|
129
|
+
result.join
|
130
|
+
end
|
131
|
+
|
68
132
|
def process_call(exp)
|
69
|
-
recv, op, args = exp
|
70
|
-
if OP_LIST.include?(op) and @context[1] == :
|
71
|
-
# NB: context.length is
|
133
|
+
recv, op, *args = exp
|
134
|
+
if OP_LIST.include?(op) and @context[1] == :defn and @context.length == 2
|
135
|
+
# NB: context.length is 2 when see a method call at the top-level of a
|
72
136
|
# :defn block -- this is where we expect Bloom statements to appear
|
73
137
|
do_rule(exp)
|
74
138
|
elsif op == :notin
|
75
139
|
# Special case. In the rule "z <= x.notin(y)", z depends positively on x,
|
76
140
|
# but negatively on y. See further explanation in the "else" section for
|
77
141
|
# why this is a special case.
|
78
|
-
notintab = call_to_id(args[
|
142
|
+
notintab = call_to_id(args[0]) # args expected to be of the form (:call nil :y ...)
|
79
143
|
@tables[notintab.to_s] = true # "true" denotes non-monotonic dependency
|
80
144
|
super
|
81
145
|
else
|
@@ -88,15 +152,16 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
88
152
|
# a.b.c.notin(d.e.f), we register a non-monotonic dependency of lhs on
|
89
153
|
# "d.e.f", not with "a.b.c"
|
90
154
|
ty, qn, _ = exp_id_type(recv, op, args) # qn = qualified name
|
91
|
-
if ty == :collection
|
155
|
+
if ty == :collection or ty == :lattice
|
92
156
|
(@tables[qn] = @nm if @collect) unless @tables[qn]
|
157
|
+
@refs_in_body << qn unless @iter_stack.empty?
|
93
158
|
#elsif ty == :import .. do nothing
|
94
159
|
elsif ty == :not_coll_id
|
95
160
|
# Check if receiver is a collection, and further if the current exp
|
96
161
|
# represents a field lookup
|
97
162
|
op_is_field_name = false
|
98
163
|
if recv and recv.first == :call
|
99
|
-
rty, _, robj = exp_id_type(recv[1], recv[2], recv[3])
|
164
|
+
rty, _, robj = exp_id_type(recv[1], recv[2], recv[3..-1])
|
100
165
|
if rty == :collection
|
101
166
|
cols = robj.cols
|
102
167
|
op_is_field_name = true if cols and cols.include?(op)
|
@@ -104,18 +169,14 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
104
169
|
end
|
105
170
|
# For CALM analysis, mark deletion rules as non-monotonic
|
106
171
|
@nm = true if op == :-@
|
172
|
+
|
173
|
+
# Don't worry about monotone ops, table names, table.attr calls, or
|
174
|
+
# accessors of iterator variables
|
107
175
|
if recv
|
108
|
-
# Don't worry about monotone ops, table names, table.attr calls, or
|
109
|
-
# accessors of iterator variables
|
110
176
|
unless RuleRewriter.is_monotone(op) or op_is_field_name or
|
111
177
|
recv.first == :lvar or op.to_s.start_with?("__")
|
112
178
|
@nm = true
|
113
179
|
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
180
|
end
|
120
181
|
end
|
121
182
|
if TEMP_OP_LIST.include? op
|
@@ -126,25 +187,35 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
126
187
|
end
|
127
188
|
|
128
189
|
def self.is_monotone(op)
|
129
|
-
MONOTONE_WHITELIST.include?(op)
|
190
|
+
MONOTONE_WHITELIST.include?(op) ||
|
191
|
+
is_morphism(op) ||
|
192
|
+
Bud::Lattice.global_mfuncs.include?(op)
|
130
193
|
end
|
131
194
|
|
132
|
-
|
133
|
-
|
195
|
+
def self.is_morphism(op)
|
196
|
+
Bud::Lattice.global_morphs.include?(op)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Rewrite top-level rhs array and hash literals to lambdas. During wiring,
|
200
|
+
# these are turned into coll_expr collections.
|
134
201
|
def lambda_rewrite(rhs)
|
135
202
|
# the <= case
|
136
|
-
if rhs[0]
|
137
|
-
return s(:iter, s(:call, nil, :lambda, s(:
|
203
|
+
if is_coll_literal(rhs[0])
|
204
|
+
return s(:iter, s(:call, nil, :lambda), s(:args), rhs)
|
138
205
|
# the superator case
|
139
206
|
elsif rhs[0] == :call \
|
140
|
-
and rhs[1] and rhs[1][0] and rhs[1][0]
|
207
|
+
and rhs[1] and rhs[1][0] and is_coll_literal(rhs[1][0]) \
|
141
208
|
and rhs[2] and (rhs[2] == :+@ or rhs[2] == :-@ or rhs[2] == :~@)
|
142
|
-
return s(rhs[0], s(:iter, s(:call, nil, :lambda, s(:
|
209
|
+
return s(rhs[0], s(:iter, s(:call, nil, :lambda), s(:args), rhs[1]), rhs[2], *rhs[3..-1])
|
143
210
|
else
|
144
211
|
return rhs
|
145
212
|
end
|
146
213
|
end
|
147
214
|
|
215
|
+
def is_coll_literal(e)
|
216
|
+
[:array, :hash].include? e
|
217
|
+
end
|
218
|
+
|
148
219
|
def collect_rhs(exp)
|
149
220
|
exp = lambda_rewrite(exp)
|
150
221
|
|
@@ -155,13 +226,13 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
155
226
|
end
|
156
227
|
|
157
228
|
def reset_instance_vars
|
229
|
+
@refs_in_body = Set.new
|
158
230
|
@tables = {}
|
159
231
|
@nm = false
|
160
|
-
@nm_funcs_called = false
|
161
232
|
@temp_op = nil
|
162
233
|
end
|
163
234
|
|
164
|
-
def record_rule(lhs, op, rhs_pos, rhs)
|
235
|
+
def record_rule(lhs, op, rhs_pos, rhs, unsafe_funcs_called)
|
165
236
|
rule_txt_orig = "#{lhs} #{op} (#{rhs})"
|
166
237
|
rule_txt = "#{lhs} #{op} (#{rhs_pos})"
|
167
238
|
if op == :<
|
@@ -170,9 +241,11 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
170
241
|
op = op.to_s
|
171
242
|
end
|
172
243
|
|
173
|
-
@rules << [@bud_instance, @rule_indx, lhs, op, rule_txt,
|
244
|
+
@rules << [@bud_instance, @rule_indx, lhs, op, rule_txt,
|
245
|
+
rule_txt_orig, unsafe_funcs_called]
|
174
246
|
@tables.each_pair do |t, nm|
|
175
|
-
|
247
|
+
in_rule_body = @refs_in_body.include? t
|
248
|
+
@depends << [@bud_instance, @rule_indx, lhs, op, t, nm, in_rule_body]
|
176
249
|
end
|
177
250
|
|
178
251
|
reset_instance_vars
|
@@ -180,17 +253,14 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
180
253
|
end
|
181
254
|
|
182
255
|
def do_rule(exp)
|
183
|
-
lhs =
|
184
|
-
|
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]
|
256
|
+
lhs, op, rhs_ast = exp
|
257
|
+
lhs = process(lhs)
|
192
258
|
|
259
|
+
rhs_ast = MapRewriter.new.process(rhs_ast)
|
193
260
|
rhs_ast = RenameRewriter.new(@bud_instance).process(rhs_ast)
|
261
|
+
rhs_ast = LatticeRefRewriter.new(@bud_instance).process(rhs_ast)
|
262
|
+
ufr = UnsafeFuncRewriter.new
|
263
|
+
rhs_ast = ufr.process(rhs_ast)
|
194
264
|
|
195
265
|
if @bud_instance.options[:no_attr_rewrite]
|
196
266
|
rhs = collect_rhs(rhs_ast)
|
@@ -203,35 +273,37 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
203
273
|
reset_instance_vars
|
204
274
|
rhs_pos = collect_rhs(AttrNameRewriter.new(@bud_instance).process(rhs_ast_dup))
|
205
275
|
end
|
206
|
-
record_rule(lhs, op, rhs_pos, rhs)
|
276
|
+
record_rule(lhs, op, rhs_pos, rhs, ufr.unsafe_func_called)
|
207
277
|
drain(exp)
|
208
278
|
end
|
209
279
|
|
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
280
|
def drain(exp)
|
230
281
|
exp.shift until exp.empty?
|
231
282
|
return ""
|
232
283
|
end
|
233
284
|
end
|
234
285
|
|
286
|
+
# We want to rewrite "map" calls on BudCollections to "pro" calls. It is hard
|
287
|
+
# to do this precisely (issue #225), so we just replace map calls liberally
|
288
|
+
# and define Enumerable#pro as an alias for "map".
|
289
|
+
class MapRewriter < SexpProcessor
|
290
|
+
def initialize
|
291
|
+
super
|
292
|
+
self.require_empty = false
|
293
|
+
self.expected = Sexp
|
294
|
+
end
|
295
|
+
|
296
|
+
def process_call(exp)
|
297
|
+
tag, recv, op, *args = exp
|
298
|
+
|
299
|
+
if op == :map and args.empty?
|
300
|
+
op = :pro
|
301
|
+
end
|
302
|
+
|
303
|
+
s(tag, process(recv), op, *(args.map{|a| process(a)}))
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
235
307
|
# Look for rename statements and define the necessary scratch collections
|
236
308
|
class RenameRewriter < SexpProcessor
|
237
309
|
def initialize(bud_instance)
|
@@ -252,14 +324,110 @@ class RenameRewriter < SexpProcessor
|
|
252
324
|
end
|
253
325
|
|
254
326
|
def process_call(exp)
|
255
|
-
|
327
|
+
tag, recv, op, *args = exp
|
256
328
|
|
257
329
|
if op == :rename
|
258
|
-
|
330
|
+
raise Bud::CompileError, "reduce takes two arguments" unless args.size == 2
|
331
|
+
namelit, schemahash = args
|
259
332
|
register_scratch(namelit[1], schemahash)
|
260
333
|
end
|
261
334
|
|
262
|
-
return s(
|
335
|
+
return s(tag, process(recv), op, *(args.map{|a| process(a)}))
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Check for whether the rule invokes any "unsafe" functions (funcs that might
|
340
|
+
# return a different value every time they are called, e.g., budtime). Note that
|
341
|
+
# although we call this a rewriter, it doesn't modify the input AST.
|
342
|
+
class UnsafeFuncRewriter < SexpProcessor
|
343
|
+
attr_reader :unsafe_func_called
|
344
|
+
|
345
|
+
def initialize
|
346
|
+
super()
|
347
|
+
self.require_empty = false
|
348
|
+
self.expected = Sexp
|
349
|
+
@unsafe_func_called = false
|
350
|
+
@elem_stack = []
|
351
|
+
end
|
352
|
+
|
353
|
+
def process_call(exp)
|
354
|
+
tag, recv, op, *args = exp
|
355
|
+
|
356
|
+
# We assume that unsafe funcs have a nil receiver (Bud instance is implicit
|
357
|
+
# receiver).
|
358
|
+
if recv.nil? and @elem_stack.size > 0
|
359
|
+
@unsafe_func_called = true unless RuleRewriter.is_monotone(op)
|
360
|
+
end
|
361
|
+
|
362
|
+
return s(tag, process(recv), op, *(args.map{|a| process(a)}))
|
363
|
+
end
|
364
|
+
|
365
|
+
def process_iter(exp)
|
366
|
+
tag, recv, iter_args, body = exp
|
367
|
+
new_body = push_and_process(body)
|
368
|
+
return s(tag, process(recv), process(iter_args), new_body)
|
369
|
+
end
|
370
|
+
|
371
|
+
def push_and_process(exp)
|
372
|
+
obj_id = exp.object_id
|
373
|
+
@elem_stack.push(obj_id)
|
374
|
+
rv = process(exp)
|
375
|
+
raise Bud::Error unless @elem_stack.pop == obj_id
|
376
|
+
return rv
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Rewrite references to lattice identifiers that appear in rule bodies. A
|
381
|
+
# reference to a lattice identifier returns the associated lattice wrapper. When
|
382
|
+
# the identifier appears at the top-level of the rule RHS, that is fine (since
|
383
|
+
# we want the wrapper to do wiring). But for references that appear inside rule
|
384
|
+
# bodies, we want to instead fetch the current value associated with the lattice
|
385
|
+
# wrapper.
|
386
|
+
class LatticeRefRewriter < SexpProcessor
|
387
|
+
def initialize(bud_instance)
|
388
|
+
super()
|
389
|
+
self.require_empty = false
|
390
|
+
self.expected = Sexp
|
391
|
+
@bud_instance = bud_instance
|
392
|
+
@elem_stack = []
|
393
|
+
end
|
394
|
+
|
395
|
+
def process_iter(exp)
|
396
|
+
tag, recv, iter_args, body = exp
|
397
|
+
new_body = push_and_process(body)
|
398
|
+
return s(tag, process(recv), process(iter_args), new_body)
|
399
|
+
end
|
400
|
+
|
401
|
+
def process_array(exp)
|
402
|
+
new_body = exp.sexp_body.map {|t| push_and_process(t)}
|
403
|
+
return s(:array, *new_body)
|
404
|
+
end
|
405
|
+
|
406
|
+
def process_hash(exp)
|
407
|
+
new_body = exp.sexp_body.map {|t| push_and_process(t)}
|
408
|
+
return s(:hash, *new_body)
|
409
|
+
end
|
410
|
+
|
411
|
+
def process_call(exp)
|
412
|
+
tag, recv, op, *args = exp
|
413
|
+
|
414
|
+
if recv.nil? and args.empty? and is_lattice?(op) and @elem_stack.size > 0
|
415
|
+
return s(:call, exp, :current_value)
|
416
|
+
else
|
417
|
+
return s(tag, process(recv), op, *(args.map{|a| process(a)}))
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def push_and_process(exp)
|
422
|
+
obj_id = exp.object_id
|
423
|
+
@elem_stack.push(obj_id)
|
424
|
+
rv = process(exp)
|
425
|
+
raise Bud::Error unless @elem_stack.pop == obj_id
|
426
|
+
return rv
|
427
|
+
end
|
428
|
+
|
429
|
+
def is_lattice?(op)
|
430
|
+
@bud_instance.lattices.has_key? op.to_sym
|
263
431
|
end
|
264
432
|
end
|
265
433
|
|
@@ -278,32 +446,43 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
|
|
278
446
|
# iter vars
|
279
447
|
def process_iter(exp)
|
280
448
|
if exp[1] and exp[1][0] == :call
|
449
|
+
return exp unless exp[2]
|
281
450
|
gather_collection_names(exp[1])
|
451
|
+
meth_name = exp[1][2]
|
282
452
|
|
283
453
|
# now find iter vars and match up
|
284
|
-
if exp[2]
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
454
|
+
if exp[2][0] == :args and @collnames.size == 1 # single-table iter
|
455
|
+
if @iterhash[exp[2][1]]
|
456
|
+
raise Bud::CompileError, "redefinition of block variable \"#{exp[2][1]}\" not allowed"
|
457
|
+
end
|
458
|
+
|
459
|
+
# XXX: The BudChannel#payloads method assigns the correct schema to
|
460
|
+
# tuples that pass through it (i.e., it omits the location specifier);
|
461
|
+
# hence we don't want to apply the location rewrite to the code block
|
462
|
+
# that is passed to payloads(). This is a dirty hack.
|
463
|
+
unless meth_name == :payloads
|
464
|
+
@iterhash[exp[2][1]] = @collnames[0]
|
465
|
+
end
|
466
|
+
elsif exp[2][0] == :args and not @collnames.empty? # join iter with lefts/rights
|
467
|
+
case meth_name
|
289
468
|
when :lefts
|
290
469
|
@iterhash[exp[2][1]] = @collnames[0]
|
291
470
|
when :rights
|
292
471
|
@iterhash[exp[2][1]] = @collnames[1]
|
293
|
-
|
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
|
472
|
+
when :reduce
|
299
473
|
unless @collnames.length == 1
|
300
|
-
raise Bud::
|
474
|
+
raise Bud::CompileError, "reduce should only have one associated collection, but has #{@collnames.inspect}"
|
301
475
|
end
|
302
|
-
@iterhash[exp[2][1]
|
303
|
-
else
|
304
|
-
|
305
|
-
|
306
|
-
|
476
|
+
@iterhash[exp[2][1]] = @collnames[0]
|
477
|
+
else
|
478
|
+
# join
|
479
|
+
if @iterhash[exp[2][1]]
|
480
|
+
raise Bud::CompileError, "redefinition of block variable \"#{exp[2][1]}\" not allowed"
|
481
|
+
end
|
482
|
+
|
483
|
+
@collnames.each_with_index do |c,i|
|
484
|
+
next unless exp[2][i+1]
|
485
|
+
@iterhash[exp[2][i+1]] = c
|
307
486
|
end
|
308
487
|
end
|
309
488
|
end
|
@@ -313,36 +492,43 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
|
|
313
492
|
end
|
314
493
|
|
315
494
|
def gather_collection_names(exp)
|
316
|
-
|
495
|
+
# We expect a reference to a collection name to look like a function call
|
496
|
+
# (nil receiver) with no arguments.
|
497
|
+
if exp.sexp_type == :call and exp[1].nil? and exp.length == 3
|
317
498
|
@collnames << exp[2]
|
318
|
-
elsif exp
|
319
|
-
|
499
|
+
elsif exp.sexp_type == :call and exp[2] == :rename
|
500
|
+
namelit = exp[3]
|
320
501
|
@collnames << namelit[1]
|
502
|
+
elsif exp.sexp_type == :call and [:group, :argagg].include?(exp[2])
|
503
|
+
# For grouping and argagg expressions, only look at the receiver (the
|
504
|
+
# collection we're grouping on); otherwise, we might mistakenly think some
|
505
|
+
# of the arguments to the grouping operation are collection names.
|
506
|
+
gather_collection_names(exp[1])
|
321
507
|
else
|
322
|
-
exp.each { |e| gather_collection_names(e) if e
|
508
|
+
exp.each { |e| gather_collection_names(e) if e.class <= Sexp }
|
323
509
|
end
|
324
510
|
end
|
325
511
|
|
326
512
|
def process_call(exp)
|
327
|
-
call, recv, op, args = exp
|
513
|
+
call, recv, op, *args = exp
|
328
514
|
|
329
|
-
if recv
|
515
|
+
if recv.class == Sexp and recv.sexp_type == :lvar and @iterhash[recv[1]]
|
330
516
|
if @bud_instance.respond_to?(@iterhash[recv[1]])
|
331
517
|
if @bud_instance.send(@iterhash[recv[1]]).class <= Bud::BudCollection
|
332
518
|
cols = @bud_instance.send(@iterhash[recv[1]]).cols
|
333
519
|
if op != :[] and @bud_instance.send(@iterhash[recv[1]]).respond_to?(op)
|
334
|
-
# if the op is an attribute name in the schema,
|
335
|
-
|
336
|
-
unless
|
520
|
+
# if the op is an attribute name in the schema, col_idx is its index
|
521
|
+
col_idx = cols.index(op) unless cols.nil?
|
522
|
+
unless col_idx.nil?
|
337
523
|
op = :[]
|
338
|
-
args = s(:
|
524
|
+
args = [s(:lit, col_idx)]
|
339
525
|
end
|
340
526
|
end
|
341
527
|
end
|
342
|
-
return s(call, recv, op, args)
|
528
|
+
return s(call, recv, op, *args)
|
343
529
|
end
|
344
530
|
end
|
345
|
-
return s(call, process(recv), op, process(
|
531
|
+
return s(call, process(recv), op, *(args.map{|a| process(a)}))
|
346
532
|
end
|
347
533
|
end
|
348
534
|
|
@@ -365,52 +551,41 @@ class TempExpander < SexpProcessor # :nodoc: all
|
|
365
551
|
end
|
366
552
|
|
367
553
|
def process_defn(exp)
|
368
|
-
tag, name, args,
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
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)
|
554
|
+
tag, name, args, *body = exp
|
555
|
+
return exp unless name.to_s =~ /^__bloom__.+/
|
556
|
+
|
557
|
+
body.each_with_index do |n,i|
|
558
|
+
# temp declarations are misparsed if the RHS contains certain constructs
|
559
|
+
# (e.g., group, "do |f| ... end" rather than "{|f| ... }"). Rewrite to
|
560
|
+
# correct the misparsing.
|
561
|
+
if n.sexp_type == :iter
|
562
|
+
iter_body = n.sexp_body
|
563
|
+
new_n = fix_temp_decl(iter_body)
|
564
|
+
unless new_n.nil?
|
565
|
+
body[i] = n = new_n
|
393
566
|
@did_work = true
|
394
567
|
end
|
395
568
|
end
|
569
|
+
|
570
|
+
_, recv, meth, meth_args = n
|
571
|
+
if meth == KEYWORD and recv.nil?
|
572
|
+
body[i] = rewrite_me(n)
|
573
|
+
@did_work = true
|
574
|
+
end
|
396
575
|
end
|
397
|
-
s(tag, name, args,
|
576
|
+
s(tag, name, args, *body)
|
398
577
|
end
|
399
578
|
|
400
579
|
private
|
401
580
|
def fix_temp_decl(iter_body)
|
402
581
|
if iter_body.first.sexp_type == :call
|
403
582
|
call_node = iter_body.first
|
583
|
+
_, recv, meth, *meth_args = call_node
|
404
584
|
|
405
|
-
_, recv, meth, meth_args = call_node
|
406
585
|
if meth == KEYWORD and recv.nil?
|
407
|
-
_, lhs, op, rhs = meth_args.
|
408
|
-
|
409
|
-
|
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)
|
586
|
+
_, lhs, op, rhs = meth_args.first
|
587
|
+
new_rhs = s(:iter, rhs, *(iter_body[1..-1]))
|
588
|
+
meth_args.first[3] = new_rhs
|
414
589
|
return call_node
|
415
590
|
end
|
416
591
|
end
|
@@ -418,18 +593,18 @@ class TempExpander < SexpProcessor # :nodoc: all
|
|
418
593
|
end
|
419
594
|
|
420
595
|
def rewrite_me(exp)
|
421
|
-
_, recv, meth, args = exp
|
596
|
+
_, recv, meth, *args = exp
|
422
597
|
|
423
|
-
raise Bud::CompileError unless recv
|
424
|
-
nest_call = args.
|
598
|
+
raise Bud::CompileError unless recv.nil?
|
599
|
+
nest_call = args.first
|
425
600
|
raise Bud::CompileError unless nest_call.sexp_type == :call
|
426
601
|
|
427
|
-
nest_recv, nest_op, nest_args = nest_call.sexp_body
|
602
|
+
nest_recv, nest_op, *nest_args = nest_call.sexp_body
|
428
603
|
raise Bud::CompileError unless nest_recv.sexp_type == :lit
|
429
604
|
|
430
605
|
tmp_name = nest_recv.sexp_body.first
|
431
606
|
@tmp_tables << tmp_name
|
432
|
-
new_recv = s(:call, nil, tmp_name
|
433
|
-
return s(:call, new_recv, nest_op, nest_args)
|
607
|
+
new_recv = s(:call, nil, tmp_name)
|
608
|
+
return s(:call, new_recv, nest_op, *nest_args)
|
434
609
|
end
|
435
610
|
end
|