bud 0.9.6 → 0.9.7

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.
@@ -118,18 +118,39 @@ class Bud::MapLattice < Bud::Lattice
118
118
  if @v.has_key? k
119
119
  @v[k]
120
120
  else
121
- raise Bud::Error if args.empty?
121
+ if args.empty?
122
+ raise Bud::Error, "missing key for lmap#at(#{k}) but no bottom type given"
123
+ end
122
124
  args.first.new
123
125
  end
124
126
  end
125
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
+
126
139
  morph :apply_morph do |sym, *args|
127
- raise Bud::Error unless Bud::Lattice.global_morphs.include? sym
140
+ unless Bud::Lattice.global_morphs.include? sym
141
+ raise Bud::Error, "apply_morph called with non-morphism: #{sym}"
142
+ end
128
143
  do_apply(sym, args)
129
144
  end
130
145
 
131
- monotone :apply_monotone do |sym, *args|
132
- raise Bud::Error unless Bud::Lattice.global_mfuncs.include? sym
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
133
154
  do_apply(sym, args)
134
155
  end
135
156
 
@@ -223,19 +244,6 @@ class Bud::SetLattice < Bud::Lattice
223
244
  wrap_unsafe(@v & i.reveal)
224
245
  end
225
246
 
226
- morph :product do |i, &blk|
227
- rv = Set.new
228
- @v.each do |a|
229
- if blk.nil?
230
- t = i.pro {|b| [a,b]}
231
- else
232
- t = i.pro {|b| blk.call(a, b)}
233
- end
234
- rv.merge(t.reveal)
235
- end
236
- wrap_unsafe(rv)
237
- end
238
-
239
247
  morph :contains? do |i|
240
248
  Bud::BoolLattice.new(@v.member? i)
241
249
  end
@@ -254,44 +262,68 @@ class Bud::SetLattice < Bud::Lattice
254
262
  Bud::MaxLattice.new(@v.size)
255
263
  end
256
264
 
257
- # Assuming that this set contains tuples (arrays) as elements, this performs
258
- # an equijoin between the current lattice and i. The join predicate is
259
- # "self_t[lhs_idx] == i_t[rhs_idx]", for all tuples self_t and i_t in self and
260
- # i, respectively. The return value is the result of passing pairs of join
261
- # tuples to the user-supplied block.
262
- morph :eqjoin do |i, lhs_idx, rhs_idx, &blk|
265
+ # Assuming that the elements of this set are Structs (tuples with named field
266
+ # accessors), this performs an equijoin between the current lattice and
267
+ # i. `preds` is a hash of join predicates; each k/v pair in the hash is an
268
+ # equality predicate that self_tup[k] == i_tup[v]. The return value is the
269
+ # result of passing pairs of join tuples to the user-supplied code block
270
+ # (values for which the code block returns nil are omitted from the
271
+ # result). Note that if no predicates are passed, this computes the Cartesian
272
+ # product (in which case the input elements do not need to be Structs).
273
+ morph :eqjoin do |*args, &blk|
274
+ # Need to emulate default block arguments for MRI 1.8
275
+ i, preds = args
276
+ preds ||= {}
263
277
  rv = Set.new
264
278
  @v.each do |a|
265
- i.probe(rhs_idx, a[lhs_idx]).each do |b|
266
- rv << blk.call(a, b)
279
+ i.probe(a, preds).each do |b|
280
+ if blk.nil?
281
+ rv << [a,b]
282
+ else
283
+ val = blk.call(a, b)
284
+ rv << val unless val.nil?
285
+ end
267
286
  end
268
287
  end
269
288
  wrap_unsafe(rv)
270
289
  end
271
290
 
272
- # Assuming that this set contains tuples (arrays), this returns a list of
273
- # tuples (possibly empty) whose idx'th column has the value "v".
274
- # XXX: we assume probe(idx, v) will only be called for a single value of idx!
275
- def probe(idx, v)
276
- @ht ||= build_ht(idx)
277
- return @ht[v] || []
291
+ # Assuming that this set contains Structs, this method takes a value "val" and
292
+ # a hash of predicates "preds". It returns all the structs t where val[k] =
293
+ # t[v] for all k,v in preds; an empty array is returned if no matches found.
294
+ def probe(val, preds)
295
+ return @v if preds.empty?
296
+
297
+ probe_val = schema_fetch(val, preds.keys)
298
+ build_index(preds.values)
299
+ index = @join_indexes[preds.values]
300
+ return index[probe_val] || []
278
301
  end
279
302
 
280
303
  private
281
- def build_ht(idx)
282
- rv = {}
283
- @v.each do |i|
284
- field = i[idx]
285
- rv[field] ||= []
286
- rv[field] << i
304
+ def schema_fetch(val, cols)
305
+ cols.map {|s| val[s]}
306
+ end
307
+
308
+ def build_index(cols)
309
+ @join_indexes ||= {}
310
+ return @join_indexes[cols] if @join_indexes.has_key? cols
311
+
312
+ idx = {}
313
+ @v.each do |val|
314
+ index_val = schema_fetch(val, cols)
315
+ idx[index_val] ||= []
316
+ idx[index_val] << val
287
317
  end
288
- rv
318
+
319
+ @join_indexes[cols] = idx
320
+ return idx
289
321
  end
290
322
  end
291
323
 
292
- # A set that admits only non-negative numbers. This allows "sum" to be an
293
- # order-preserving map. Note that this does duplicate elimination on its input,
294
- # so it actually computes "SUM(DISTINCT ...)" in SQL.
324
+ # A set that admits only non-negative numbers. This allows "sum" to be a
325
+ # monotone function. Note that this does duplicate elimination on its input, so
326
+ # it actually computes "SUM(DISTINCT ...)" in SQL.
295
327
  #
296
328
  # XXX: for methods that take a user-provided code block, we need to ensure that
297
329
  # the set continues to contain only positive numbers.
@@ -10,33 +10,34 @@ class Class
10
10
  end
11
11
  end
12
12
 
13
- # FIXME: Use a subclass of Struct.
14
- # FIXME: Should likely override eql? as well
15
- class Struct
13
+ # FIXME: Should likely override #hash and #eql? as well.
14
+ class Bud::TupleStruct < Struct
15
+ include Comparable
16
+
16
17
  def <=>(o)
17
18
  if o.class == self.class
18
19
  self.each_with_index do |e, i|
19
- cmp = e <=> o[i]
20
- return cmp if cmp != 0
20
+ other = o[i]
21
+ next if e == other
22
+ return nil if e.nil?
23
+ return nil if other.nil?
24
+ return e <=> other
21
25
  end
22
26
  return 0
23
27
  elsif o.nil?
24
- return -1
28
+ return nil
25
29
  else
26
30
  raise "Comparison (<=>) between #{o.class} and #{self.class} not implemented"
27
31
  end
28
32
  end
29
33
 
30
- alias oldeq :==
31
34
  def ==(o)
32
35
  if o.class == self.class
33
- return oldeq(o)
36
+ return super
34
37
  elsif o.class == Array
35
38
  begin
36
39
  self.each_with_index do |el, i|
37
- if el != o[i]
38
- return false
39
- end
40
+ return false if el != o[i]
40
41
  end
41
42
  return true
42
43
  rescue StandardError
@@ -62,9 +63,7 @@ end
62
63
  class Array
63
64
  alias :oldeq :==
64
65
  def ==(o)
65
- if o.kind_of? Struct
66
- o = o.to_a
67
- end
66
+ o = o.to_a if o.kind_of? Bud::TupleStruct
68
67
  self.oldeq(o)
69
68
  end
70
69
  end
@@ -126,7 +125,6 @@ class Module
126
125
  @bud_import_tbl
127
126
  end
128
127
 
129
-
130
128
  # the block of Bloom collection declarations. one per module.
131
129
  def state(&block)
132
130
  meth_name = Module.make_state_meth_name(self)
@@ -139,8 +137,9 @@ class Module
139
137
  define_method(meth_name, &block)
140
138
  end
141
139
 
142
- # bloom statements to be registered with Bud runtime. optional +block_name+
143
- # allows for multiple bloom blocks per module and method overriding
140
+ # bloom statements to be registered with Bud runtime. optional +block_name+
141
+ # assigns a name for the block; this is useful documentation, and also allows
142
+ # the block to be overridden in a child class.
144
143
  def bloom(block_name=nil, &block)
145
144
  # If no block name was specified, generate a unique name
146
145
  if block_name.nil?
data/lib/bud/rewrite.rb CHANGED
@@ -8,7 +8,7 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
8
8
  MONOTONE_WHITELIST = [:==, :+, :<=, :-, :<, :>, :*, :~,
9
9
  :pairs, :matches, :combos, :flatten, :new,
10
10
  :lefts, :rights, :map, :flat_map, :pro, :merge,
11
- :cols, :key_cols, :val_cols, :payloads, :lambda,
11
+ :schema, :cols, :key_cols, :val_cols, :payloads, :lambda,
12
12
  :tabname, :current_value].to_set
13
13
 
14
14
  def initialize(seed, bud_instance)
@@ -366,7 +366,7 @@ class UnsafeFuncRewriter < SexpProcessor
366
366
  # We assume that unsafe funcs have a nil receiver (Bud instance is implicit
367
367
  # receiver).
368
368
  if recv.nil? and @elem_stack.size > 0
369
- unless is_safe_func(op) || is_lattice?(op)
369
+ unless is_safe_func(op) || is_collection_name?(op)
370
370
  @unsafe_func_called = true
371
371
  end
372
372
  end
@@ -388,8 +388,8 @@ class UnsafeFuncRewriter < SexpProcessor
388
388
  return rv
389
389
  end
390
390
 
391
- def is_lattice?(op)
392
- @bud_instance.lattices.has_key? op.to_sym
391
+ def is_collection_name?(op)
392
+ @bud_instance.tables.has_key?(op.to_sym) || @bud_instance.lattices.has_key?(op.to_sym)
393
393
  end
394
394
 
395
395
  def is_safe_func(op)
@@ -560,7 +560,7 @@ class TempExpander < SexpProcessor # :nodoc: all
560
560
  attr_reader :tmp_tables
561
561
  attr_accessor :did_work
562
562
 
563
- KEYWORD = :temp
563
+ TEMP_KEYWORD = :temp
564
564
 
565
565
  def initialize
566
566
  super()
@@ -588,8 +588,8 @@ class TempExpander < SexpProcessor # :nodoc: all
588
588
  end
589
589
 
590
590
  _, recv, meth, meth_args = n
591
- if meth == KEYWORD and recv.nil?
592
- body[i] = rewrite_me(n)
591
+ if meth == TEMP_KEYWORD and recv.nil?
592
+ body[i] = rewrite_temp(n)
593
593
  @did_work = true
594
594
  end
595
595
  end
@@ -602,7 +602,7 @@ class TempExpander < SexpProcessor # :nodoc: all
602
602
  call_node = iter_body.first
603
603
  _, recv, meth, *meth_args = call_node
604
604
 
605
- if meth == KEYWORD and recv.nil?
605
+ if meth == TEMP_KEYWORD and recv.nil?
606
606
  _, lhs, op, rhs = meth_args.first
607
607
  new_rhs = s(:iter, rhs, *(iter_body[1..-1]))
608
608
  meth_args.first[3] = new_rhs
@@ -612,7 +612,7 @@ class TempExpander < SexpProcessor # :nodoc: all
612
612
  return nil
613
613
  end
614
614
 
615
- def rewrite_me(exp)
615
+ def rewrite_temp(exp)
616
616
  _, recv, meth, *args = exp
617
617
 
618
618
  raise Bud::CompileError unless recv.nil?
@@ -620,7 +620,10 @@ class TempExpander < SexpProcessor # :nodoc: all
620
620
  raise Bud::CompileError unless nest_call.sexp_type == :call
621
621
 
622
622
  nest_recv, nest_op, *nest_args = nest_call.sexp_body
623
- raise Bud::CompileError unless nest_recv.sexp_type == :lit
623
+ unless nest_recv.sexp_type == :lit
624
+ recv_src = Ruby2Ruby.new.process(Marshal.load(Marshal.dump(nest_recv)))
625
+ raise Bud::CompileError, "argument to temp must be a symbol: #{recv_src}"
626
+ end
624
627
 
625
628
  tmp_name = nest_recv.sexp_body.first
626
629
  @tmp_tables << tmp_name
data/lib/bud/source.rb CHANGED
@@ -17,7 +17,9 @@ module Source
17
17
  lines = cache(filename, num)
18
18
  # Note: num is 1-based.
19
19
 
20
- parser = RubyParser.for_current_ruby
20
+ # for_current_ruby might object if the current Ruby version is not supported
21
+ # by RubyParser; bravely try to continue on regardless
22
+ parser = RubyParser.for_current_ruby rescue RubyParser.new
21
23
  stmt = "" # collection of lines that form one complete Ruby statement
22
24
  ast = nil
23
25
  lines[num .. -1].each do |l|
data/lib/bud.rb CHANGED
@@ -71,6 +71,7 @@ module Bud
71
71
  attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :app_tables, :lattices
72
72
  attr_reader :push_sources, :push_elems, :push_joins, :scanners, :merge_targets
73
73
  attr_reader :this_stratum, :this_rule, :rule_orig_src, :done_bootstrap
74
+ attr_reader :inside_tick
74
75
  attr_accessor :stratified_rules
75
76
  attr_accessor :metrics, :periodics
76
77
  attr_accessor :this_rule_context, :qualified_name
@@ -165,11 +166,11 @@ module Bud
165
166
  do_rewrite
166
167
  if toplevel == self
167
168
  # initialize per-stratum state
168
- num_strata = @stratified_rules.length
169
- @scanners = num_strata.times.map{{}}
170
- @push_sources = num_strata.times.map{{}}
171
- @push_joins = num_strata.times.map{[]}
172
- @merge_targets = num_strata.times.map{Set.new}
169
+ @num_strata = @stratified_rules.length
170
+ @scanners = @num_strata.times.map{{}}
171
+ @push_sources = @num_strata.times.map{{}}
172
+ @push_joins = @num_strata.times.map{[]}
173
+ @merge_targets = @num_strata.times.map{Set.new}
173
174
  end
174
175
  end
175
176
 
@@ -318,8 +319,6 @@ module Bud
318
319
  end
319
320
 
320
321
  def do_wiring
321
- @num_strata = @stratified_rules.length
322
-
323
322
  @stratified_rules.each_with_index { |rules, stratum| eval_rules(rules, stratum) }
324
323
 
325
324
  # Prepare list of tables that will be actively used at run time. First, all
@@ -367,6 +366,24 @@ module Bud
367
366
  end
368
367
  end
369
368
 
369
+ # We create "orphan" scanners for collections that don't appear on the RHS
370
+ # of any rules, but do appear on the LHS of at least one rule. These
371
+ # scanners aren't needed to compute the fixpoint, but they are used as part
372
+ # of rescan/invalidation (e.g., if an orphaned collection receives a manual
373
+ # deletion operation, we need to arrange for the collection to be
374
+ # re-filled).
375
+ @orphan_scanners = [] # Pairs of [scanner, stratum]
376
+ @app_tables.each do |t|
377
+ next unless t.class <= Bud::BudCollection # skip lattice wrappers
378
+ next if t.scanner_cnt > 0
379
+
380
+ stratum = collection_stratum(t.qualified_tabname.to_s)
381
+ # if the collection also doesn't appear on any LHSs, skip it
382
+ next if stratum.nil?
383
+ @orphan_scanners << [Bud::ScannerElement.new(t.tabname, self, t, t.schema),
384
+ stratum]
385
+ end
386
+
370
387
  # Sanity check
371
388
  @push_sorted_elems.each do |stratum_elems|
372
389
  stratum_elems.each {|se| se.check_wiring}
@@ -432,19 +449,17 @@ module Bud
432
449
  #
433
450
  # scanner[stratum].rescan_set = Similar to above.
434
451
  def prepare_invalidation_scheme
435
- num_strata = @push_sorted_elems.size
436
452
  if $BUD_SAFE
437
453
  @app_tables = @tables.values + @lattices.values # No collections excluded
438
454
 
439
455
  rescan = Set.new
440
456
  invalidate = @app_tables.select {|t| t.class <= BudScratch}.to_set
441
- num_strata.times do |stratum|
457
+ @num_strata.times do |stratum|
442
458
  @push_sorted_elems[stratum].each do |elem|
443
459
  invalidate << elem
444
460
  rescan << elem
445
461
  end
446
462
  end
447
- #prune_rescan_invalidate(rescan, invalidate)
448
463
  @default_rescan = rescan.to_a
449
464
  @default_invalidate = invalidate.to_a
450
465
  @reset_list = [] # Nothing to reset at end of tick. It'll be overwritten anyway
@@ -474,7 +489,7 @@ module Bud
474
489
  invalidate = @app_tables.select {|t| t.invalidate_at_tick}.to_set
475
490
  rescan = Set.new
476
491
 
477
- num_strata.times do |stratum|
492
+ @num_strata.times do |stratum|
478
493
  @push_sorted_elems[stratum].each do |elem|
479
494
  rescan << elem if elem.rescan_at_tick
480
495
 
@@ -496,29 +511,35 @@ module Bud
496
511
  puts "Unsafe targets: #{unsafe_targets.inspect}"
497
512
  end
498
513
 
499
- # Now compute for each table that is to be scanned, the set of dependent
500
- # tables and elements that will be invalidated if that table were to be
501
- # invalidated at run time.
514
+ # For each collection that is to be scanned, compute the set of dependent
515
+ # tables and elements that will need invalidation and/or rescan if that
516
+ # table were to be invalidated at runtime.
502
517
  dflt_rescan = rescan
503
518
  dflt_invalidate = invalidate
504
519
  to_reset = rescan + invalidate
505
- num_strata.times do |stratum|
506
- @scanners[stratum].each_value do |scanner|
507
- # If it is going to be always invalidated, it doesn't need further
508
- # examination
509
- next if dflt_rescan.member? scanner
510
-
511
- rescan = dflt_rescan + [scanner] # add scanner to scan set
512
- invalidate = dflt_invalidate.clone
513
- rescan_invalidate_tc(stratum, rescan, invalidate)
514
- prune_rescan_invalidate(rescan, invalidate)
515
- to_reset.merge(rescan)
516
- to_reset.merge(invalidate)
517
- # Give the diffs (from default) to scanner; these are elements that are
518
- # dependent on this scanner
519
- diffscan = (rescan - dflt_rescan).find_all {|elem| elem.class <= PushElement}
520
- scanner.invalidate_at_tick(diffscan, (invalidate - dflt_invalidate).to_a)
521
- end
520
+ each_scanner do |scanner, stratum|
521
+ # If it is going to be always invalidated, it doesn't need further
522
+ # examination. Lattice scanners also don't get invalidated.
523
+ next if dflt_rescan.member? scanner
524
+ next if scanner.class <= LatticeScanner
525
+
526
+ rescan = dflt_rescan.clone
527
+ invalidate = dflt_invalidate + [scanner.collection]
528
+ rescan_invalidate_tc(stratum, rescan, invalidate)
529
+ prune_rescan_invalidate(rescan, invalidate)
530
+
531
+ # Make sure we reset the rescan/invalidate flag for this scanner at
532
+ # end-of-tick, but we can remove the scanner from its own
533
+ # rescan_set/inval_set.
534
+ to_reset.merge(rescan)
535
+ to_reset.merge(invalidate)
536
+ rescan.delete(scanner)
537
+ invalidate.delete(scanner.collection)
538
+
539
+ # Give the diffs (from default) to scanner; these are elements that are
540
+ # dependent on this scanner
541
+ diffscan = (rescan - dflt_rescan).find_all {|elem| elem.class <= PushElement}
542
+ scanner.invalidate_at_tick(diffscan, (invalidate - dflt_invalidate).to_a)
522
543
  end
523
544
  @reset_list = to_reset.to_a
524
545
 
@@ -560,6 +581,12 @@ module Bud
560
581
 
561
582
  # Given rescan, invalidate sets, compute transitive closure
562
583
  def rescan_invalidate_tc(stratum, rescan, invalidate)
584
+ # XXX: hack. If there's nothing in the given stratum, don't do
585
+ # anything. This can arise if we have an orphan scanner whose input is a
586
+ # non-monotonic operator; the stratum(LHS) = stratum(RHS) + 1, but there's
587
+ # nothing else in stratum(LHS).
588
+ return if @push_sorted_elems[stratum].nil?
589
+
563
590
  rescan_len = rescan.size
564
591
  invalidate_len = invalidate.size
565
592
  while true
@@ -576,6 +603,18 @@ module Bud
576
603
  rescan.delete_if {|e| e.rescan_at_tick}
577
604
  end
578
605
 
606
+ def each_scanner
607
+ @num_strata.times do |stratum|
608
+ @scanners[stratum].each_value do |scanner|
609
+ yield scanner, stratum
610
+ end
611
+ end
612
+
613
+ @orphan_scanners.each do |scanner,stratum|
614
+ yield scanner, stratum
615
+ end
616
+ end
617
+
579
618
  def do_rewrite
580
619
  @meta_parser = BudMeta.new(self, @declarations)
581
620
  @stratified_rules = @meta_parser.meta_rewrite
@@ -1052,23 +1091,23 @@ module Bud
1052
1091
  elem.invalidate_cache unless elem.class <= PushElement
1053
1092
  }
1054
1093
 
1055
- num_strata = @push_sorted_elems.size
1056
1094
  # The following loop invalidates additional (non-default) elements and
1057
1095
  # tables that depend on the run-time invalidation state of a table.
1058
1096
  # Loop once to set the flags.
1059
- num_strata.times do |stratum|
1060
- @scanners[stratum].each_value do |scanner|
1061
- if scanner.rescan
1062
- scanner.rescan_set.each {|e| e.rescan = true}
1063
- scanner.invalidate_set.each {|e|
1064
- e.invalidated = true
1065
- e.invalidate_cache unless e.class <= PushElement
1066
- }
1067
- end
1097
+ each_scanner do |scanner, stratum|
1098
+ if scanner.rescan
1099
+ scanner.rescan_set.each {|e| e.rescan = true}
1100
+ scanner.invalidate_set.each {|e|
1101
+ e.invalidated = true
1102
+ e.invalidate_cache unless e.class <= PushElement
1103
+ }
1068
1104
  end
1069
1105
  end
1070
- # Loop a second time to actually call invalidate_cache
1071
- num_strata.times do |stratum|
1106
+
1107
+ # Loop a second time to actually call invalidate_cache. We can't merge
1108
+ # this with the loops above because some versions of invalidate_cache
1109
+ # (e.g., join) depend on the rescan state of other elements.
1110
+ @num_strata.times do |stratum|
1072
1111
  @push_sorted_elems[stratum].each {|e| e.invalidate_cache if e.invalidated}
1073
1112
  end
1074
1113
  end
@@ -1134,14 +1173,14 @@ module Bud
1134
1173
  end
1135
1174
 
1136
1175
  # Return the stratum number of the given collection.
1137
- # NB: if a collection is not referenced by any rules, it is not currently
1138
- # assigned to a strata.
1176
+ # NB: if a collection does not appear on the lhs or rhs of any rules, it is
1177
+ # not currently assigned to a strata.
1139
1178
  def collection_stratum(collection)
1140
1179
  t_stratum.each do |t|
1141
1180
  return t.stratum if t.predicate == collection
1142
1181
  end
1143
1182
 
1144
- raise Bud::Error, "no such collection: #{collection}"
1183
+ return nil
1145
1184
  end
1146
1185
 
1147
1186
  private