bud 0.9.4 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/History.txt +106 -0
- data/README.md +6 -4
- data/Rakefile +91 -0
- data/bin/budlabel +63 -0
- data/bin/budplot +18 -8
- data/bin/budtimelines +2 -2
- data/bin/budvis +7 -1
- data/docs/README.md +8 -17
- data/docs/cheat.md +112 -13
- data/docs/getstarted.md +97 -84
- data/docs/operational.md +3 -3
- data/examples/basics/paths.rb +2 -2
- data/examples/chat/README.md +2 -0
- data/examples/chat/chat.rb +3 -2
- data/examples/chat/chat_protocol.rb +2 -2
- data/examples/chat/chat_server.rb +3 -2
- data/lib/bud.rb +229 -114
- data/lib/bud/aggs.rb +20 -4
- data/lib/bud/bud_meta.rb +83 -73
- data/lib/bud/collections.rb +306 -120
- data/lib/bud/depanalysis.rb +3 -4
- data/lib/bud/executor/README.rescan +2 -1
- data/lib/bud/executor/elements.rb +96 -95
- data/lib/bud/executor/group.rb +35 -32
- data/lib/bud/executor/join.rb +164 -183
- data/lib/bud/graphs.rb +3 -3
- data/lib/bud/labeling/bloomgraph.rb +47 -0
- data/lib/bud/labeling/budplot_style.rb +53 -0
- data/lib/bud/labeling/labeling.rb +288 -0
- data/lib/bud/lattice-core.rb +595 -0
- data/lib/bud/lattice-lib.rb +422 -0
- data/lib/bud/monkeypatch.rb +68 -32
- data/lib/bud/rebl.rb +28 -10
- data/lib/bud/rewrite.rb +361 -152
- data/lib/bud/server.rb +16 -8
- data/lib/bud/source.rb +21 -18
- data/lib/bud/state.rb +93 -4
- data/lib/bud/storage/zookeeper.rb +45 -33
- data/lib/bud/version.rb +3 -0
- data/lib/bud/viz.rb +10 -12
- data/lib/bud/viz_util.rb +8 -3
- metadata +107 -108
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
|