bud 0.9.4 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/bud/aggs.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
1
|
module Bud
|
4
2
|
######## Agg definitions
|
5
3
|
class Agg #:nodoc: all
|
@@ -38,7 +36,7 @@ module Bud
|
|
38
36
|
|
39
37
|
class Min < ArgExemplary #:nodoc: all
|
40
38
|
def trans(the_state, val)
|
41
|
-
if the_state <
|
39
|
+
if (the_state <=> val) < 0
|
42
40
|
return the_state, :ignore
|
43
41
|
elsif the_state == val
|
44
42
|
return the_state, :keep
|
@@ -55,8 +53,10 @@ module Bud
|
|
55
53
|
|
56
54
|
class Max < ArgExemplary #:nodoc: all
|
57
55
|
def trans(the_state, val)
|
58
|
-
if the_state >
|
56
|
+
if (the_state <=> val) > 0
|
59
57
|
return the_state, :ignore
|
58
|
+
elsif the_state == val
|
59
|
+
return the_state, :keep
|
60
60
|
else
|
61
61
|
return val, :replace
|
62
62
|
end
|
@@ -207,4 +207,20 @@ module Bud
|
|
207
207
|
def accum(x)
|
208
208
|
[Accum.new, x]
|
209
209
|
end
|
210
|
+
|
211
|
+
class AccumPair < Agg #:nodoc: all
|
212
|
+
def init(fst, snd)
|
213
|
+
[[fst, snd]].to_set
|
214
|
+
end
|
215
|
+
def trans(the_state, fst, snd)
|
216
|
+
the_state << [fst, snd]
|
217
|
+
return the_state, nil
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# aggregate method to be used in Bud::BudCollection.group.
|
222
|
+
# accumulates x, y inputs into a set of pairs (two element arrays).
|
223
|
+
def accum_pair(x, y)
|
224
|
+
[AccumPair.new, x, y]
|
225
|
+
end
|
210
226
|
end
|
data/lib/bud/bud_meta.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
require 'bud/rewrite'
|
2
2
|
|
3
|
-
|
4
3
|
class BudMeta #:nodoc: all
|
5
|
-
def initialize(
|
6
|
-
@bud_instance =
|
7
|
-
@declarations =
|
8
|
-
@
|
4
|
+
def initialize(bud_i)
|
5
|
+
@bud_instance = bud_i
|
6
|
+
@declarations = bud_i.methods.select {|m| m =~ /^__bloom__.+$/}.map {|m| m.to_s}
|
7
|
+
@rule_idx = 0
|
8
|
+
|
9
|
+
# The results of bud_meta are analyzed further using a helper Bloom
|
10
|
+
# instance. See depanalysis().
|
11
|
+
@dependency_analysis = nil
|
9
12
|
end
|
10
13
|
|
11
14
|
def meta_rewrite
|
@@ -13,20 +16,30 @@ class BudMeta #:nodoc: all
|
|
13
16
|
|
14
17
|
stratified_rules = []
|
15
18
|
if @bud_instance.toplevel == @bud_instance
|
16
|
-
nodes
|
19
|
+
nodes = compute_node_graph
|
20
|
+
analyze_dependencies(nodes)
|
21
|
+
|
22
|
+
stratum_map = stratify_preds(nodes)
|
23
|
+
top_stratum = stratum_map.values.max
|
24
|
+
top_stratum ||= -1
|
17
25
|
|
18
26
|
# stratum_map = {fully qualified pred => stratum}. Copy stratum_map data
|
19
27
|
# into t_stratum format.
|
20
28
|
raise unless @bud_instance.t_stratum.to_a.empty?
|
21
|
-
@bud_instance.t_stratum
|
29
|
+
@bud_instance.t_stratum.merge(stratum_map.to_a)
|
22
30
|
|
23
|
-
# slot each rule into the stratum corresponding to its lhs pred (from stratum_map)
|
24
31
|
stratified_rules = Array.new(top_stratum + 2) { [] } # stratum -> [ rules ]
|
25
32
|
@bud_instance.t_rules.each do |rule|
|
26
33
|
if rule.op == '<='
|
27
34
|
# Deductive rules are assigned to strata based on the basic Datalog
|
28
|
-
# stratification algorithm
|
29
|
-
|
35
|
+
# stratification algorithm. Note that we don't place all the rules
|
36
|
+
# with a given lhs relation in the same strata; rather, we place a
|
37
|
+
# rule in the lowest strata we can, as determined by the rule's rhs
|
38
|
+
# relations (and whether the rel is used in a non-monotonic context).
|
39
|
+
body_rels = find_body_rels(rule)
|
40
|
+
body_strata = body_rels.map {|r,is_nm| stratum_map[r] + (is_nm ? 1 : 0) || 0}
|
41
|
+
belongs_in = body_strata.max
|
42
|
+
|
30
43
|
# If the rule body doesn't reference any collections, it won't be
|
31
44
|
# assigned a stratum, so just place it in stratum zero
|
32
45
|
belongs_in ||= 0
|
@@ -36,28 +49,39 @@ class BudMeta #:nodoc: all
|
|
36
49
|
stratified_rules[top_stratum + 1] << rule
|
37
50
|
end
|
38
51
|
end
|
39
|
-
|
40
|
-
# stratified_rules[
|
41
|
-
#
|
42
|
-
|
52
|
+
|
53
|
+
# stratified_rules[0] may be empty if none of the nodes at stratum 0 are
|
54
|
+
# on the lhs stratified_rules[top_stratum+1] will be empty if there are no
|
55
|
+
# temporal rules.
|
56
|
+
stratified_rules.reject! {|r| r.empty?}
|
57
|
+
|
58
|
+
stratified_rules.each_with_index do |strat,strat_num|
|
59
|
+
strat.each do |rule|
|
60
|
+
@bud_instance.t_rule_stratum << [rule.bud_obj, rule.rule_id, strat_num]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
43
64
|
dump_rewrite(stratified_rules) if @bud_instance.options[:dump_rewrite]
|
44
65
|
end
|
45
66
|
return stratified_rules
|
46
67
|
end
|
47
68
|
|
69
|
+
def find_body_rels(rule)
|
70
|
+
@bud_instance.t_depends.map do |d|
|
71
|
+
[d.body, d.nm] if d.rule_id == rule.rule_id and d.bud_obj == rule.bud_obj
|
72
|
+
end.compact
|
73
|
+
end
|
74
|
+
|
48
75
|
def shred_rules
|
49
|
-
#
|
50
|
-
#
|
51
|
-
# after making this pass, we no longer care about the names of methods.
|
52
|
-
# we are shredding down to the granularity of rule heads.
|
53
|
-
seed = 0
|
76
|
+
# After making this pass, we no longer care about the names of methods. We
|
77
|
+
# are shredding down to the granularity of rule heads.
|
54
78
|
rulebag = {}
|
55
79
|
@bud_instance.class.ancestors.reverse.each do |anc|
|
56
80
|
@declarations.each do |meth_name|
|
57
|
-
rw = rewrite_rule_block(anc, meth_name
|
81
|
+
rw = rewrite_rule_block(anc, meth_name)
|
58
82
|
if rw
|
59
|
-
seed = rw.rule_indx
|
60
83
|
rulebag[meth_name] = rw
|
84
|
+
@rule_idx = rw.rule_idx
|
61
85
|
end
|
62
86
|
end
|
63
87
|
end
|
@@ -68,7 +92,7 @@ class BudMeta #:nodoc: all
|
|
68
92
|
end
|
69
93
|
end
|
70
94
|
|
71
|
-
def rewrite_rule_block(klass, block_name
|
95
|
+
def rewrite_rule_block(klass, block_name)
|
72
96
|
return unless klass.respond_to? :__bloom_asts__
|
73
97
|
|
74
98
|
pt = klass.__bloom_asts__[block_name]
|
@@ -104,18 +128,18 @@ class BudMeta #:nodoc: all
|
|
104
128
|
end
|
105
129
|
raise Bud::CompileError, "#{error_msg} in rule block \"#{block_name}\"#{src_msg}"
|
106
130
|
end
|
107
|
-
rewriter = RuleRewriter.new(
|
131
|
+
rewriter = RuleRewriter.new(@bud_instance, @rule_idx)
|
108
132
|
rewriter.process(pt)
|
109
133
|
return rewriter
|
110
134
|
end
|
111
135
|
|
112
136
|
def get_qual_name(pt)
|
113
137
|
# expect to see a parse tree corresponding to a dotted name
|
114
|
-
# a.b.c == s(:call, s1, :c
|
115
|
-
# where s1 == s(:call, s2, :b
|
116
|
-
# where s2 == s(:call, nil, :a
|
117
|
-
tag, recv, name, args = pt
|
118
|
-
return nil unless tag == :call and args.
|
138
|
+
# a.b.c == s(:call, s1, :c)
|
139
|
+
# where s1 == s(:call, s2, :b)
|
140
|
+
# where s2 == s(:call, nil, :a)
|
141
|
+
tag, recv, name, *args = pt
|
142
|
+
return nil unless tag == :call and args.empty?
|
119
143
|
|
120
144
|
if recv
|
121
145
|
qn = get_qual_name(recv)
|
@@ -127,23 +151,16 @@ class BudMeta #:nodoc: all
|
|
127
151
|
end
|
128
152
|
|
129
153
|
# Perform some basic sanity checks on the AST of a rule block. We expect a
|
130
|
-
# rule block to consist of a :defn
|
154
|
+
# rule block to consist of a :defn whose body consists of a sequence of
|
131
155
|
# statements. Each statement is a :call node. Returns nil (no error found), a
|
132
156
|
# Sexp (containing an error), or a pair of [Sexp, error message].
|
133
157
|
def check_rule_ast(pt)
|
134
|
-
# :defn format: node tag, block name, args,
|
135
|
-
|
136
|
-
|
137
|
-
return pt if scope.sexp_type != :scope
|
138
|
-
block = scope[1]
|
139
|
-
|
140
|
-
block.each_with_index do |n,i|
|
141
|
-
if i == 0
|
142
|
-
return pt if n != :block
|
143
|
-
next
|
144
|
-
end
|
158
|
+
# :defn format: node tag, block name, args, body_0, ..., body_n
|
159
|
+
tag, name, args, *body = pt
|
160
|
+
return pt if tag != :defn
|
145
161
|
|
146
|
-
|
162
|
+
body.each_with_index do |n,i|
|
163
|
+
next if i == 0 and n == s(:nil) # a block got rewritten to an empty block
|
147
164
|
|
148
165
|
# Check for a common case
|
149
166
|
if n.sexp_type == :lasgn
|
@@ -157,8 +174,9 @@ class BudMeta #:nodoc: all
|
|
157
174
|
# Check that LHS references a named collection
|
158
175
|
lhs_name = get_qual_name(lhs)
|
159
176
|
return [n, "unexpected lhs format: #{lhs}"] if lhs_name.nil?
|
160
|
-
unless @bud_instance.tables.has_key? lhs_name.to_sym
|
161
|
-
|
177
|
+
unless @bud_instance.tables.has_key? lhs_name.to_sym or
|
178
|
+
@bud_instance.lattices.has_key? lhs_name.to_sym
|
179
|
+
return [n, "Collection does not exist: '#{lhs_name}'"]
|
162
180
|
end
|
163
181
|
|
164
182
|
return [n, "illegal operator: '#{op}'"] unless [:<, :<=].include? op
|
@@ -171,13 +189,11 @@ class BudMeta #:nodoc: all
|
|
171
189
|
# XXX: We don't check for illegal superators (e.g., "<--"). That would be
|
172
190
|
# tricky, because they are encoded as a nested unary op in the rule body.
|
173
191
|
if op == :<
|
174
|
-
return n unless rhs.sexp_type == :
|
175
|
-
|
176
|
-
return n unless body.sexp_type == :call
|
177
|
-
op_tail = body[2]
|
192
|
+
return n unless rhs.sexp_type == :call
|
193
|
+
op_tail = rhs[2]
|
178
194
|
return n unless [:~, :-@, :+@].include? op_tail
|
179
|
-
rhs_args =
|
180
|
-
return n
|
195
|
+
rhs_args = rhs[3..-1]
|
196
|
+
return n unless rhs_args.empty?
|
181
197
|
end
|
182
198
|
end
|
183
199
|
|
@@ -185,64 +201,61 @@ class BudMeta #:nodoc: all
|
|
185
201
|
end
|
186
202
|
|
187
203
|
|
188
|
-
Node = Struct.new :name, :status, :stratum, :edges, :in_lhs, :in_body, :in_cycle, :is_neg_head
|
189
204
|
# Node.status is one of :init, :in_progress, :done
|
205
|
+
Node = Struct.new :name, :status, :stratum, :edges, :in_lhs, :in_body, :already_neg
|
190
206
|
Edge = Struct.new :to, :op, :neg, :temporal
|
191
207
|
|
192
|
-
def
|
193
|
-
bud = @bud_instance.toplevel
|
208
|
+
def compute_node_graph
|
194
209
|
nodes = {}
|
195
|
-
|
196
|
-
#t_depends [:bud_instance, :rule_id, :lhs, :op, :body] => [:nm]
|
197
|
-
lhs = (nodes[d.lhs] ||= Node.new(d.lhs, :init, 0, [], true, false
|
210
|
+
@bud_instance.toplevel.t_depends.each do |d|
|
211
|
+
# t_depends [:bud_instance, :rule_id, :lhs, :op, :body] => [:nm, :in_body]
|
212
|
+
lhs = (nodes[d.lhs] ||= Node.new(d.lhs, :init, 0, [], true, false))
|
198
213
|
lhs.in_lhs = true
|
199
|
-
body = (nodes[d.body] ||= Node.new(d.body, :init, 0, [], false, true
|
200
|
-
temporal = d.op != "<="
|
201
|
-
lhs.edges << Edge.new(body, d.op, d.nm, temporal)
|
214
|
+
body = (nodes[d.body] ||= Node.new(d.body, :init, 0, [], false, true))
|
202
215
|
body.in_body = true
|
216
|
+
temporal = (d.op != "<=")
|
217
|
+
lhs.edges << Edge.new(body, d.op, d.nm, temporal)
|
203
218
|
end
|
204
219
|
|
220
|
+
return nodes
|
221
|
+
end
|
222
|
+
|
223
|
+
def stratify_preds(nodes)
|
205
224
|
nodes.each_value {|n| calc_stratum(n, false, false, [n.name])}
|
225
|
+
|
206
226
|
# Normalize stratum numbers because they may not be 0-based or consecutive
|
207
227
|
remap = {}
|
208
228
|
# if the nodes stratum numbers are [2, 3, 2, 4], remap = {2 => 0, 3 => 1, 4 => 2}
|
209
229
|
nodes.values.map {|n| n.stratum}.uniq.sort.each_with_index do |num, i|
|
210
230
|
remap[num] = i
|
211
231
|
end
|
232
|
+
|
212
233
|
stratum_map = {}
|
213
|
-
top_stratum = -1
|
214
234
|
nodes.each_pair do |name, n|
|
215
235
|
n.stratum = remap[n.stratum]
|
216
236
|
stratum_map[n.name] = n.stratum
|
217
|
-
top_stratum = max(top_stratum, n.stratum)
|
218
237
|
end
|
219
|
-
|
220
|
-
return nodes, stratum_map, top_stratum
|
238
|
+
return stratum_map
|
221
239
|
end
|
222
240
|
|
223
|
-
def max(a, b) ; a > b ? a : b ; end
|
224
|
-
|
225
241
|
def calc_stratum(node, neg, temporal, path)
|
226
242
|
if node.status == :in_process
|
227
|
-
node.
|
228
|
-
if neg and !temporal and node.is_neg_head
|
243
|
+
if neg and not temporal and not node.already_neg
|
229
244
|
raise Bud::CompileError, "unstratifiable program: #{path.uniq.join(',')}"
|
230
245
|
end
|
231
246
|
elsif node.status == :init
|
232
247
|
node.status = :in_process
|
233
248
|
node.edges.each do |edge|
|
234
|
-
node.is_neg_head = edge.neg
|
235
249
|
next unless edge.op == "<="
|
250
|
+
node.already_neg = neg
|
236
251
|
body_stratum = calc_stratum(edge.to, (neg or edge.neg), (edge.temporal or temporal), path + [edge.to.name])
|
237
|
-
node.
|
238
|
-
node.stratum = max(node.stratum, body_stratum + (edge.neg ? 1 : 0))
|
252
|
+
node.stratum = [node.stratum, body_stratum + (edge.neg ? 1 : 0)].max
|
239
253
|
end
|
240
254
|
node.status = :done
|
241
255
|
end
|
242
256
|
node.stratum
|
243
257
|
end
|
244
258
|
|
245
|
-
|
246
259
|
def analyze_dependencies(nodes) # nodes = {node name => node}
|
247
260
|
preds_in_lhs = nodes.select {|_, node| node.in_lhs}.map {|name, _| name}.to_set
|
248
261
|
preds_in_body = nodes.select {|_, node| node.in_body}.map {|name, _| name}.to_set
|
@@ -274,9 +287,6 @@ class BudMeta #:nodoc: all
|
|
274
287
|
da = ::DepAnalysis.new
|
275
288
|
da.providing <+ @bud_instance.tables[:t_provides].to_a
|
276
289
|
da.depends <+ @bud_instance.t_depends.map{|d| [d.lhs, d.op, d.body, d.nm]}
|
277
|
-
|
278
|
-
#@bud_instance.tables[:t_provides].each {|t| da.providing <+ t}
|
279
|
-
#@bud_instance.tables[:t_depends].each {|t| da.depends_tc <+ t}
|
280
290
|
da.tick_internal
|
281
291
|
@dependency_analysis = da
|
282
292
|
end
|
data/lib/bud/collections.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'msgpack'
|
2
|
-
|
3
|
-
$struct_classes = {}
|
4
1
|
module Bud
|
5
2
|
########
|
6
3
|
#--
|
@@ -16,11 +13,11 @@ module Bud
|
|
16
13
|
class BudCollection
|
17
14
|
include Enumerable
|
18
15
|
|
19
|
-
attr_accessor :bud_instance
|
20
|
-
attr_reader :cols, :key_cols # :nodoc: all
|
16
|
+
attr_accessor :bud_instance # :nodoc: all
|
17
|
+
attr_reader :tabname, :cols, :key_cols # :nodoc: all
|
21
18
|
attr_reader :struct
|
22
|
-
attr_reader :
|
23
|
-
attr_reader :wired_by, :
|
19
|
+
attr_reader :new_delta, :pending # :nodoc: all
|
20
|
+
attr_reader :wired_by, :scanner_cnt
|
24
21
|
attr_accessor :invalidated, :rescan
|
25
22
|
attr_accessor :is_source
|
26
23
|
attr_accessor :accumulate_tick_deltas # updated in bud.do_wiring
|
@@ -30,6 +27,7 @@ module Bud
|
|
30
27
|
@bud_instance = bud_instance
|
31
28
|
@invalidated = true
|
32
29
|
@is_source = true # unless it shows up on the lhs of some rule
|
30
|
+
@scanner_cnt = 0
|
33
31
|
@wired_by = []
|
34
32
|
@accumulate_tick_deltas = false
|
35
33
|
init_schema(given_schema) unless given_schema.nil? and defer_schema
|
@@ -54,16 +52,17 @@ module Bud
|
|
54
52
|
# user-specified schema.
|
55
53
|
@cols.each do |s|
|
56
54
|
if s.to_s.start_with? "@"
|
57
|
-
raise Bud::
|
55
|
+
raise Bud::CompileError, "illegal use of location specifier (@) in column #{s} of non-channel collection #{tabname}"
|
58
56
|
end
|
59
57
|
end
|
60
58
|
|
61
59
|
@key_colnums = @key_cols.map {|k| @cols.index(k)}
|
60
|
+
@val_colnums = val_cols.map {|k| @cols.index(k)}
|
62
61
|
|
63
62
|
if @cols.empty?
|
64
63
|
@cols = nil
|
65
64
|
else
|
66
|
-
@struct = (
|
65
|
+
@struct = Bud::TupleStruct.new_struct(@cols)
|
67
66
|
@structlen = @struct.members.length
|
68
67
|
end
|
69
68
|
setup_accessors
|
@@ -132,11 +131,19 @@ module Bud
|
|
132
131
|
end
|
133
132
|
end
|
134
133
|
|
135
|
-
#
|
134
|
+
# Setup schema accessors, which are class methods. Note that the same
|
135
|
+
# table/column name might appear multiple times on the LHS of a single
|
136
|
+
# join (e.g., (foo * bar).combos(foo.x => bar.y, foo.x => bar.z)). Because
|
137
|
+
# the join predicates are represented as a hash, we need the two instances
|
138
|
+
# of foo.x to be distinct values (otherwise the resulting hash will only
|
139
|
+
# have a single key). Hence, we add a unique ID to the value returned by
|
140
|
+
# schema accessors.
|
136
141
|
@cols_access = Module.new do
|
137
142
|
sc.each_with_index do |c, i|
|
138
143
|
define_method c do
|
139
|
-
|
144
|
+
@counter ||= 0
|
145
|
+
@counter += 1
|
146
|
+
[qualified_tabname, i, c, @counter]
|
140
147
|
end
|
141
148
|
end
|
142
149
|
end
|
@@ -189,13 +196,27 @@ module Bud
|
|
189
196
|
if @bud_instance.wiring?
|
190
197
|
pusher = to_push_elem(the_name, the_schema)
|
191
198
|
# If there is no code block evaluate, use the scanner directly
|
192
|
-
|
193
|
-
|
194
|
-
pusher_pro.elem_name = the_name
|
195
|
-
pusher_pro.tabname = the_name
|
196
|
-
pusher_pro
|
199
|
+
pusher = pusher.pro(&blk) unless blk.nil?
|
200
|
+
pusher
|
197
201
|
else
|
198
|
-
|
202
|
+
rv = []
|
203
|
+
self.each do |t|
|
204
|
+
t = blk.call(t)
|
205
|
+
rv << t unless t.nil?
|
206
|
+
end
|
207
|
+
rv
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# XXX: Although we support each_with_index over Bud collections, using it is
|
212
|
+
# probably not a great idea: the index assigned to a given collection member
|
213
|
+
# is not defined by the language semantics.
|
214
|
+
def each_with_index(the_name=tabname, the_schema=schema, &blk)
|
215
|
+
if @bud_instance.wiring?
|
216
|
+
pusher = to_push_elem(the_name, the_schema)
|
217
|
+
pusher.each_with_index(&blk)
|
218
|
+
else
|
219
|
+
super(&blk)
|
199
220
|
end
|
200
221
|
end
|
201
222
|
|
@@ -218,7 +239,7 @@ module Bud
|
|
218
239
|
end
|
219
240
|
elem.set_block(&f)
|
220
241
|
toplevel.push_elems[[self.object_id, :flatten]] = elem
|
221
|
-
|
242
|
+
elem
|
222
243
|
else
|
223
244
|
@storage.flat_map(&blk)
|
224
245
|
end
|
@@ -228,16 +249,18 @@ module Bud
|
|
228
249
|
def sort(&blk)
|
229
250
|
if @bud_instance.wiring?
|
230
251
|
pusher = self.pro
|
231
|
-
pusher.sort("sort#{object_id}", @bud_instance, @cols, &blk)
|
252
|
+
pusher.sort("sort#{object_id}".to_sym, @bud_instance, @cols, &blk)
|
232
253
|
else
|
233
|
-
@storage.sort
|
254
|
+
@storage.values.sort(&blk)
|
234
255
|
end
|
235
256
|
end
|
236
257
|
|
237
258
|
def rename(the_name, the_schema=nil, &blk)
|
238
|
-
raise unless @bud_instance.wiring?
|
259
|
+
raise Bud::Error unless @bud_instance.wiring?
|
239
260
|
# a scratch with this name should have been defined during rewriting
|
240
|
-
|
261
|
+
unless @bud_instance.respond_to? the_name
|
262
|
+
raise Bud::Error, "rename failed to define a scratch named #{the_name}"
|
263
|
+
end
|
241
264
|
pro(the_name, the_schema, &blk)
|
242
265
|
end
|
243
266
|
|
@@ -251,7 +274,12 @@ module Bud
|
|
251
274
|
|
252
275
|
public
|
253
276
|
def each_raw(&block)
|
254
|
-
@storage
|
277
|
+
each_from([@storage], &block)
|
278
|
+
end
|
279
|
+
|
280
|
+
public
|
281
|
+
def each_delta(&block)
|
282
|
+
each_from([@delta], &block)
|
255
283
|
end
|
256
284
|
|
257
285
|
public
|
@@ -266,43 +294,37 @@ module Bud
|
|
266
294
|
|
267
295
|
public
|
268
296
|
def non_temporal_predecessors
|
269
|
-
@wired_by.select {|
|
297
|
+
@wired_by.select {|e| e.outputs.include? self}
|
298
|
+
end
|
299
|
+
|
300
|
+
public
|
301
|
+
def positive_predecessors
|
302
|
+
@wired_by.select {|e| e.outputs.include?(self) || e.pendings.include?(self)}
|
270
303
|
end
|
271
304
|
|
272
305
|
public
|
273
306
|
def tick_metrics
|
274
307
|
strat_num = bud_instance.this_stratum
|
275
|
-
rule_num = bud_instance.this_rule
|
276
|
-
addr = nil
|
277
308
|
addr = bud_instance.ip_port unless bud_instance.port.nil?
|
309
|
+
key = { :addr=>addr, :tabname=>qualified_tabname,
|
310
|
+
:strat_num=>strat_num}
|
311
|
+
|
278
312
|
bud_instance.metrics[:collections] ||= {}
|
279
|
-
bud_instance.metrics[:collections][
|
280
|
-
bud_instance.metrics[:collections][
|
313
|
+
bud_instance.metrics[:collections][key] ||= 0
|
314
|
+
bud_instance.metrics[:collections][key] += 1
|
281
315
|
end
|
282
316
|
|
283
317
|
private
|
284
318
|
def each_from(bufs, &block) # :nodoc: all
|
319
|
+
do_metrics = bud_instance.options[:metrics]
|
285
320
|
bufs.each do |b|
|
286
321
|
b.each_value do |v|
|
287
|
-
tick_metrics if
|
322
|
+
tick_metrics if do_metrics
|
288
323
|
yield v
|
289
324
|
end
|
290
325
|
end
|
291
326
|
end
|
292
327
|
|
293
|
-
public
|
294
|
-
def each_from_sym(buf_syms, &block) # :nodoc: all
|
295
|
-
bufs = buf_syms.map do |s|
|
296
|
-
case s
|
297
|
-
when :storage then @storage
|
298
|
-
when :delta then @delta
|
299
|
-
when :new_delta then @new_delta
|
300
|
-
else raise Bud::Error, "bad symbol passed into each_from_sym"
|
301
|
-
end
|
302
|
-
end
|
303
|
-
each_from(bufs, &block)
|
304
|
-
end
|
305
|
-
|
306
328
|
private
|
307
329
|
def init_storage
|
308
330
|
@storage = {}
|
@@ -345,7 +367,7 @@ module Bud
|
|
345
367
|
# checks for +item+ in the collection
|
346
368
|
public
|
347
369
|
def include?(item)
|
348
|
-
return true if key_cols.nil?
|
370
|
+
return true if key_cols.nil?
|
349
371
|
return false if item.nil?
|
350
372
|
key = get_key_vals(item)
|
351
373
|
return (item == self[key])
|
@@ -374,13 +396,18 @@ module Bud
|
|
374
396
|
private
|
375
397
|
def raise_pk_error(new, old)
|
376
398
|
key = get_key_vals(old)
|
377
|
-
raise Bud::KeyConstraintError, "key conflict inserting #{new.inspect} into \"#{
|
399
|
+
raise Bud::KeyConstraintError, "key conflict inserting #{new.inspect} into \"#{qualified_tabname}\": existing tuple #{old.inspect}, key = #{key.inspect}"
|
400
|
+
end
|
401
|
+
|
402
|
+
private
|
403
|
+
def is_lattice_val(v)
|
404
|
+
v.kind_of? Bud::Lattice
|
378
405
|
end
|
379
406
|
|
380
407
|
private
|
381
408
|
def prep_tuple(o)
|
382
409
|
return o if o.class == @struct
|
383
|
-
if o.
|
410
|
+
if o.kind_of? Array
|
384
411
|
if @struct.nil?
|
385
412
|
sch = (1 .. o.length).map{|i| "c#{i}".to_sym}
|
386
413
|
init_schema(sch)
|
@@ -391,9 +418,16 @@ module Bud
|
|
391
418
|
raise Bud::TypeError, "array or struct type expected in \"#{qualified_tabname}\": #{o.inspect}"
|
392
419
|
end
|
393
420
|
|
421
|
+
@key_colnums.each do |i|
|
422
|
+
next if i >= o.length
|
423
|
+
if is_lattice_val(o[i])
|
424
|
+
raise Bud::TypeError, "lattice value cannot be a key for #{qualified_tabname}: #{o[i].inspect}"
|
425
|
+
end
|
426
|
+
end
|
394
427
|
if o.length > @structlen
|
395
428
|
raise Bud::TypeError, "too many columns for \"#{qualified_tabname}\": #{o.inspect}"
|
396
429
|
end
|
430
|
+
|
397
431
|
return @struct.new(*o)
|
398
432
|
end
|
399
433
|
|
@@ -411,7 +445,7 @@ module Bud
|
|
411
445
|
when @delta.object_id; "delta"
|
412
446
|
when @new_delta.object_id; "new_delta"
|
413
447
|
end
|
414
|
-
puts "#{qualified_tabname}.#{storetype} ==> #{t}"
|
448
|
+
puts "#{qualified_tabname}.#{storetype} ==> #{t.inspect}"
|
415
449
|
end
|
416
450
|
return if t.nil? # silently ignore nils resulting from map predicates failing
|
417
451
|
t = prep_tuple(t)
|
@@ -420,13 +454,44 @@ module Bud
|
|
420
454
|
end
|
421
455
|
|
422
456
|
# Merge "tup" with key values "key" into "buf". "old" is an existing tuple
|
423
|
-
# with the same key columns as "tup" (if any such tuple exists).
|
457
|
+
# with the same key columns as "tup" (if any such tuple exists). If "old"
|
458
|
+
# exists and "tup" is not a duplicate, check whether the two tuples disagree
|
459
|
+
# on a non-key, non-lattice value; if so, raise a PK error. Otherwise,
|
460
|
+
# construct and return a merged tuple by using lattice merge functions.
|
424
461
|
private
|
425
462
|
def merge_to_buf(buf, key, tup, old)
|
426
|
-
if old.nil?
|
463
|
+
if old.nil? # no matching tuple found
|
427
464
|
buf[key] = tup
|
428
|
-
|
429
|
-
|
465
|
+
return
|
466
|
+
end
|
467
|
+
return if tup == old # ignore duplicates
|
468
|
+
|
469
|
+
# Check for PK violation
|
470
|
+
@val_colnums.each do |i|
|
471
|
+
old_v = old[i]
|
472
|
+
new_v = tup[i]
|
473
|
+
|
474
|
+
unless old_v == new_v || (is_lattice_val(old_v) && is_lattice_val(new_v))
|
475
|
+
raise_pk_error(tup, old)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Construct new tuple version. We discard the newly-constructed tuple if
|
480
|
+
# merging every lattice field doesn't yield a new value.
|
481
|
+
new_t = null_tuple
|
482
|
+
saw_change = false
|
483
|
+
@val_colnums.each do |i|
|
484
|
+
if old[i] == tup[i]
|
485
|
+
new_t[i] = old[i]
|
486
|
+
else
|
487
|
+
new_t[i] = old[i].merge(tup[i])
|
488
|
+
saw_change = true if new_t[i].reveal != old[i].reveal
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
if saw_change
|
493
|
+
@key_colnums.each {|k| new_t[k] = old[k]}
|
494
|
+
buf[key] = new_t
|
430
495
|
end
|
431
496
|
end
|
432
497
|
|
@@ -508,6 +573,12 @@ module Bud
|
|
508
573
|
add_merge_target
|
509
574
|
tbl = register_coll_expr(o)
|
510
575
|
tbl.pro.wire_to self
|
576
|
+
elsif o.class <= Bud::LatticePushElement
|
577
|
+
add_merge_target
|
578
|
+
o.wire_to self
|
579
|
+
elsif o.class <= Bud::LatticeWrapper
|
580
|
+
add_merge_target
|
581
|
+
o.to_push_elem.wire_to self
|
511
582
|
else
|
512
583
|
unless o.nil?
|
513
584
|
o = o.uniq.compact if o.respond_to?(:uniq)
|
@@ -528,6 +599,10 @@ module Bud
|
|
528
599
|
public
|
529
600
|
# instantaneously merge items from collection +o+ into +buf+
|
530
601
|
def <=(collection)
|
602
|
+
unless bud_instance.toplevel.inside_tick
|
603
|
+
raise Bud::CompileError, "illegal use of <= outside of bloom block, use <+ instead"
|
604
|
+
end
|
605
|
+
|
531
606
|
merge(collection)
|
532
607
|
end
|
533
608
|
|
@@ -557,11 +632,22 @@ module Bud
|
|
557
632
|
add_merge_target
|
558
633
|
tbl = register_coll_expr(o)
|
559
634
|
tbl.pro.wire_to(self, :pending)
|
635
|
+
elsif o.class <= Bud::LatticePushElement
|
636
|
+
add_merge_target
|
637
|
+
o.wire_to(self, :pending)
|
638
|
+
elsif o.class <= Bud::LatticeWrapper
|
639
|
+
add_merge_target
|
640
|
+
o.to_push_elem.wire_to(self, :pending)
|
560
641
|
else
|
561
642
|
pending_merge(o)
|
562
643
|
end
|
563
644
|
end
|
564
645
|
|
646
|
+
superator "<~" do |o|
|
647
|
+
# Overridden when <~ is defined (i.e., channels and terminals)
|
648
|
+
raise Bud::CompileError, "#{tabname} cannot appear on the lhs of a <~ operator"
|
649
|
+
end
|
650
|
+
|
565
651
|
def tick
|
566
652
|
raise Bud::Error, "tick must be overriden in #{self.class}"
|
567
653
|
end
|
@@ -634,6 +720,7 @@ module Bud
|
|
634
720
|
self, the_schema)
|
635
721
|
toplevel.scanners[this_stratum][[oid, the_name]] = scanner
|
636
722
|
toplevel.push_sources[this_stratum][[oid, the_name]] = scanner
|
723
|
+
@scanner_cnt += 1
|
637
724
|
end
|
638
725
|
return toplevel.scanners[this_stratum][[oid, the_name]]
|
639
726
|
end
|
@@ -656,10 +743,10 @@ module Bud
|
|
656
743
|
# for each distinct value of the grouping key columns, return the items in that group
|
657
744
|
# that have the value of the exemplary aggregate +aggname+
|
658
745
|
public
|
659
|
-
def argagg(aggname, gbkey_cols, collection)
|
746
|
+
def argagg(aggname, gbkey_cols, collection, &blk)
|
660
747
|
elem = to_push_elem
|
661
748
|
gbkey_cols = gbkey_cols.map{|k| canonicalize_col(k)} unless gbkey_cols.nil?
|
662
|
-
retval = elem.argagg(aggname, gbkey_cols, canonicalize_col(collection))
|
749
|
+
retval = elem.argagg(aggname, gbkey_cols, canonicalize_col(collection), &blk)
|
663
750
|
# PushElement inherits the schema accessors from this Collection
|
664
751
|
retval.extend @cols_access
|
665
752
|
retval
|
@@ -669,37 +756,46 @@ module Bud
|
|
669
756
|
# that group that have the minimum value of the attribute +col+. Note that
|
670
757
|
# multiple tuples might be returned.
|
671
758
|
public
|
672
|
-
def argmin(gbkey_cols, col)
|
673
|
-
argagg(:min, gbkey_cols, col)
|
759
|
+
def argmin(gbkey_cols, col, &blk)
|
760
|
+
argagg(:min, gbkey_cols, col, &blk)
|
674
761
|
end
|
675
762
|
|
676
763
|
# for each distinct value of the grouping key columns, return the items in
|
677
764
|
# that group that have the maximum value of the attribute +col+. Note that
|
678
765
|
# multiple tuples might be returned.
|
679
766
|
public
|
680
|
-
def argmax(gbkey_cols, col)
|
681
|
-
argagg(:max, gbkey_cols, col)
|
767
|
+
def argmax(gbkey_cols, col, &blk)
|
768
|
+
argagg(:max, gbkey_cols, col, &blk)
|
682
769
|
end
|
683
770
|
|
684
771
|
# form a collection containing all pairs of items in +self+ and items in
|
685
772
|
# +collection+
|
686
773
|
public
|
687
774
|
def *(collection)
|
688
|
-
|
689
|
-
|
775
|
+
return to_push_elem.join(collection)
|
776
|
+
end
|
777
|
+
|
778
|
+
def prep_aggpairs(aggpairs)
|
779
|
+
aggpairs.map do |ap|
|
780
|
+
agg, *rest = ap
|
781
|
+
if rest.empty?
|
782
|
+
[agg]
|
783
|
+
else
|
784
|
+
[agg] + rest.map {|c| canonicalize_col(c)}
|
785
|
+
end
|
786
|
+
end
|
690
787
|
end
|
691
788
|
|
692
789
|
def group(key_cols, *aggpairs, &blk)
|
693
|
-
elem = to_push_elem
|
694
790
|
key_cols = key_cols.map{|k| canonicalize_col(k)} unless key_cols.nil?
|
695
|
-
aggpairs = aggpairs
|
696
|
-
return
|
791
|
+
aggpairs = prep_aggpairs(aggpairs)
|
792
|
+
return to_push_elem.group(key_cols, *aggpairs, &blk)
|
697
793
|
end
|
698
794
|
|
699
795
|
def notin(collection, *preds, &blk)
|
700
796
|
elem1 = to_push_elem
|
701
797
|
elem2 = collection.to_push_elem
|
702
|
-
return elem1.notin(elem2, preds, &blk)
|
798
|
+
return elem1.notin(elem2, *preds, &blk)
|
703
799
|
end
|
704
800
|
|
705
801
|
def canonicalize_col(col)
|
@@ -725,6 +821,7 @@ module Bud
|
|
725
821
|
false
|
726
822
|
end
|
727
823
|
|
824
|
+
# tick_delta for scratches is @storage, so iterate over that instead
|
728
825
|
public
|
729
826
|
def each_tick_delta(&block)
|
730
827
|
@storage.each_value(&block)
|
@@ -751,6 +848,11 @@ module Bud
|
|
751
848
|
public
|
752
849
|
def add_rescan_invalidate(rescan, invalidate)
|
753
850
|
srcs = non_temporal_predecessors
|
851
|
+
|
852
|
+
# XXX: this seems wrong. We might rescan a node for many reasons (e.g.,
|
853
|
+
# because another one of the node's outputs needs to be refilled). We only
|
854
|
+
# need to invalidate + rescan this scratch if one of the inputs to this
|
855
|
+
# collection is *invalidated*.
|
754
856
|
if srcs.any? {|e| rescan.member? e}
|
755
857
|
invalidate << self
|
756
858
|
rescan.merge(srcs)
|
@@ -786,6 +888,8 @@ module Bud
|
|
786
888
|
given_schema ||= [:@address, :val]
|
787
889
|
@is_loopback = loopback
|
788
890
|
@locspec_idx = nil
|
891
|
+
@wire_buf = StringIO.new
|
892
|
+
@packer = MessagePack::Packer.new(@wire_buf)
|
789
893
|
|
790
894
|
# We're going to mutate the caller's given_schema (to remove the location
|
791
895
|
# specifier), so make a deep copy first. We also save a ref to the
|
@@ -870,28 +974,66 @@ module Bud
|
|
870
974
|
raise Bud::Error, "'#{t[@locspec_idx]}', channel '#{@tabname}'" if the_locspec[0].nil? or the_locspec[1].nil? or the_locspec[0] == '' or the_locspec[1] == ''
|
871
975
|
end
|
872
976
|
puts "channel #{qualified_tabname}.send: #{t}" if $BUD_DEBUG
|
873
|
-
|
977
|
+
|
978
|
+
# Convert the tuple into a suitable wire format. Because MsgPack cannot
|
979
|
+
# marshal arbitrary Ruby objects that we need to send via channels (in
|
980
|
+
# particular, lattice values and Class instances), we first encode such
|
981
|
+
# values using Marshal, and then encode the entire tuple with
|
982
|
+
# MsgPack. Obviously, this is gross. The wire format also includes an
|
983
|
+
# array of indices, indicating which fields hold Marshall'd values.
|
984
|
+
@packer.write_array_header(3)
|
985
|
+
@packer.write(qualified_tabname.to_s)
|
986
|
+
# The second element, wire_tuple, is an array. We will write it one
|
987
|
+
# element at a time:
|
988
|
+
@packer.write_array_header(t.length)
|
989
|
+
@packer.flush
|
990
|
+
marshall_indexes = []
|
991
|
+
t.each_with_index do |f,i|
|
992
|
+
# Performance optimization for cases where we know that we can't
|
993
|
+
# marshal the field using MsgPack:
|
994
|
+
if [Bud::Lattice, Class].any?{|t| f.class <= t}
|
995
|
+
marshall_indexes << i
|
996
|
+
@wire_buf << Marshal.dump(f).to_msgpack
|
997
|
+
else
|
998
|
+
begin
|
999
|
+
@wire_buf << f.to_msgpack
|
1000
|
+
rescue NoMethodError
|
1001
|
+
# If MsgPack can't marshal the field, fall back to Marshal.
|
1002
|
+
# This handles fields that contain nested non-MsgPack-able
|
1003
|
+
# objects (in these cases, the entire field is Marshal'd.)
|
1004
|
+
marshall_indexes << i
|
1005
|
+
@wire_buf << Marshal.dump(f).to_msgpack
|
1006
|
+
end
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
@packer.write(marshall_indexes)
|
1010
|
+
@packer.flush
|
1011
|
+
toplevel.dsock.send_datagram(@wire_buf.string,
|
874
1012
|
the_locspec[0], the_locspec[1])
|
1013
|
+
|
1014
|
+
# Reset output buffer
|
1015
|
+
@wire_buf.rewind
|
1016
|
+
@wire_buf.truncate(0)
|
875
1017
|
end
|
876
1018
|
@pending.clear
|
877
1019
|
end
|
878
1020
|
|
879
1021
|
public
|
880
1022
|
# project to the non-address fields
|
881
|
-
def payloads
|
882
|
-
return self.pro if @is_loopback
|
883
|
-
|
884
|
-
if
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
retval = self.pro{|t| t[(@locspec_idx == 0) ? 1 : 0]}
|
1023
|
+
def payloads(&blk)
|
1024
|
+
return self.pro(&blk) if @is_loopback
|
1025
|
+
|
1026
|
+
if @payload_struct.nil?
|
1027
|
+
payload_cols = cols.dup
|
1028
|
+
payload_cols.delete_at(@locspec_idx)
|
1029
|
+
@payload_struct = Bud::TupleStruct.new(*payload_cols)
|
1030
|
+
@payload_colnums = payload_cols.map {|k| cols.index(k)}
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
retval = self.pro do |t|
|
1034
|
+
@payload_struct.new(*t.values_at(*@payload_colnums))
|
894
1035
|
end
|
1036
|
+
retval = retval.pro(&blk) unless blk.nil?
|
895
1037
|
return retval
|
896
1038
|
end
|
897
1039
|
|
@@ -903,51 +1045,40 @@ module Bud
|
|
903
1045
|
elsif o.class <= Proc
|
904
1046
|
tbl = register_coll_expr(o)
|
905
1047
|
tbl.pro.wire_to(self, :pending)
|
1048
|
+
elsif o.class <= Bud::LatticePushElement
|
1049
|
+
add_merge_target
|
1050
|
+
o.wire_to(self, :pending)
|
1051
|
+
elsif o.class <= Bud::LatticeWrapper
|
1052
|
+
add_merge_target
|
1053
|
+
o.to_push_elem.wire_to(self, :pending)
|
906
1054
|
else
|
907
1055
|
pending_merge(o)
|
908
1056
|
end
|
909
1057
|
end
|
910
1058
|
|
911
1059
|
superator "<+" do |o|
|
912
|
-
raise Bud::
|
1060
|
+
raise Bud::CompileError, "illegal use of <+ with channel '#{@tabname}' on left"
|
913
1061
|
end
|
914
1062
|
|
915
1063
|
undef merge
|
916
1064
|
|
917
1065
|
def <=(o)
|
918
|
-
raise Bud::
|
1066
|
+
raise Bud::CompileError, "illegal use of <= with channel '#{@tabname}' on left"
|
919
1067
|
end
|
920
1068
|
end
|
921
1069
|
|
922
1070
|
class BudTerminal < BudScratch # :nodoc: all
|
923
|
-
def initialize(name,
|
924
|
-
super(name, bud_instance,
|
1071
|
+
def initialize(name, bud_instance, prompt=false) # :nodoc: all
|
1072
|
+
super(name, bud_instance, [:line])
|
925
1073
|
@prompt = prompt
|
926
1074
|
end
|
927
1075
|
|
928
1076
|
public
|
929
1077
|
def start_stdin_reader # :nodoc: all
|
930
|
-
|
931
|
-
# we should add the terminal file descriptor to the EM event loop.
|
932
|
-
@reader = Thread.new do
|
1078
|
+
Thread.new do
|
933
1079
|
begin
|
934
|
-
toplevel = @bud_instance.toplevel
|
935
1080
|
while true
|
936
|
-
|
937
|
-
out_io.print("#{tabname} > ") if @prompt
|
938
|
-
|
939
|
-
in_io = toplevel.options[:stdin]
|
940
|
-
s = in_io.gets
|
941
|
-
break if s.nil? # Hit EOF
|
942
|
-
s = s.chomp if s
|
943
|
-
tup = [s]
|
944
|
-
|
945
|
-
ip = toplevel.ip
|
946
|
-
port = toplevel.port
|
947
|
-
EventMachine::schedule do
|
948
|
-
socket = EventMachine::open_datagram_socket("127.0.0.1", 0)
|
949
|
-
socket.send_datagram([tabname, tup].to_msgpack, ip, port)
|
950
|
-
end
|
1081
|
+
break unless read_line
|
951
1082
|
end
|
952
1083
|
rescue Exception
|
953
1084
|
puts "terminal reader thread failed: #{$!}"
|
@@ -957,13 +1088,37 @@ module Bud
|
|
957
1088
|
end
|
958
1089
|
end
|
959
1090
|
|
1091
|
+
# XXX: Ugly hack. Rather than sending terminal data to EM via UDP, we should
|
1092
|
+
# add the terminal file descriptor to the EM event loop.
|
1093
|
+
private
|
1094
|
+
def read_line
|
1095
|
+
get_out_io.print("#{tabname} > ") if @prompt
|
1096
|
+
|
1097
|
+
toplevel = @bud_instance.toplevel
|
1098
|
+
in_io = toplevel.options[:stdin]
|
1099
|
+
input_str = in_io.gets
|
1100
|
+
return false if input_str.nil? # Hit EOF
|
1101
|
+
input_str.chomp!
|
1102
|
+
|
1103
|
+
EventMachine::schedule do
|
1104
|
+
socket = EventMachine::open_datagram_socket("127.0.0.1", 0)
|
1105
|
+
socket.send_datagram([tabname, [input_str], []].to_msgpack,
|
1106
|
+
toplevel.ip, toplevel.port)
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
return true
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
public
|
1113
|
+
def bootstrap
|
1114
|
+
# override BudCollection; pending should not be moved into delta.
|
1115
|
+
end
|
1116
|
+
|
960
1117
|
public
|
961
1118
|
def flush #:nodoc: all
|
962
1119
|
out_io = get_out_io
|
963
|
-
@pending.each_value
|
964
|
-
|
965
|
-
out_io.flush
|
966
|
-
end
|
1120
|
+
@pending.each_value {|p| out_io.puts p[0]}
|
1121
|
+
out_io.flush
|
967
1122
|
@pending.clear
|
968
1123
|
end
|
969
1124
|
|
@@ -975,7 +1130,7 @@ module Bud
|
|
975
1130
|
public
|
976
1131
|
def tick #:nodoc: all
|
977
1132
|
unless @pending.empty?
|
978
|
-
@delta = @pending
|
1133
|
+
@delta = @pending # pending used for input tuples in this case
|
979
1134
|
@tick_delta = @pending.values
|
980
1135
|
@pending.clear
|
981
1136
|
else
|
@@ -983,8 +1138,7 @@ module Bud
|
|
983
1138
|
@delta.clear
|
984
1139
|
@tick_delta.clear
|
985
1140
|
end
|
986
|
-
@invalidated = true
|
987
|
-
raise Bud::Error, "orphaned pending tuples in terminal" unless @pending.empty?
|
1141
|
+
@invalidated = true # channels and terminals are always invalidated
|
988
1142
|
end
|
989
1143
|
|
990
1144
|
public
|
@@ -995,7 +1149,11 @@ module Bud
|
|
995
1149
|
|
996
1150
|
public
|
997
1151
|
def <=(o) #:nodoc: all
|
998
|
-
raise Bud::
|
1152
|
+
raise Bud::CompileError, "illegal use of <= with terminal '#{@tabname}' on left"
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
superator "<+" do |o|
|
1156
|
+
raise Bud::CompileError, "illegal use of <+ with terminal '#{@tabname}' on left"
|
999
1157
|
end
|
1000
1158
|
|
1001
1159
|
superator "<~" do |o|
|
@@ -1015,26 +1173,28 @@ module Bud
|
|
1015
1173
|
def get_out_io
|
1016
1174
|
rv = @bud_instance.toplevel.options[:stdout]
|
1017
1175
|
rv ||= $stdout
|
1018
|
-
|
1176
|
+
if rv.closed?
|
1177
|
+
raise Bud::Error, "attempt to write to closed terminal '#{tabname}'"
|
1178
|
+
end
|
1019
1179
|
rv
|
1020
1180
|
end
|
1021
1181
|
end
|
1022
1182
|
|
1023
1183
|
class BudPeriodic < BudScratch # :nodoc: all
|
1024
1184
|
def <=(o)
|
1025
|
-
raise Bud::
|
1185
|
+
raise Bud::CompileError, "illegal use of <= with periodic '#{tabname}' on left"
|
1026
1186
|
end
|
1027
1187
|
|
1028
1188
|
superator "<~" do |o|
|
1029
|
-
raise Bud::
|
1189
|
+
raise Bud::CompileError, "illegal use of <~ with periodic '#{tabname}' on left"
|
1030
1190
|
end
|
1031
1191
|
|
1032
1192
|
superator "<-" do |o|
|
1033
|
-
raise Bud::
|
1193
|
+
raise Bud::CompileError, "illegal use of <- with periodic '#{tabname}' on left"
|
1034
1194
|
end
|
1035
1195
|
|
1036
1196
|
superator "<+" do |o|
|
1037
|
-
raise Bud::
|
1197
|
+
raise Bud::CompileError, "illegal use of <+ with periodic '#{tabname}' on left"
|
1038
1198
|
end
|
1039
1199
|
|
1040
1200
|
def tick
|
@@ -1070,8 +1230,8 @@ module Bud
|
|
1070
1230
|
public
|
1071
1231
|
def tick #:nodoc: all
|
1072
1232
|
if $BUD_DEBUG
|
1073
|
-
puts "#{tabname}.
|
1074
|
-
puts "#{tabname}.
|
1233
|
+
puts "#{tabname}.storage -= pending deletes" unless @to_delete.empty? and @to_delete_by_key.empty?
|
1234
|
+
puts "#{tabname}.delta += pending" unless @pending.empty?
|
1075
1235
|
end
|
1076
1236
|
@tick_delta.clear
|
1077
1237
|
deleted = nil
|
@@ -1099,7 +1259,9 @@ module Bud
|
|
1099
1259
|
end
|
1100
1260
|
|
1101
1261
|
def invalidated=(val)
|
1102
|
-
|
1262
|
+
# Might be reset to false at end-of-tick, but shouldn't be set to true
|
1263
|
+
raise Bud::Error, "cannot set invalidate on table '#{@tabname}'" if val
|
1264
|
+
super
|
1103
1265
|
end
|
1104
1266
|
|
1105
1267
|
def pending_delete(o)
|
@@ -1113,6 +1275,12 @@ module Bud
|
|
1113
1275
|
add_merge_target
|
1114
1276
|
tbl = register_coll_expr(o)
|
1115
1277
|
tbl.pro.wire_to(self, :delete)
|
1278
|
+
elsif o.class <= Bud::LatticePushElement
|
1279
|
+
add_merge_target
|
1280
|
+
o.wire_to(self, :delete)
|
1281
|
+
elsif o.class <= Bud::LatticeWrapper
|
1282
|
+
add_merge_target
|
1283
|
+
o.to_push_elem.wire_to(self, :delete)
|
1116
1284
|
else
|
1117
1285
|
unless o.nil?
|
1118
1286
|
o = o.uniq.compact if o.respond_to?(:uniq)
|
@@ -1150,7 +1318,7 @@ module Bud
|
|
1150
1318
|
# No cache to invalidate. Also, tables do not invalidate dependents,
|
1151
1319
|
# because their own state is not considered invalidated; that happens only
|
1152
1320
|
# if there were pending deletes at the beginning of a tick (see tick())
|
1153
|
-
puts "******** invalidate_cache called on
|
1321
|
+
puts "******** invalidate_cache called on table '#{@tabname}'" if $BUD_DEBUG
|
1154
1322
|
end
|
1155
1323
|
|
1156
1324
|
public
|
@@ -1166,11 +1334,11 @@ module Bud
|
|
1166
1334
|
|
1167
1335
|
class BudReadOnly < BudCollection # :nodoc: all
|
1168
1336
|
superator "<+" do |o|
|
1169
|
-
raise CompileError, "illegal use of <+ with read-only collection '#{@tabname}' on left"
|
1337
|
+
raise Bud::CompileError, "illegal use of <+ with read-only collection '#{@tabname}' on left"
|
1170
1338
|
end
|
1171
1339
|
public
|
1172
1340
|
def merge(o) #:nodoc: all
|
1173
|
-
raise CompileError, "illegal use of <= with read-only collection '#{@tabname}' on left"
|
1341
|
+
raise Bud::CompileError, "illegal use of <= with read-only collection '#{@tabname}' on left"
|
1174
1342
|
end
|
1175
1343
|
public
|
1176
1344
|
def invalidate_cache
|
@@ -1195,7 +1363,20 @@ module Bud
|
|
1195
1363
|
|
1196
1364
|
public
|
1197
1365
|
def each(&block)
|
1198
|
-
@expr.call
|
1366
|
+
v = @expr.call
|
1367
|
+
return if v.nil? or v == [nil]
|
1368
|
+
|
1369
|
+
# XXX: Gross hack. We want to support RHS expressions that do not
|
1370
|
+
# necessarily return BudCollections (they might instead return lattice
|
1371
|
+
# values or hashes). Since it isn't easy to distinguish between these two
|
1372
|
+
# cases statically, instead we just always use CollExpr; at runtime, if
|
1373
|
+
# the value doesn't look like a traditional Bloom collection, we don't try
|
1374
|
+
# to break it up into tuples.
|
1375
|
+
if v.class <= Array || v.class <= BudCollection
|
1376
|
+
v.each(&block)
|
1377
|
+
else
|
1378
|
+
yield v
|
1379
|
+
end
|
1199
1380
|
end
|
1200
1381
|
|
1201
1382
|
public
|
@@ -1205,13 +1386,18 @@ module Bud
|
|
1205
1386
|
end
|
1206
1387
|
|
1207
1388
|
class BudFileReader < BudReadOnly # :nodoc: all
|
1208
|
-
def initialize(name, filename,
|
1389
|
+
def initialize(name, filename, bud_instance) # :nodoc: all
|
1209
1390
|
super(name, bud_instance, {[:lineno] => [:text]})
|
1210
1391
|
@filename = filename
|
1211
1392
|
@storage = {}
|
1212
1393
|
# NEEDS A TRY/RESCUE BLOCK
|
1213
1394
|
@fd = File.open(@filename, "r")
|
1214
1395
|
@linenum = 0
|
1396
|
+
@invalidated = true
|
1397
|
+
end
|
1398
|
+
|
1399
|
+
def tick
|
1400
|
+
@invalidated = true
|
1215
1401
|
end
|
1216
1402
|
|
1217
1403
|
public
|