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.
- checksums.yaml +7 -0
- data/History.txt +106 -0
- data/README.md +6 -4
- data/Rakefile +91 -0
- data/bin/budlabel +63 -0
- data/bin/budplot +18 -8
- data/bin/budtimelines +2 -2
- data/bin/budvis +7 -1
- data/docs/README.md +8 -17
- data/docs/cheat.md +112 -13
- data/docs/getstarted.md +97 -84
- data/docs/operational.md +3 -3
- data/examples/basics/paths.rb +2 -2
- data/examples/chat/README.md +2 -0
- data/examples/chat/chat.rb +3 -2
- data/examples/chat/chat_protocol.rb +2 -2
- data/examples/chat/chat_server.rb +3 -2
- data/lib/bud.rb +229 -114
- data/lib/bud/aggs.rb +20 -4
- data/lib/bud/bud_meta.rb +83 -73
- data/lib/bud/collections.rb +306 -120
- data/lib/bud/depanalysis.rb +3 -4
- data/lib/bud/executor/README.rescan +2 -1
- data/lib/bud/executor/elements.rb +96 -95
- data/lib/bud/executor/group.rb +35 -32
- data/lib/bud/executor/join.rb +164 -183
- data/lib/bud/graphs.rb +3 -3
- 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 +595 -0
- data/lib/bud/lattice-lib.rb +422 -0
- data/lib/bud/monkeypatch.rb +68 -32
- data/lib/bud/rebl.rb +28 -10
- data/lib/bud/rewrite.rb +361 -152
- data/lib/bud/server.rb +16 -8
- data/lib/bud/source.rb +21 -18
- data/lib/bud/state.rb +93 -4
- data/lib/bud/storage/zookeeper.rb +45 -33
- data/lib/bud/version.rb +3 -0
- data/lib/bud/viz.rb +10 -12
- data/lib/bud/viz_util.rb +8 -3
- metadata +107 -108
@@ -0,0 +1,422 @@
|
|
1
|
+
require 'bud/lattice-core'
|
2
|
+
|
3
|
+
# Float::INFINITY only defined in MRI 1.9.2+
|
4
|
+
unless defined? Float::INFINITY
|
5
|
+
Float::INFINITY = 1.0/0.0
|
6
|
+
end
|
7
|
+
|
8
|
+
class Bud::MaxLattice < Bud::Lattice
|
9
|
+
wrapper_name :lmax
|
10
|
+
|
11
|
+
def initialize(i=-Float::INFINITY)
|
12
|
+
reject_input(i) unless i.class <= Comparable
|
13
|
+
@v = i
|
14
|
+
end
|
15
|
+
|
16
|
+
def merge(i)
|
17
|
+
i.reveal > @v ? i : self
|
18
|
+
end
|
19
|
+
|
20
|
+
morph :gt do |k|
|
21
|
+
Bud::BoolLattice.new(!!(@v > k))
|
22
|
+
end
|
23
|
+
|
24
|
+
morph :gt_eq do |k|
|
25
|
+
Bud::BoolLattice.new(!!(@v >= k))
|
26
|
+
end
|
27
|
+
|
28
|
+
# XXX: support MaxLattice input?
|
29
|
+
morph :+ do |i|
|
30
|
+
# NB: since bottom of lmax is negative infinity, + is a no-op
|
31
|
+
reject_input(i, "+") unless i.class <= Numeric
|
32
|
+
self.class.new(@v + i)
|
33
|
+
end
|
34
|
+
|
35
|
+
morph :min_of do |i|
|
36
|
+
reject_input(i, "min_of") unless i.class <= Numeric
|
37
|
+
i < @v ? self.class.new(i) : self
|
38
|
+
end
|
39
|
+
|
40
|
+
def lt_eq(k)
|
41
|
+
Bud::BoolLattice.new(!!(@v <= k))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Bud::MinLattice < Bud::Lattice
|
46
|
+
wrapper_name :lmin
|
47
|
+
|
48
|
+
def initialize(i=Float::INFINITY)
|
49
|
+
reject_input(i) unless i.class <= Comparable
|
50
|
+
@v = i
|
51
|
+
end
|
52
|
+
|
53
|
+
def merge(i)
|
54
|
+
i.reveal < @v ? i : self
|
55
|
+
end
|
56
|
+
|
57
|
+
morph :lt do |k|
|
58
|
+
Bud::BoolLattice.new(!!(@v < k))
|
59
|
+
end
|
60
|
+
|
61
|
+
# XXX: support MinLattice input
|
62
|
+
morph :+ do |i|
|
63
|
+
# Since bottom of lmin is infinity, + is a no-op
|
64
|
+
reject_input(i, "+") unless i.class <= Numeric
|
65
|
+
self.class.new(@v + i)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# XXX: consider creating two fixed ("interned") values for true and false.
|
70
|
+
class Bud::BoolLattice < Bud::Lattice
|
71
|
+
wrapper_name :lbool
|
72
|
+
|
73
|
+
def initialize(i=false)
|
74
|
+
reject_input(i) unless [true, false].include? i
|
75
|
+
@v = i
|
76
|
+
end
|
77
|
+
|
78
|
+
def merge(i)
|
79
|
+
self.class.new(@v || i.reveal)
|
80
|
+
end
|
81
|
+
|
82
|
+
# XXX: ugly syntax
|
83
|
+
morph :when_true do |&blk|
|
84
|
+
blk.call if @v
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class Bud::MapLattice < Bud::Lattice
|
89
|
+
wrapper_name :lmap
|
90
|
+
|
91
|
+
def initialize(i={})
|
92
|
+
reject_input(i) unless i.class == Hash
|
93
|
+
i.each_pair do |k,val|
|
94
|
+
reject_input(i) if k.class <= Bud::Lattice
|
95
|
+
reject_input(i) unless val.class <= Bud::Lattice
|
96
|
+
end
|
97
|
+
@v = i
|
98
|
+
end
|
99
|
+
|
100
|
+
def merge(i)
|
101
|
+
rv = @v.merge(i.reveal) do |k, lhs_v, rhs_v|
|
102
|
+
lhs_v.merge(rhs_v)
|
103
|
+
end
|
104
|
+
wrap_unsafe(rv)
|
105
|
+
end
|
106
|
+
|
107
|
+
def inspect
|
108
|
+
"<#{self.class.wrapper}: #{@v.inspect}>"
|
109
|
+
end
|
110
|
+
|
111
|
+
# XXX: If the key is not in the map, we would like to return some generic
|
112
|
+
# "bottom" value that is shared by all lattice types. Unfortunately, such a
|
113
|
+
# value does not exist, so we need the caller to tell us which class to use as
|
114
|
+
# an optional second argument (if omitted, fetching a non-existent key yields
|
115
|
+
# a runtime exception). Another alternative would be to specify the type of
|
116
|
+
# the map's values when the lmap is declared, but that hinders code reuse.
|
117
|
+
morph :at do |k, *args|
|
118
|
+
if @v.has_key? k
|
119
|
+
@v[k]
|
120
|
+
else
|
121
|
+
if args.empty?
|
122
|
+
raise Bud::Error, "missing key for lmap#at(#{k}) but no bottom type given"
|
123
|
+
end
|
124
|
+
args.first.new
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
morph :filter do
|
129
|
+
rv = {}
|
130
|
+
@v.each_pair do |k, val|
|
131
|
+
unless val.class <= Bud::BoolLattice
|
132
|
+
raise Bud::Error, "filter invoked on non-boolean map value: #{val}"
|
133
|
+
end
|
134
|
+
rv[k] = val if val.reveal == true
|
135
|
+
end
|
136
|
+
wrap_unsafe(rv)
|
137
|
+
end
|
138
|
+
|
139
|
+
morph :apply_morph do |sym, *args|
|
140
|
+
unless Bud::Lattice.global_morphs.include? sym
|
141
|
+
raise Bud::Error, "apply_morph called with non-morphism: #{sym}"
|
142
|
+
end
|
143
|
+
do_apply(sym, args)
|
144
|
+
end
|
145
|
+
|
146
|
+
# NB: "apply" can be used with both monotone functions and morphisms. We also
|
147
|
+
# provide apply_morph, which is slightly faster when theprogrammer knows they
|
148
|
+
# are applying a morphism.
|
149
|
+
monotone :apply do |sym, *args|
|
150
|
+
unless Bud::Lattice.global_mfuncs.include?(sym) ||
|
151
|
+
Bud::Lattice.global_morphs.include?(sym)
|
152
|
+
raise Bud::Error, "apply called with non-monotone function: #{sym}"
|
153
|
+
end
|
154
|
+
do_apply(sym, args)
|
155
|
+
end
|
156
|
+
|
157
|
+
def do_apply(sym, args)
|
158
|
+
rv = {}
|
159
|
+
@v.each_pair do |k, val|
|
160
|
+
res = val.send(sym, *args)
|
161
|
+
raise Bud::Error unless res.kind_of? Bud::Lattice
|
162
|
+
rv[k] = res
|
163
|
+
end
|
164
|
+
wrap_unsafe(rv)
|
165
|
+
end
|
166
|
+
|
167
|
+
morph :key? do |k|
|
168
|
+
Bud::BoolLattice.new(@v.has_key? k)
|
169
|
+
end
|
170
|
+
|
171
|
+
morph :key_set do
|
172
|
+
Bud::SetLattice.new(@v.keys)
|
173
|
+
end
|
174
|
+
|
175
|
+
monotone :size do
|
176
|
+
Bud::MaxLattice.new(@v.size)
|
177
|
+
end
|
178
|
+
|
179
|
+
morph :intersect do |i|
|
180
|
+
i_tbl = i.reveal
|
181
|
+
# Scan the smaller map, probe the larger one
|
182
|
+
scan, probe = (@v.size < i_tbl.size ? [@v, i_tbl] : [i_tbl, @v])
|
183
|
+
rv = {}
|
184
|
+
scan.each do |k,val|
|
185
|
+
rv[k] = val.merge(probe[k]) if probe.has_key? k
|
186
|
+
end
|
187
|
+
wrap_unsafe(rv)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Produce a Bloom collection (array of tuples) from this lmap, optionally
|
191
|
+
# applying a user-provided code block to each (k,v) pair in turn. Note that
|
192
|
+
# this is slightly different from how projection over an lmap would work: we
|
193
|
+
# return an array, whereas projection would return an lmap.
|
194
|
+
morph :to_collection do |&blk|
|
195
|
+
@v.map(&blk)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Return true if this map is strictly smaller than or equal to the given
|
199
|
+
# map. "x" is strictly smaller than or equal to "y" if:
|
200
|
+
# (a) every key in "x" also appears in "y"
|
201
|
+
# (b) for every key k in "x", x[k] <= y[k]
|
202
|
+
#
|
203
|
+
# NB: For this to be a morphism, we require that (a) "self" is deflationary
|
204
|
+
# (or fixed) (b) the input lattice value is inflationary (or fixed). We
|
205
|
+
# currently don't have a way to express (a) in the type system.
|
206
|
+
def lt_eq(i)
|
207
|
+
reject_input(i, "lt_eq") unless i.class <= self.class
|
208
|
+
|
209
|
+
@v.each do |k, v|
|
210
|
+
unless i.key?(k).reveal == true
|
211
|
+
return Bud::BoolLattice.new(false)
|
212
|
+
end
|
213
|
+
unless v.lt_eq(i.at(k).reveal).reveal == true
|
214
|
+
return Bud::BoolLattice.new(false)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
return Bud::BoolLattice.new(true)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# A set lattice contains zero or more primitive (non-lattice) values.
|
223
|
+
class Bud::SetLattice < Bud::Lattice
|
224
|
+
wrapper_name :lset
|
225
|
+
|
226
|
+
def initialize(i=Set.new)
|
227
|
+
reject_input(i) unless i.kind_of? Enumerable
|
228
|
+
reject_input(i) if i.any? {|e| e.kind_of? Bud::Lattice}
|
229
|
+
|
230
|
+
i = Set.new(i) unless i.kind_of? Set
|
231
|
+
@v = i
|
232
|
+
end
|
233
|
+
|
234
|
+
def merge(i)
|
235
|
+
wrap_unsafe(@v | i.reveal)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Override default "inspect" implementation to produce slightly nicer output
|
239
|
+
def inspect
|
240
|
+
"<#{self.class.wrapper}: #{reveal.to_a.sort.inspect}>"
|
241
|
+
end
|
242
|
+
|
243
|
+
morph :intersect do |i|
|
244
|
+
wrap_unsafe(@v & i.reveal)
|
245
|
+
end
|
246
|
+
|
247
|
+
morph :contains? do |i|
|
248
|
+
Bud::BoolLattice.new(@v.member? i)
|
249
|
+
end
|
250
|
+
|
251
|
+
monotone :group_count do |key_cols|
|
252
|
+
# Assume key_cols for now gives indices
|
253
|
+
rv = Hash.new(Bud::MaxLattice.new(0))
|
254
|
+
@v.each do |t|
|
255
|
+
unless t.class == Array
|
256
|
+
raise Bud::TypeError, "group_count only works if lset elements are type Array"
|
257
|
+
end
|
258
|
+
|
259
|
+
key = []
|
260
|
+
key_cols.each do |ind|
|
261
|
+
if ind >= t.length
|
262
|
+
raise Bud::Error, "lset element in group_count does not have column index #{ind}"
|
263
|
+
end
|
264
|
+
key << t[ind]
|
265
|
+
end
|
266
|
+
rv[key] += 1
|
267
|
+
end
|
268
|
+
Bud::MapLattice.new(rv)
|
269
|
+
end
|
270
|
+
|
271
|
+
morph :pro do |&blk|
|
272
|
+
# We don't use Set#map, since it returns an Array (ugh).
|
273
|
+
rv = Set.new
|
274
|
+
@v.each do |t|
|
275
|
+
val = blk.call(t)
|
276
|
+
rv << val unless val.nil?
|
277
|
+
end
|
278
|
+
wrap_unsafe(rv)
|
279
|
+
end
|
280
|
+
|
281
|
+
monotone :size do
|
282
|
+
Bud::MaxLattice.new(@v.size)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Assuming that the elements of this set are Structs (tuples with named field
|
286
|
+
# accessors), this performs an equijoin between the current lattice and
|
287
|
+
# i. `preds` is a hash of join predicates; each k/v pair in the hash is an
|
288
|
+
# equality predicate that self_tup[k] == i_tup[v]. The return value is the
|
289
|
+
# result of passing pairs of join tuples to the user-supplied code block
|
290
|
+
# (values for which the code block returns nil are omitted from the
|
291
|
+
# result). Note that if no predicates are passed, this computes the Cartesian
|
292
|
+
# product (in which case the input elements do not need to be Structs).
|
293
|
+
morph :eqjoin do |*args, &blk|
|
294
|
+
# Need to emulate default block arguments for MRI 1.8
|
295
|
+
i, preds = args
|
296
|
+
preds ||= {}
|
297
|
+
rv = Set.new
|
298
|
+
@v.each do |a|
|
299
|
+
i.probe(a, preds).each do |b|
|
300
|
+
if blk.nil?
|
301
|
+
rv << [a,b]
|
302
|
+
else
|
303
|
+
val = blk.call(a, b)
|
304
|
+
rv << val unless val.nil?
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
wrap_unsafe(rv)
|
309
|
+
end
|
310
|
+
|
311
|
+
# Assuming that this set contains Structs, this method takes a value "val" and
|
312
|
+
# a hash of predicates "preds". It returns all the structs t where val[k] =
|
313
|
+
# t[v] for all k,v in preds; an empty array is returned if no matches found.
|
314
|
+
def probe(val, preds)
|
315
|
+
return @v if preds.empty?
|
316
|
+
|
317
|
+
probe_val = schema_fetch(val, preds.keys)
|
318
|
+
build_index(preds.values)
|
319
|
+
index = @join_indexes[preds.values]
|
320
|
+
return index[probe_val] || []
|
321
|
+
end
|
322
|
+
|
323
|
+
private
|
324
|
+
def schema_fetch(val, cols)
|
325
|
+
cols.map {|s| val[s]}
|
326
|
+
end
|
327
|
+
|
328
|
+
def build_index(cols)
|
329
|
+
@join_indexes ||= {}
|
330
|
+
return @join_indexes[cols] if @join_indexes.has_key? cols
|
331
|
+
|
332
|
+
idx = {}
|
333
|
+
@v.each do |val|
|
334
|
+
index_val = schema_fetch(val, cols)
|
335
|
+
idx[index_val] ||= []
|
336
|
+
idx[index_val] << val
|
337
|
+
end
|
338
|
+
|
339
|
+
@join_indexes[cols] = idx
|
340
|
+
return idx
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# A set that admits only non-negative numbers. This allows "sum" to be a
|
345
|
+
# monotone function. Note that this does duplicate elimination on its input, so
|
346
|
+
# it actually computes "SUM(DISTINCT ...)" in SQL.
|
347
|
+
#
|
348
|
+
# XXX: for methods that take a user-provided code block, we need to ensure that
|
349
|
+
# the set continues to contain only positive numbers.
|
350
|
+
class Bud::PositiveSetLattice < Bud::SetLattice
|
351
|
+
wrapper_name :lpset
|
352
|
+
|
353
|
+
def initialize(i=[])
|
354
|
+
super
|
355
|
+
@v.each do |n|
|
356
|
+
reject_input(i) unless n.class <= Numeric
|
357
|
+
reject_input(i) if n < 0
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
monotone :pos_sum do
|
362
|
+
@sum = @v.reduce(Bud::MaxLattice.new(0), :+) if @sum.nil?
|
363
|
+
@sum
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# XXX: Should this be just syntax sugar for a map lattice instead?
|
368
|
+
class Bud::BagLattice < Bud::Lattice
|
369
|
+
wrapper_name :lbag
|
370
|
+
|
371
|
+
def initialize(i={})
|
372
|
+
reject_input(i) unless i.class <= Hash
|
373
|
+
i.each do |k, mult|
|
374
|
+
reject_input(i) if k.class <= Bud::Lattice
|
375
|
+
reject_input(i) unless (mult.class <= Integer && mult > 0)
|
376
|
+
end
|
377
|
+
@v = i
|
378
|
+
end
|
379
|
+
|
380
|
+
# Note that for merge to be idempotent, we need to use the traditional
|
381
|
+
# definition of multiset union (per-element max of multiplicities, rather than
|
382
|
+
# sum of multiplicities).
|
383
|
+
def merge(i)
|
384
|
+
rv = @v.merge(i.reveal) do |k, lhs_v, rhs_v|
|
385
|
+
[lhs_v, rhs_v].max
|
386
|
+
end
|
387
|
+
wrap_unsafe(rv)
|
388
|
+
end
|
389
|
+
|
390
|
+
morph :intersect do |i|
|
391
|
+
i_tbl = i.reveal
|
392
|
+
# Scan the smaller one, probe the larger one
|
393
|
+
scan, probe = (@v.size < i_tbl.size ? [@v, i_tbl] : [i_tbl, @v])
|
394
|
+
rv = {}
|
395
|
+
scan.each do |k,val|
|
396
|
+
rv[k] = [val, probe[k]].min if probe.has_key? k
|
397
|
+
end
|
398
|
+
wrap_unsafe(rv)
|
399
|
+
end
|
400
|
+
|
401
|
+
morph :multiplicity do |k|
|
402
|
+
rv = @v[k]
|
403
|
+
rv ||= 0
|
404
|
+
Bud::MaxLattice.new(rv)
|
405
|
+
end
|
406
|
+
|
407
|
+
morph :+ do |i|
|
408
|
+
rv = @v.merge(i.reveal) do |k, lhs_v, rhs_v|
|
409
|
+
lhs_v + rhs_v
|
410
|
+
end
|
411
|
+
self.class.new(rv)
|
412
|
+
end
|
413
|
+
|
414
|
+
morph :contains? do |i|
|
415
|
+
Bud::BoolLattice.new(@v.has_key? i)
|
416
|
+
end
|
417
|
+
|
418
|
+
monotone :size do
|
419
|
+
@size = @v.values.reduce(Bud::MaxLattice.new(0), :+) if @size.nil?
|
420
|
+
@size
|
421
|
+
end
|
422
|
+
end
|
data/lib/bud/monkeypatch.rb
CHANGED
@@ -10,42 +10,63 @@ class Class
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
$struct_classes = {}
|
14
|
+
$struct_lock = Mutex.new
|
15
|
+
|
16
|
+
# FIXME: Should likely override #hash and #eql? as well.
|
17
|
+
class Bud::TupleStruct < Struct
|
18
|
+
include Comparable
|
19
|
+
|
20
|
+
def self.new_struct(cols)
|
21
|
+
$struct_lock.synchronize {
|
22
|
+
($struct_classes[cols] ||= Bud::TupleStruct.new(*cols))
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
# XXX: This only considers two TupleStruct instances to be equal if they have
|
27
|
+
# the same schema (column names) AND the same contents; unclear if structural
|
28
|
+
# equality (consider only values, not column names) would be better.
|
15
29
|
def <=>(o)
|
16
30
|
if o.class == self.class
|
17
31
|
self.each_with_index do |e, i|
|
18
|
-
|
19
|
-
|
32
|
+
other = o[i]
|
33
|
+
next if e == other
|
34
|
+
return e <=> other
|
20
35
|
end
|
21
36
|
return 0
|
22
37
|
elsif o.nil?
|
23
|
-
return
|
38
|
+
return nil
|
24
39
|
else
|
25
40
|
raise "Comparison (<=>) between #{o.class} and #{self.class} not implemented"
|
26
41
|
end
|
27
42
|
end
|
28
43
|
|
29
|
-
alias oldeq :==
|
30
44
|
def ==(o)
|
31
45
|
if o.class == self.class
|
32
|
-
return
|
46
|
+
return super
|
33
47
|
elsif o.class == Array
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
return false
|
38
|
-
end
|
39
|
-
end
|
40
|
-
return true
|
41
|
-
rescue StandardError
|
42
|
-
return false
|
48
|
+
return false if self.length != o.length
|
49
|
+
self.each_with_index do |el, i|
|
50
|
+
return false if el != o[i]
|
43
51
|
end
|
52
|
+
return true
|
44
53
|
end
|
45
54
|
false
|
46
55
|
end
|
47
56
|
|
48
|
-
def
|
57
|
+
def hash
|
58
|
+
self.values.hash
|
59
|
+
end
|
60
|
+
|
61
|
+
def eql?(o)
|
62
|
+
self == o
|
63
|
+
end
|
64
|
+
|
65
|
+
def +(o)
|
66
|
+
self.to_ary + o.to_ary
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_msgpack(out=nil)
|
49
70
|
self.to_a.to_msgpack(out)
|
50
71
|
end
|
51
72
|
|
@@ -54,17 +75,23 @@ class Struct
|
|
54
75
|
end
|
55
76
|
|
56
77
|
alias :to_s :inspect
|
78
|
+
alias :to_ary :to_a
|
57
79
|
end
|
58
80
|
|
59
81
|
# XXX: TEMPORARY/UGLY hack to ensure that arrays and structs compare. This can be
|
60
82
|
# removed once tests are rewritten.
|
61
83
|
class Array
|
62
|
-
alias :
|
84
|
+
alias :old_eq :==
|
85
|
+
alias :old_eql? :eql?
|
86
|
+
|
63
87
|
def ==(o)
|
64
|
-
if o.kind_of?
|
65
|
-
|
66
|
-
|
67
|
-
|
88
|
+
o = o.to_a if o.kind_of? Bud::TupleStruct
|
89
|
+
self.old_eq(o)
|
90
|
+
end
|
91
|
+
|
92
|
+
def eql?(o)
|
93
|
+
o = o.to_a if o.kind_of? Bud::TupleStruct
|
94
|
+
self.old_eql?(o)
|
68
95
|
end
|
69
96
|
end
|
70
97
|
|
@@ -125,7 +152,6 @@ class Module
|
|
125
152
|
@bud_import_tbl
|
126
153
|
end
|
127
154
|
|
128
|
-
|
129
155
|
# the block of Bloom collection declarations. one per module.
|
130
156
|
def state(&block)
|
131
157
|
meth_name = Module.make_state_meth_name(self)
|
@@ -138,8 +164,9 @@ class Module
|
|
138
164
|
define_method(meth_name, &block)
|
139
165
|
end
|
140
166
|
|
141
|
-
# bloom statements to be registered with Bud runtime. optional +block_name+
|
142
|
-
#
|
167
|
+
# bloom statements to be registered with Bud runtime. optional +block_name+
|
168
|
+
# assigns a name for the block; this is useful documentation, and also allows
|
169
|
+
# the block to be overridden in a child class.
|
143
170
|
def bloom(block_name=nil, &block)
|
144
171
|
# If no block name was specified, generate a unique name
|
145
172
|
if block_name.nil?
|
@@ -148,7 +175,7 @@ class Module
|
|
148
175
|
@block_id += 1
|
149
176
|
else
|
150
177
|
unless block_name.class <= Symbol
|
151
|
-
raise Bud::CompileError, "
|
178
|
+
raise Bud::CompileError, "block name must be a symbol: #{block_name}"
|
152
179
|
end
|
153
180
|
end
|
154
181
|
|
@@ -161,15 +188,24 @@ class Module
|
|
161
188
|
# module; this indicates a likely programmer error.
|
162
189
|
if instance_methods(false).include?(meth_name) ||
|
163
190
|
instance_methods(false).include?(meth_name.to_sym)
|
164
|
-
raise Bud::CompileError, "duplicate
|
191
|
+
raise Bud::CompileError, "duplicate block name: '#{block_name}' in #{self}"
|
165
192
|
end
|
166
193
|
ast = Source.read_block(caller[0]) # pass in caller's location via backtrace
|
194
|
+
|
167
195
|
# ast corresponds only to the statements of the block. Wrap it in a method
|
168
196
|
# definition for backward compatibility for now.
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
197
|
+
|
198
|
+
# If the block contained multiple statements, the AST will have a top-level
|
199
|
+
# :block node. Since ruby_parser ASTs for method definitions don't contain
|
200
|
+
# such a node, remove it.
|
201
|
+
if ast.nil?
|
202
|
+
ast = []
|
203
|
+
elsif ast.sexp_type == :block
|
204
|
+
ast = ast.sexp_body
|
205
|
+
else
|
206
|
+
ast = [ast]
|
207
|
+
end
|
208
|
+
ast = s(:defn, meth_name.to_sym, s(:args), *ast)
|
173
209
|
unless self.respond_to? :__bloom_asts__
|
174
210
|
def self.__bloom_asts__
|
175
211
|
@__bloom_asts__ ||= {}
|
@@ -180,11 +216,11 @@ class Module
|
|
180
216
|
define_method(meth_name.to_sym, &block)
|
181
217
|
end
|
182
218
|
|
183
|
-
private
|
184
219
|
# Return a string with a version of the class name appropriate for embedding
|
185
220
|
# into a method name. Annoyingly, if you define class X nested inside
|
186
221
|
# class/module Y, X's class name is the string "Y::X". We don't want to define
|
187
222
|
# method names with semicolons in them, so just return "X" instead.
|
223
|
+
private
|
188
224
|
def self.get_class_name(klass)
|
189
225
|
(klass.name.nil? or klass.name == "") \
|
190
226
|
? "Anon#{klass.object_id}" \
|