bud 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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