bud 0.0.5 → 0.0.6

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.
data/bin/budplot CHANGED
@@ -8,7 +8,7 @@ require 'bud/viz_util'
8
8
 
9
9
  include VizUtil
10
10
 
11
- def is_module?(m)
11
+ def is_constant?(m)
12
12
  begin
13
13
  return (eval("defined?(#{m})") == "constant")
14
14
  rescue SyntaxError
@@ -16,16 +16,42 @@ def is_module?(m)
16
16
  end
17
17
  end
18
18
 
19
- def process(mods)
19
+ def make_instance(mods)
20
+ # If we're given a single identifier that names a class, just return an
21
+ # instance of that class. Otherwise, define a bogus class that includes all
22
+ # the module names specified by the user and return an instance.
20
23
  mods.each do |m|
21
- unless is_module? m
22
- puts "Error: unable to find definition for module \"#{m}\""
24
+ unless is_constant? m
25
+ puts "Error: unable to find definition for module or class \"#{m}\""
26
+ exit
27
+ end
28
+
29
+ mod_klass = eval m
30
+ if mod_klass.class == Class
31
+ if mods.length == 1
32
+ return mod_klass.new
33
+ else
34
+ puts "Error: cannot intermix classes and modules"
35
+ exit
36
+ end
37
+ elsif mod_klass.class != Module
38
+ puts "Error: \"#{m}\" is not a module or class name"
23
39
  exit
24
40
  end
25
41
  end
26
42
 
27
- classdef = "class FooBar\ninclude Bud\n" + mods.map{|m| "include #{m}"}.join("\n") + "\nend\n FooBar.new"
28
- d = eval(classdef)
43
+ def_lines = ["class FooBar",
44
+ "include Bud",
45
+ mods.map {|m| "include #{m}"},
46
+ "end"
47
+ ]
48
+ class_def = def_lines.flatten.join("\n")
49
+ eval(class_def)
50
+ FooBar.new
51
+ end
52
+
53
+ def process(mods)
54
+ d = make_instance(mods)
29
55
 
30
56
  interfaces = {}
31
57
  d.t_provides.each do |name, is_input|
@@ -39,10 +65,10 @@ def process(mods)
39
65
  d.tables.each do |t|
40
66
  tab = t[0].to_s
41
67
  tabinf[tab] = t[1].class.to_s
68
+ next if d.builtin_tables.has_key? t[0]
69
+
42
70
  if interfaces[tab].nil?
43
- unless tab =~ /^t_/ or tab == "stdio" or tab == "localtick"
44
- priv << t
45
- end
71
+ priv << t
46
72
  else
47
73
  if interfaces[tab]
48
74
  inp << t
@@ -84,7 +110,6 @@ def do_table(f, info)
84
110
  info.sort{|a, b| a[0].to_s <=> b[0].to_s}.each do |tbl_name, tbl_impl|
85
111
  next if tbl_impl.schema.nil?
86
112
  key_s = tbl_impl.key_cols.join(", ")
87
- key_s = "[]" if key_s == ""
88
113
  val_s = tbl_impl.val_cols.join(", ")
89
114
  f.puts "<tr><td><b>#{tbl_name}</b></td>"
90
115
  f.puts "<td>#{key_s}</td><td>#{val_s}</td></tr>"
@@ -93,7 +118,7 @@ def do_table(f, info)
93
118
  end
94
119
 
95
120
  if ARGV.length < 2
96
- puts "Usage: budplot LIST_OF_FILES LIST_OF_MODULES"
121
+ puts "Usage: budplot LIST_OF_FILES LIST_OF_MODULES_OR_CLASSES"
97
122
  exit
98
123
  end
99
124
 
@@ -102,7 +127,7 @@ end
102
127
  modules = []
103
128
  ARGV.each do |arg|
104
129
  if File.exists? arg
105
- eval "require '#{arg}'"
130
+ require arg
106
131
  else
107
132
  modules << arg
108
133
  end
data/bin/budtimelines CHANGED
@@ -36,7 +36,7 @@ end
36
36
  module TPSchema
37
37
  state do
38
38
  table :deltas, [:bud_time, :tab, :nm]
39
- table :zerod_cards, [:bud_time, :table, :cnt]
39
+ table :zerod_cards, [:bud_time, :table, :cnt, :pred]
40
40
  table :nm_tab, [:table]
41
41
  table :collapsible_base, [:start, :fin]
42
42
  table :collapsible, [:start, :fin]
@@ -49,19 +49,18 @@ end
49
49
  module DeltaLogic
50
50
  include TPSchema
51
51
  bloom do
52
- zerod_cards <= cardinalities
52
+ zerod_cards <= cardinalities{|c| c + [c.bud_time-1]}
53
53
  zerod_cards <= (times * depends).pairs do |t, d|
54
54
  unless cardinalities{|c| c[1] if c[0] == t.bud_time}.include? d[1]
55
- [t.bud_time, d[1], 0]
55
+ [t.bud_time, d[1], 0, t.bud_time - 1]
56
56
  end
57
57
  end
58
58
 
59
-
60
59
  nm_tab <= depends do |d|
61
60
  [d[1]] if d[4]
62
61
  end
63
62
 
64
- deltas <= (zerod_cards * zerod_cards).pairs(:table => :table) do |c1, c2|
63
+ deltas <= (zerod_cards * zerod_cards).pairs(:table => :table, :bud_time => :pred) do |c1, c2|
65
64
  if c1.bud_time == c2.bud_time - 1 and c1.table == c2.table and c1.cnt != c2.cnt
66
65
  if nm_tab.include? [c1.table]
67
66
  [c2.bud_time, c1.table, true]
@@ -77,26 +76,22 @@ module VanillaTraceProcessing
77
76
  include TPSchema
78
77
  include DeltaLogic
79
78
 
80
- bloom do
81
- collapsible_base <= times do |t|
82
- unless deltas{|d| d.bud_time if d.nm}.include? t.bud_time
83
- [t.bud_time-1, t.bud_time]
84
- end
85
- end
79
+ state do
80
+ scratch :tp, times.schema
81
+ scratch :bi1, best_interval.schema
82
+ end
86
83
 
84
+ bloom do
85
+ tp <= times.notin(deltas, :bud_time => :bud_time) {|t, d| true if d.nm}
86
+ collapsible_base <= tp {|t| [t.bud_time-1, t.bud_time]}
87
87
  collapsible <= collapsible_base
88
88
 
89
89
  collapsible <= (collapsible_base * collapsible).pairs(:fin => :start) do |b, c|
90
- puts "another collapsible row; now #{b.inspect} - #{c.inspect}"
91
90
  [b.start, c.fin]
92
91
  end
93
92
 
94
- best_interval <= collapsible do |c|
95
- unless collapsible{|c1| c1.start == c.start and c1.fin > c.fin}.any? \
96
- or collapsible{|c2| c2.fin == c.fin and c2.start < c.start}.any?
97
- c
98
- end
99
- end
93
+ bi1 <= collapsible.notin(collapsible, :start => :start) {|c1, c2| true if c2.fin > c1.fin}
94
+ best_interval <= bi1.notin(collapsible, :fin => :fin) {|c1, c2| true if c2.start < c1.start}
100
95
  end
101
96
  end
102
97
 
@@ -139,12 +134,12 @@ da = GlobalDepAnalyzer.new
139
134
 
140
135
  ARGV.each do |arg_raw|
141
136
  elems = arg_raw.split("_")
142
- arg = elems[1..3].join("_")
137
+ arg = elems[1..4].join("_")
143
138
  clean_arg << arg
144
139
  snd_info[arg] = []
145
140
  rcv_info[arg] = []
146
141
 
147
- meta, data = get_meta2("#{arg_raw}/bud_")
142
+ meta, data = get_meta2("#{arg_raw}")
148
143
  tp = SimpleTraceProcessor.new
149
144
 
150
145
  meta[:depends].each do |m|
@@ -166,7 +161,6 @@ ARGV.each do |arg_raw|
166
161
 
167
162
  tp.tick
168
163
 
169
-
170
164
  puts "entries in collapsible: #{tp.collapsible.length}"
171
165
  puts "entries in base: #{tp.collapsible_base.length}"
172
166
  puts "entries in deltas: #{tp.deltas.length}"
@@ -182,7 +176,11 @@ da.tick
182
176
  nmreach = {}
183
177
  da.depends_tc.each do |d|
184
178
  nmreach[d[0]] = {} unless nmreach[d[0]]
185
- nmreach[d[0]][d[1]] = d[3]
179
+ if nmreach[d[0]][d[1]]
180
+ nmreach[d[0]][d[1]] = d[3] or nmreach[d[0]][d[1]]
181
+ else
182
+ nmreach[d[0]][d[1]] = d[3]
183
+ end
186
184
  end
187
185
 
188
186
  # our local intervals relations are too optimistic. to say that intervals[foo] = [2, 5]
data/bin/budvis CHANGED
@@ -7,7 +7,7 @@ require 'bud/viz_util'
7
7
 
8
8
  include VizUtil
9
9
 
10
- BUD_DBM_DIR = "#{ARGV[0]}/bud_"
10
+ BUD_DBM_DIR = "#{ARGV[0]}"
11
11
 
12
12
 
13
13
  def usage
data/docs/cheat.md CHANGED
@@ -82,6 +82,13 @@ State declaration includes interval (in seconds).
82
82
 
83
83
  periodic :timer, 0.1
84
84
 
85
+ Note that because periodics are just a simple wrapper over the system clock, Bud
86
+ provides few semantic guarantees about the behavior of periodics. In particular,
87
+ periodics execute in a best-effort manner (there is no guarantee of timely
88
+ delivery of a periodic tuple), and the system clock value stored in the `val`
89
+ field may not be monotonically increasing (e.g., if the system clock is changed
90
+ in the midst of Bud execution).
91
+
85
92
  ### stdio ###
86
93
  Built-in scratch collection for performing terminal I/O.<br>
87
94
  System-provided attributes: `[:line] => []`
@@ -151,12 +158,10 @@ update/upsert:
151
158
 
152
159
  * `left <+- right` &nbsp;&nbsp;&nbsp; (*deferred*)<br>
153
160
  deferred insert of items on rhs and deferred deletion of items with matching
154
- keys on lhs.
155
-
156
- That is, for each fact produced by the rhs, the upsert operator removes any
157
- existing tuples that match on the lhs collection's key columns before inserting
158
- the corresponding rhs fact. Note that both the removal and insertion operators
159
- happen atomically in the next timestep.
161
+ keys on lhs. That is, for each fact produced by the rhs, the upsert operator
162
+ removes any existing tuples that match on the lhs collection's key columns
163
+ before inserting the corresponding rhs fact. Note that both the removal and
164
+ insertion operations happen atomically in the next timestep.
160
165
 
161
166
  ### Collection Methods ###
162
167
  Standard Ruby methods used on a BudCollection `bc`:
@@ -184,26 +189,36 @@ implicit map:
184
189
 
185
190
  `bc.include?`:
186
191
 
187
- t5 <= bc do |t| # like SQL's NOT IN
192
+ # This is similar to SQL's NOT IN; note that Bud provides a "notin"
193
+ # collection method that should probably be preferred to this approach.
194
+ t5 <= bc do |t|
188
195
  t unless t2.include?([t.col1, t.col2])
189
196
  end
190
197
 
191
198
  ## BudCollection-Specific Methods ##
192
- `bc.keys`: projects `bc` to key columns<br>
199
+ `bc.schema`: returns the schema of `bc` (Hash of key column names => non-key column names)<br>
193
200
 
194
- `bc.values`: projects `bc` to non-key columns<br>
201
+ `bc.cols`: returns the column names in `bc` as an Array<br>
195
202
 
196
- `bc.inspected`: shorthand for `bc {|t| [t.inspect]}`
203
+ `bc.key_cols`: returns the key column names in `bc` as an Array<br>
197
204
 
198
- stdio <~ bc.inspected
205
+ `bc.val_cols`: returns the non-key column names in `bc` as an Array<br>
206
+
207
+ `bc.keys`: projects `bc` to key columns<br>
208
+
209
+ `bc.values`: projects `bc` to non-key columns<br>
199
210
 
200
211
  `chan.payloads`: projects `chan` to non-address columns. Only defined for channels.
201
212
 
202
213
  # at sender
203
- msgs <~ requests {|r| "127.0.0.1:12345", r}
214
+ msgs <~ requests {|r| ["127.0.0.1:12345", r]}
204
215
  # at receiver
205
216
  requests <= msgs.payloads
206
217
 
218
+ `bc.inspected`: returns a human-readable version of the contents of `bc`
219
+
220
+ stdio <~ bc.inspected
221
+
207
222
  `bc.exists?`: test for non-empty collection. Can optionally pass in a block.
208
223
 
209
224
  stdio <~ [["Wake Up!"] if timer.exists?]
@@ -211,10 +226,18 @@ implicit map:
211
226
  [r.inspect] if msgs.exists?{|m| r.ident == m.ident}
212
227
  end
213
228
 
214
- `bc.notin(bc2, `*optional hash pairs*`)` *optional ruby block*:<br>
215
- Output each item of `bc` such that (a) it has no match in `bc2` on the hash-pairs attributes, or (b) there is no matching item in `bc2` that leads to a non-nil return value from the block.
216
- Hash pairs can be fully qualified (`bc.attr1 => bc2.attr2`)
217
- or shorthand (`:attr1 => :attr2`).
229
+ `bc.notin(bc2, `*optional hash pairs*`, `*optional ruby block*`)`:<br>
230
+ Output the facts in `bc` that do not appear in `bc2`, as follows. First, we form a temporary collection `t` as follows:
231
+
232
+ 1. Join `bc` and `bc2` according to the specified hash pairs. Hash pairs can
233
+ be fully qualified (`bc.attr1 => bc2.attr2`) or shorthand (`:attr1 =>
234
+ :attr2`).
235
+
236
+ 2. If a code block is specified, invoke the block on every pair of matching
237
+ tuples in the join result. Any matches for which the block returns `nil`
238
+ are removed from `t`.
239
+
240
+ Finally, we output every tuple of `bc` that does *not* appear in `t`.
218
241
 
219
242
  # output items from foo if (a) there is no matching key in bar, or
220
243
  # (b) all matching keys in bar have a smaller value
@@ -330,13 +353,23 @@ There are two ways to use a module *B* in another Bloom module *A*:
330
353
  (facts inserted into a collection defined in `b1` won't also be inserted
331
354
  into `b2`'s copy of the collection).
332
355
 
356
+ In practice, a Bloom program is often composed of a collection of modules (which
357
+ may themselves include or import sub-modules) and one "top-level class" that
358
+ includes/imports those modules as well as the `Bud` module. An instance of this
359
+ top-level class represents an instance of the Bud interpreter; it is on this
360
+ top-level class that the `run_fg` method should be invoked, for example.
361
+
362
+ Note that to enable the Bloom DSL for a collection of Ruby code, it is
363
+ sufficient to include the `Bud` module *once* in the top-level class. That is,
364
+ you should *not* include `Bud` in every Bloom module that you write.
365
+
333
366
  ## Skeleton of a Bud Module ##
334
367
 
335
368
  require 'rubygems'
336
369
  require 'bud'
337
370
 
338
371
  module YourModule
339
- include Bud
372
+ import SubModule => :sub_m
340
373
 
341
374
  state do
342
375
  ...
@@ -355,3 +388,7 @@ There are two ways to use a module *B* in another Bloom module *A*:
355
388
  end
356
389
  end
357
390
 
391
+ class TopLevelClass
392
+ include Bud
393
+ include YourModule
394
+ end
data/docs/operational.md CHANGED
@@ -31,7 +31,7 @@ It is important to understand how the Bloom collection operators fit into these
31
31
 
32
32
  ## Atomicity: Timesteps and Deferred Operators ##
33
33
 
34
- The only instantaneous Bloom operator is a merge (`<=`), which can only introduce additional items into a collection--it can not delete or change existing items. As a result, all state within a Bloom timestep is *immutable*: once an item is in a collection at timestep *T*, it stays in that collection throughout timestep *T*. (And forever after, the fact that the item was in that collection at timestep *T* remains true.)
34
+ The only instantaneous Bloom operator is a merge (`<=`), which can only introduce additional items into a collection--it cannot delete or change existing items. As a result, all state within a Bloom timestep is *immutable*: once an item is in a collection at timestep *T*, it stays in that collection throughout timestep *T*. (And forever after, the fact that the item was in that collection at timestep *T* remains true.)
35
35
 
36
36
  To get atomic state change in Bloom, you exploit the combination of two language features:
37
37
 
data/lib/bud.rb CHANGED
@@ -5,6 +5,10 @@ require 'socket'
5
5
  require 'superators'
6
6
  require 'thread'
7
7
 
8
+ # Ruby2Ruby 1.3.1 is buggy (see issue #250)
9
+ gem 'ruby2ruby', '< 1.3.1'
10
+ require 'ruby2ruby'
11
+
8
12
  require 'bud/monkeypatch'
9
13
 
10
14
  require 'bud/aggs'
@@ -61,7 +65,8 @@ $bud_instances = {} # Map from instance id => Bud instance
61
65
  module Bud
62
66
  attr_reader :strata, :budtime, :inbound, :options, :meta_parser, :viz, :rtracer
63
67
  attr_reader :dsock
64
- attr_reader :tables, :channels, :tc_tables, :zk_tables, :dbm_tables, :sources, :sinks
68
+ attr_reader :builtin_tables, :tables
69
+ attr_reader :channels, :tc_tables, :zk_tables, :dbm_tables, :sources, :sinks
65
70
  attr_reader :stratum_first_iter, :joinstate
66
71
  attr_reader :this_stratum, :this_rule, :rule_orig_src
67
72
  attr_reader :running_async
@@ -96,8 +101,8 @@ module Bud
96
101
  # * <tt>:deploy</tt> enable deployment
97
102
  # * <tt>:deploy_child_opts</tt> option hash to pass to deployed instances
98
103
  def initialize(options={})
104
+ @builtin_tables = {}
99
105
  @tables = {}
100
- @table_meta = []
101
106
  @rewritten_strata = []
102
107
  @channels = {}
103
108
  @tc_tables = {}
@@ -180,11 +185,14 @@ module Bud
180
185
 
181
186
  # Rewrite methods defined in the given klass to expand module references and
182
187
  # temp collections. Imported modules are rewritten during the import process;
183
- # we rewrite the main Bud class and any included modules here. Note that we
184
- # only rewrite each distinct Class once.
188
+ # we rewrite the main class associated with this Bud instance and any included
189
+ # modules here. Note that we only rewrite each distinct Class once, and we
190
+ # skip methods defined by the Bud (Ruby) module directly (since we can be sure
191
+ # those won't reference Bloom modules).
185
192
  def self.rewrite_local_methods(klass)
186
193
  @done_rewrite ||= {}
187
194
  return if @done_rewrite.has_key? klass.name
195
+ return if klass.name == self.name # Skip methods defined in the Bud module
188
196
 
189
197
  u = Unifier.new
190
198
  ref_expander = NestedRefRewriter.new(klass.bud_import_table)
@@ -368,7 +376,7 @@ module Bud
368
376
  # If we're called from the EventMachine thread (and EM is running), blocking
369
377
  # the current thread would imply deadlocking ourselves.
370
378
  if Thread.current == EventMachine::reactor_thread and EventMachine::reactor_running?
371
- raise BudError, "Cannot invoke run_fg from inside EventMachine"
379
+ raise BudError, "cannot invoke run_fg from inside EventMachine"
372
380
  end
373
381
 
374
382
  q = Queue.new
@@ -486,7 +494,7 @@ module Bud
486
494
  cb_id = nil
487
495
  schedule_and_wait do
488
496
  unless @tables.has_key? tbl_name
489
- raise Bud::BudError, "No such table: #{tbl_name}"
497
+ raise Bud::BudError, "no such table: #{tbl_name}"
490
498
  end
491
499
 
492
500
  raise Bud::BudError if @callbacks.has_key? @callback_id
@@ -500,7 +508,7 @@ module Bud
500
508
  # Unregister the callback that has the given ID.
501
509
  def unregister_callback(id)
502
510
  schedule_and_wait do
503
- raise Bud::BudError, "Missing callback: #{id.inspect}" unless @callbacks.has_key? id
511
+ raise Bud::BudError, "missing callback: #{id.inspect}" unless @callbacks.has_key? id
504
512
  @callbacks.delete(id)
505
513
  end
506
514
  end
@@ -730,16 +738,19 @@ module Bud
730
738
  private
731
739
 
732
740
  # Builtin BUD state (predefined collections). We could define this using the
733
- # standard "state" syntax, but we want to ensure that builtin state is
741
+ # standard state block syntax, but we want to ensure that builtin state is
734
742
  # initialized before user-defined state.
735
743
  def builtin_state
744
+ # We expect there to be no previously-defined tables
745
+ raise BudError unless @tables.empty?
746
+
736
747
  loopback :localtick, [:col1]
737
748
  @stdio = terminal :stdio
738
749
  readonly :signals, [:key]
739
750
  scratch :halt, [:key]
740
751
  @periodics = table :periodics_tbl, [:pername] => [:period]
741
752
 
742
- # for Bud reflection
753
+ # For Bud reflection
743
754
  table :t_rules, [:rule_id] => [:lhs, :op, :src, :orig_src]
744
755
  table :t_depends, [:rule_id, :lhs, :op, :body] => [:nm]
745
756
  table :t_depends_tc, [:head, :body, :via, :neg, :temporal]
@@ -749,6 +760,9 @@ module Bud
749
760
  table :t_cycle, [:predicate, :via, :neg, :temporal]
750
761
  table :t_table_info, [:tab_name, :tab_type]
751
762
  table :t_table_schema, [:tab_name, :col_name, :ord, :loc]
763
+
764
+ # Identify builtin tables as such
765
+ @builtin_tables = @tables.clone
752
766
  end
753
767
 
754
768
  # Handle any inbound tuples off the wire. Received messages are placed
@@ -826,7 +840,7 @@ module Bud
826
840
  unless new_e.class <= BudError
827
841
  new_e = BudError
828
842
  end
829
- raise new_e, "Exception during Bud evaluation.\nException: #{e.inspect}.#{src_msg}"
843
+ raise new_e, "exception during Bud evaluation.\nException: #{e.inspect}.#{src_msg}"
830
844
  end
831
845
  end
832
846
  @stratum_first_iter = false
@@ -835,14 +849,13 @@ module Bud
835
849
  colls = @stratum_collection_map[strat_num] if @stratum_collection_map
836
850
  colls ||= @tables.keys
837
851
  colls.each do |name|
838
- begin
839
- coll = self.send(name)
840
- unless coll.delta.empty? and coll.new_delta.empty?
841
- coll.tick_deltas
842
- fixpoint = false
843
- end
844
- rescue
845
- # ignore missing tables; rebl for example deletes them mid-stream
852
+ coll = @tables[name]
853
+ # ignore missing tables; rebl for example deletes them mid-stream
854
+ next if coll.nil?
855
+
856
+ unless coll.delta.empty? and coll.new_delta.empty?
857
+ fixpoint = false unless coll.new_delta.empty?
858
+ coll.tick_deltas
846
859
  end
847
860
  end
848
861
  end while not fixpoint