bud 0.9.4 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +23 -0
- data/bin/budlabel +63 -0
- data/bin/budtimelines +1 -1
- data/docs/cheat.md +1 -1
- data/docs/getstarted.md +8 -8
- data/examples/chat/README.md +2 -0
- data/examples/chat/chat.rb +3 -2
- data/examples/chat/chat_protocol.rb +1 -1
- data/examples/chat/chat_server.rb +3 -2
- data/lib/bud/aggs.rb +16 -2
- data/lib/bud/bud_meta.rb +19 -28
- data/lib/bud/collections.rb +157 -39
- data/lib/bud/depanalysis.rb +3 -4
- data/lib/bud/executor/elements.rb +62 -57
- data/lib/bud/executor/group.rb +35 -32
- data/lib/bud/executor/join.rb +0 -11
- data/lib/bud/graphs.rb +1 -1
- 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 +563 -0
- data/lib/bud/lattice-lib.rb +367 -0
- data/lib/bud/monkeypatch.rb +18 -8
- data/lib/bud/rewrite.rb +314 -139
- data/lib/bud/server.rb +13 -2
- data/lib/bud/source.rb +34 -18
- data/lib/bud/state.rb +90 -1
- data/lib/bud/storage/zookeeper.rb +38 -33
- data/lib/bud/viz.rb +0 -1
- data/lib/bud.rb +55 -15
- metadata +15 -8
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'bud/labeling/labeling'
|
2
|
+
|
3
|
+
module PDG
|
4
|
+
include GuardedAsync
|
5
|
+
# a bloomgraph program that plots a NM-and-async-aware PDG
|
6
|
+
state do
|
7
|
+
scratch :bodies, [:table] => [:tbl_type]
|
8
|
+
scratch :source, [:pred]
|
9
|
+
scratch :sink, [:pred]
|
10
|
+
end
|
11
|
+
|
12
|
+
bloom do
|
13
|
+
bodies <= dep{|d| [d.body, coll_type(d.body)]}
|
14
|
+
bodies <= dep{|d| [d.head, coll_type(d.head)]}
|
15
|
+
|
16
|
+
bnode <= bodies do |b|
|
17
|
+
shape = case b.tbl_type
|
18
|
+
when Bud::BudTable then "rectangle"
|
19
|
+
when Bud::LatticeWrapper then "triangle"
|
20
|
+
else "oval"
|
21
|
+
end
|
22
|
+
[b.table, {:shape => shape}]
|
23
|
+
end
|
24
|
+
|
25
|
+
bedge <= dep do |d|
|
26
|
+
line = d.label == "A" ? "dashed" : "solid"
|
27
|
+
circle = d.label == "N" ? "veeodot" : "normal"
|
28
|
+
[d.body, d.head, {:style => line, :arrowhead => circle, :penwidth => 4}]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
bloom :endpoints do
|
33
|
+
source <= t_provides do |p|
|
34
|
+
if p.input and !dep_tc.map{|d| d.head}.include? p.interface
|
35
|
+
[p.interface]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
sink <= t_provides do |p|
|
40
|
+
if !p.input and !dep_tc.map{|d| d.body}.include? p.interface
|
41
|
+
[p.interface]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
bedge <= source{|s| ["S", s.pred, {}]}
|
46
|
+
bedge <= sink{|s| [s.pred, "T", {}]}
|
47
|
+
end
|
48
|
+
|
49
|
+
bootstrap do
|
50
|
+
bnode << ["S", {:shape => "diamond", :color => "blue"}]
|
51
|
+
bnode << ["T", {:shape => "diamond", :color => "blue"}]
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bud'
|
3
|
+
|
4
|
+
module Validate
|
5
|
+
state do
|
6
|
+
scratch :dep, [:body, :head, :label]
|
7
|
+
scratch :dep_tc, [:body, :head, :members]
|
8
|
+
scratch :scc, [:pred, :cluster]
|
9
|
+
scratch :scc_raw, scc.schema
|
10
|
+
scratch :new_dep, [:body, :head, :label]
|
11
|
+
scratch :labeled_path, [:body, :head, :path, :label]
|
12
|
+
scratch :full_path, labeled_path.schema
|
13
|
+
scratch :ndn, new_dep.schema
|
14
|
+
scratch :iinterface, t_provides.schema
|
15
|
+
scratch :ointerface, t_provides.schema
|
16
|
+
scratch :iin, t_provides.schema
|
17
|
+
scratch :iout, t_provides.schema
|
18
|
+
end
|
19
|
+
|
20
|
+
bloom do
|
21
|
+
dep <= t_depends do |d|
|
22
|
+
[d.body, d.lhs, labelof(d.op, d.nm)]
|
23
|
+
end
|
24
|
+
|
25
|
+
dep_tc <= dep do |d|
|
26
|
+
[d.body, d.head, Set.new([d.body, d.head])]
|
27
|
+
end
|
28
|
+
dep_tc <= (dep * dep_tc).pairs(:head => :body) do |d, t|
|
29
|
+
[d.body, t.head, t.members | [d.head]]
|
30
|
+
end
|
31
|
+
|
32
|
+
scc_raw <= dep_tc do |d|
|
33
|
+
if d.head == d.body
|
34
|
+
[d.head, d.members.to_a.sort]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
scc <= scc_raw.reduce(Hash.new) do |memo, i|
|
39
|
+
memo[i.pred] ||= []
|
40
|
+
memo[i.pred] |= i.cluster
|
41
|
+
memo
|
42
|
+
end
|
43
|
+
|
44
|
+
new_dep <= (dep * scc * scc).combos do |d, s1, s2|
|
45
|
+
if d.head == s1.pred and d.body == s2.pred
|
46
|
+
["#{s2.cluster.join(",")}_IN", "#{s1.cluster.join(",")}_OUT", d.label]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
new_dep <= (dep * scc).pairs(:body => :pred) do |d, s|
|
50
|
+
["#{s.cluster.join(",")}_OUT", d.head, d.label] unless s.cluster.include? d.head
|
51
|
+
end
|
52
|
+
new_dep <= (dep * scc).pairs(:head => :pred) do |d, s|
|
53
|
+
[d.body, "#{s.cluster.join(",")}_IN", d.label] unless s.cluster.include? d.body
|
54
|
+
end
|
55
|
+
|
56
|
+
ndn <= dep.notin(scc, :body => :pred)
|
57
|
+
new_dep <= ndn.notin(scc, :head => :pred)
|
58
|
+
end
|
59
|
+
|
60
|
+
bloom :channel_inputs do
|
61
|
+
temp :dummy_input <= t_provides do |p|
|
62
|
+
if p.input and coll_type(p.interface) == Bud::BudChannel
|
63
|
+
[p.interface]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
dep <= dummy_input{|i| ["#{i.first}_INPUT", i.first, "A"]}
|
67
|
+
dep <= dummy_input{|i| ["#{i.first}_INPUT", i.first, "A"]}
|
68
|
+
t_provides <= dummy_input{|i| ["#{i.first}_INPUT", true]}
|
69
|
+
end
|
70
|
+
|
71
|
+
bloom :full_paths do
|
72
|
+
iin <= t_provides{|p| p if p.input}
|
73
|
+
iout <= t_provides{|p| p if !p.input}
|
74
|
+
iinterface <= iin.notin(new_dep, :interface => :head)
|
75
|
+
ointerface <= iout.notin(new_dep, :interface => :body)
|
76
|
+
|
77
|
+
labeled_path <= (new_dep * iinterface).pairs(:body => :interface) do |d, p|
|
78
|
+
[d.body, d.head, [d.body, d.head], [d.label]]
|
79
|
+
end
|
80
|
+
labeled_path <= (labeled_path * new_dep).pairs(:head => :body) do |p, d|
|
81
|
+
[p.body, d.head, p.path + [d.head], p.label + [d.label]]
|
82
|
+
end
|
83
|
+
|
84
|
+
full_path <= (labeled_path * ointerface).lefts(:head => :interface)
|
85
|
+
end
|
86
|
+
|
87
|
+
def validate
|
88
|
+
dp = Set.new
|
89
|
+
divergent_preds.each do |p|
|
90
|
+
dp.add(p.coll)
|
91
|
+
end
|
92
|
+
report = []
|
93
|
+
full_path.to_a.each do |p|
|
94
|
+
state = ["Bot"]
|
95
|
+
start_a = -1
|
96
|
+
p.label.each_with_index do |lbl, i|
|
97
|
+
if lbl == "A"
|
98
|
+
start_a = i + 1
|
99
|
+
end
|
100
|
+
os = state.first
|
101
|
+
state = do_collapse(state, [lbl])
|
102
|
+
end
|
103
|
+
if dp.include? p.head
|
104
|
+
report << (p.to_a + [:unguarded, ["D"]])
|
105
|
+
else
|
106
|
+
report << (p.to_a + [:path, state])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
return report
|
110
|
+
end
|
111
|
+
|
112
|
+
def do_collapse(left, right)
|
113
|
+
l = left.pop
|
114
|
+
r = right.shift
|
115
|
+
left + collapse(l, r) + right
|
116
|
+
end
|
117
|
+
|
118
|
+
def labelof(op, nm)
|
119
|
+
if op == "<~"
|
120
|
+
"A"
|
121
|
+
elsif nm
|
122
|
+
"N"
|
123
|
+
else
|
124
|
+
"Bot"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def collapse(left, right)
|
129
|
+
return [right] if left == 'Bot'
|
130
|
+
return [left] if right == 'Bot'
|
131
|
+
return [left] if left == right
|
132
|
+
return ['D'] if left == 'D' or right == 'D'
|
133
|
+
# CALM
|
134
|
+
return ['D'] if left == 'A' and right =~ /N/
|
135
|
+
# sometimes we cannot reduce
|
136
|
+
return [left, right]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
module GuardedAsync
|
142
|
+
include Validate
|
143
|
+
state do
|
144
|
+
scratch :meet, [:chan, :partner, :at, :lpath, :rpath]
|
145
|
+
scratch :meet_stg, meet.schema
|
146
|
+
scratch :channel_race, [:chan, :partner, :to, :guarded]
|
147
|
+
scratch :dep_tc_type, [:body, :head, :types]
|
148
|
+
scratch :divergent_preds, [:coll]
|
149
|
+
end
|
150
|
+
|
151
|
+
bloom do
|
152
|
+
dep_tc_type <= dep do |d|
|
153
|
+
btab = coll_type(d.body)
|
154
|
+
htab = coll_type(d.head)
|
155
|
+
[d.body, d.head, Set.new([btab, htab])]
|
156
|
+
end
|
157
|
+
dep_tc_type <= (dep * dep_tc_type).pairs(:head => :body) do |d, t|
|
158
|
+
htab = coll_type(d.head)
|
159
|
+
[d.body, t.head, t.types | [htab]]
|
160
|
+
end
|
161
|
+
|
162
|
+
meet_stg <= (dep_tc_type * dep_tc_type).pairs(:head => :head) do |l, r|
|
163
|
+
ltab = self.tables[l.body.to_sym]
|
164
|
+
rtab = self.tables[r.body.to_sym]
|
165
|
+
if ltab.class == Bud::BudChannel and rtab.class == Bud::BudChannel and l.body != r.body
|
166
|
+
[l.body, r.body, l.head, l.types, r.types]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
meet <= meet_stg.notin(dep_tc_type, :chan => :body, :partner => :head)
|
171
|
+
channel_race <= meet{|m| [m.chan, m.partner, m.at, guarded(m.lpath, m.rpath)]}
|
172
|
+
divergent_preds <= channel_race{|r| [r.to] unless r.guarded}
|
173
|
+
divergent_preds <= (channel_race * dep_tc_type).pairs(:to => :body){|r, t| [t.head] unless r.guarded}
|
174
|
+
end
|
175
|
+
|
176
|
+
def coll_type(nm)
|
177
|
+
tab = self.tables[nm.to_sym]
|
178
|
+
if tab.nil?
|
179
|
+
tab = self.lattices[nm.to_sym]
|
180
|
+
end
|
181
|
+
tab.class
|
182
|
+
end
|
183
|
+
|
184
|
+
def guarded(lpath, rpath)
|
185
|
+
if lpath.include? Bud::BudTable or lpath.include? Bud::LatticeWrapper
|
186
|
+
if rpath.include? Bud::BudTable or rpath.include? Bud::LatticeWrapper
|
187
|
+
return true
|
188
|
+
end
|
189
|
+
end
|
190
|
+
false
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
require 'bud/labeling/bloomgraph'
|
195
|
+
require 'bud/labeling/budplot_style'
|
196
|
+
|
197
|
+
module MetaMods
|
198
|
+
include Validate
|
199
|
+
include GuardedAsync
|
200
|
+
include BloomGraph
|
201
|
+
include PDG
|
202
|
+
end
|
203
|
+
|
204
|
+
class Label
|
205
|
+
attr_reader :f
|
206
|
+
|
207
|
+
def initialize(mod)
|
208
|
+
@report = nil
|
209
|
+
@mod = Object.const_get(mod)
|
210
|
+
if @mod.class == Class
|
211
|
+
nc = new_class_from_class(@mod)
|
212
|
+
elsif @mod.class == Module
|
213
|
+
nc = new_class(@mod)
|
214
|
+
else
|
215
|
+
raise "#{mod} neither class nor module"
|
216
|
+
end
|
217
|
+
@f = nc.new
|
218
|
+
@f.tick
|
219
|
+
end
|
220
|
+
|
221
|
+
def validate
|
222
|
+
@report = @f.validate if @report.nil?
|
223
|
+
end
|
224
|
+
|
225
|
+
def output_report
|
226
|
+
validate
|
227
|
+
rep = {}
|
228
|
+
@report.each do |from, to, path, labels, reason, final|
|
229
|
+
rep[to] ||= "Bot"
|
230
|
+
rep[to] = disjunction(rep[to], final.last)
|
231
|
+
end
|
232
|
+
rep
|
233
|
+
end
|
234
|
+
|
235
|
+
def path_report
|
236
|
+
validate
|
237
|
+
zips = {}
|
238
|
+
@report.each do |from, to, path, labels, reason, final|
|
239
|
+
zips[to] ||= {}
|
240
|
+
zips[to][from] ||= "Bot"
|
241
|
+
zips[to][from] = disjunction(zips[to][from], final.last)
|
242
|
+
end
|
243
|
+
zips
|
244
|
+
end
|
245
|
+
|
246
|
+
def disjunction(l, r)
|
247
|
+
both = [l, r]
|
248
|
+
if both.include? "D"
|
249
|
+
"D"
|
250
|
+
elsif both.include? "N"
|
251
|
+
if both.include? "A"
|
252
|
+
return "D"
|
253
|
+
else
|
254
|
+
return "N"
|
255
|
+
end
|
256
|
+
elsif both.include? "A"
|
257
|
+
return "A"
|
258
|
+
else
|
259
|
+
return "Bot"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def new_class(mod)
|
264
|
+
Class.new do
|
265
|
+
include Bud
|
266
|
+
include MetaMods
|
267
|
+
include mod
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def new_class_from_class(cls)
|
272
|
+
Class.new(cls) do
|
273
|
+
include MetaMods
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def internal_tabs
|
278
|
+
cls = Class.new do
|
279
|
+
include Bud
|
280
|
+
include MetaMods
|
281
|
+
end
|
282
|
+
cls.new.tables.keys
|
283
|
+
end
|
284
|
+
|
285
|
+
def write_graph(fmt=:pdf)
|
286
|
+
f.finish(internal_tabs, "#{@mod.to_s}.#{fmt}", fmt)
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,563 @@
|
|
1
|
+
require 'bud/executor/elements'
|
2
|
+
|
3
|
+
class Bud::Lattice
|
4
|
+
@@lattice_kinds = {}
|
5
|
+
@@global_morphs = Set.new
|
6
|
+
@@global_mfuncs = Set.new
|
7
|
+
|
8
|
+
def self.wrapper_name(name)
|
9
|
+
if @wrapper_name
|
10
|
+
raise Bud::CompileError, "lattice #{self.name} has multiple wrapper names"
|
11
|
+
end
|
12
|
+
if @@lattice_kinds.has_key? name
|
13
|
+
raise Bud::CompileError, "duplicate lattice definition: #{name}"
|
14
|
+
end
|
15
|
+
@@lattice_kinds[name] = self
|
16
|
+
@wrapper_name = name
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.lattice_kinds
|
20
|
+
@@lattice_kinds
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.wrapper
|
24
|
+
@wrapper_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.morph(name, &block)
|
28
|
+
if mfuncs.include?(name) || @@global_mfuncs.include?(name)
|
29
|
+
raise Bud::CompileError, "#{name} declared as both monotone and morph"
|
30
|
+
end
|
31
|
+
@morphs ||= Set.new
|
32
|
+
@morphs << name
|
33
|
+
@@global_morphs << name
|
34
|
+
define_method(name, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.morphs
|
38
|
+
@morphs || Set.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.global_morphs
|
42
|
+
@@global_morphs
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.monotone(name, &block)
|
46
|
+
if morphs.include?(name) || @@global_morphs.include?(name)
|
47
|
+
raise Bud::CompileError, "#{name} declared as both monotone and morph"
|
48
|
+
end
|
49
|
+
@mfuncs ||= Set.new
|
50
|
+
@mfuncs << name
|
51
|
+
@@global_mfuncs << name
|
52
|
+
define_method(name, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.mfuncs
|
56
|
+
@mfuncs || Set.new
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.global_mfuncs
|
60
|
+
@@global_mfuncs
|
61
|
+
end
|
62
|
+
|
63
|
+
def reject_input(i, meth="initialize")
|
64
|
+
site = "#{self.class.wrapper}\##{meth}"
|
65
|
+
raise Bud::TypeError, "illegal input to #{site}: #{i.inspect}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# The default equality semantics for lattice objects is based on reveal. Note
|
69
|
+
# that this isn't always appropriate: if the intended equality semantics for
|
70
|
+
# the lattice type differ from the equality semantics of the object returned
|
71
|
+
# by reveal (e.g., a set lattice might return an array with an unpredictable
|
72
|
+
# order), the lattice type should override this behavior.
|
73
|
+
def ==(o)
|
74
|
+
return false unless o.kind_of? Bud::Lattice
|
75
|
+
return reveal == o.reveal
|
76
|
+
end
|
77
|
+
|
78
|
+
def eql?(o)
|
79
|
+
return self == o
|
80
|
+
end
|
81
|
+
|
82
|
+
# Ensure hashing and equality semantics are consistent.
|
83
|
+
def hash
|
84
|
+
reveal.hash
|
85
|
+
end
|
86
|
+
|
87
|
+
# Return the state valued associated with a lattice instance. Note that this
|
88
|
+
# is non-monotonic when invoked from user code; it should be used with care by
|
89
|
+
# framework code.
|
90
|
+
def reveal
|
91
|
+
@v
|
92
|
+
end
|
93
|
+
|
94
|
+
def inspect
|
95
|
+
"<#{self.class.wrapper}: #{reveal.inspect}>"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Construct a new instance of the current class that wraps "new_v". We assume
|
99
|
+
# that new_v is already a legal input value for the class, so we can bypass
|
100
|
+
# the class's normal initializer -- this avoids redundant error checks.
|
101
|
+
def wrap_unsafe(new_v)
|
102
|
+
rv = self.class.new
|
103
|
+
rv.instance_variable_set('@v', new_v)
|
104
|
+
rv
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# TODO:
|
109
|
+
# * merge logic for set-oriented collections
|
110
|
+
# * invalidation/rescan/non-monotonic stuff?
|
111
|
+
# * expressions on RHS ("CollExpr")
|
112
|
+
class Bud::LatticePushElement
|
113
|
+
attr_reader :wired_by, :outputs
|
114
|
+
attr_accessor :invalidated, :rescan
|
115
|
+
|
116
|
+
def initialize(bud_instance)
|
117
|
+
@bud_instance = bud_instance
|
118
|
+
@wired_by = []
|
119
|
+
@outputs = []
|
120
|
+
@pendings = []
|
121
|
+
@invalidated = true
|
122
|
+
@rescan = true
|
123
|
+
end
|
124
|
+
|
125
|
+
def wire_to(element, kind=:output)
|
126
|
+
case kind
|
127
|
+
when :output
|
128
|
+
@outputs << element
|
129
|
+
when :pending
|
130
|
+
@pendings << element
|
131
|
+
else
|
132
|
+
raise Bud::Error, "unrecognized wiring kind: #{kind}"
|
133
|
+
end
|
134
|
+
|
135
|
+
element.wired_by << self
|
136
|
+
end
|
137
|
+
|
138
|
+
def check_wiring
|
139
|
+
if @outputs.empty? and @pendings.empty?
|
140
|
+
raise Bud::Error, "no output specified for #{inspect}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def print_wiring(depth=0, accum="")
|
145
|
+
puts "#{' ' * depth}#{accum} #{inspect}"
|
146
|
+
|
147
|
+
[@outputs, @pendings].each do |buf|
|
148
|
+
if buf == @outputs
|
149
|
+
next_accum = "=> "
|
150
|
+
else
|
151
|
+
next_accum = "+> "
|
152
|
+
end
|
153
|
+
|
154
|
+
buf.each do |o|
|
155
|
+
if o.respond_to? :print_wiring
|
156
|
+
o.print_wiring(depth + 1, next_accum)
|
157
|
+
else
|
158
|
+
puts "#{' ' * (depth + 1)}#{next_accum} #{o.inspect}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def inspect
|
165
|
+
"#{self.class}:#{self.object_id.to_s(16)}"
|
166
|
+
end
|
167
|
+
|
168
|
+
def wirings
|
169
|
+
@outputs + @pendings
|
170
|
+
end
|
171
|
+
|
172
|
+
def method_missing(meth, *args, &blk)
|
173
|
+
if @bud_instance.wiring?
|
174
|
+
Bud::PushApplyMethod.new(@bud_instance, self, meth, args, blk)
|
175
|
+
else
|
176
|
+
super
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Push-based dataflow
|
181
|
+
def insert(v, source)
|
182
|
+
push_out(v)
|
183
|
+
end
|
184
|
+
|
185
|
+
def push_out(v)
|
186
|
+
@outputs.each do |o|
|
187
|
+
# If we're emitting outputs to a traditional Bloom collection, merge
|
188
|
+
# operators (e.g., <=, <+) take a collection of tuples, so we need to
|
189
|
+
# convert the lattice value into a collection of tuple-like values. For
|
190
|
+
# now, we hardcode a single way to do this: we simply assume the value
|
191
|
+
# embedded inside the lattice is Enumerable. We also allow lattice
|
192
|
+
# morphisms to just produce Enumerable values directly, so we don't call
|
193
|
+
# reveal in that case.
|
194
|
+
# XXX: rethink this.
|
195
|
+
if o.class <= Bud::BudCollection
|
196
|
+
o <= (v.class <= Bud::Lattice ? v.reveal : v)
|
197
|
+
else
|
198
|
+
o.insert(v, self)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
@pendings.each do |o|
|
202
|
+
if o.class <= Bud::BudCollection
|
203
|
+
o.pending_merge(v.class <= Bud::Lattice ? v.reveal : v)
|
204
|
+
else
|
205
|
+
o <+ v
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def flush
|
211
|
+
end
|
212
|
+
|
213
|
+
def stratum_end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Rescan and invalidation
|
217
|
+
def add_rescan_invalidate(rescan, invalidate)
|
218
|
+
end
|
219
|
+
|
220
|
+
def invalidate_at_tick(rescan, invalidate)
|
221
|
+
end
|
222
|
+
|
223
|
+
def invalidate_cache
|
224
|
+
end
|
225
|
+
|
226
|
+
# Tick (delta processing)
|
227
|
+
def tick
|
228
|
+
end
|
229
|
+
|
230
|
+
def tick_deltas
|
231
|
+
end
|
232
|
+
|
233
|
+
def rescan_at_tick
|
234
|
+
false
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# A push-based dataflow element that scans a lattice wrapper
|
239
|
+
class Bud::LatticeScanner < Bud::LatticePushElement
|
240
|
+
attr_reader :collection, :rescan_set, :invalidate_set
|
241
|
+
|
242
|
+
def initialize(bud_instance, collection)
|
243
|
+
super(bud_instance)
|
244
|
+
@collection = collection
|
245
|
+
@rescan_set = []
|
246
|
+
@invalidate_set = []
|
247
|
+
end
|
248
|
+
|
249
|
+
def scan(first_iter)
|
250
|
+
if first_iter || @bud_instance.options[:disable_lattice_semi_naive]
|
251
|
+
push_out(@collection.current_value)
|
252
|
+
else
|
253
|
+
push_out(@collection.current_delta)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def inspect
|
258
|
+
"#{super} [#{collection.qualified_tabname}]"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class Bud::LatticeWrapper; end
|
263
|
+
|
264
|
+
# A push-based dataflow element that applies a method to a lattice value
|
265
|
+
class Bud::PushApplyMethod < Bud::LatticePushElement
|
266
|
+
SOURCE_TYPES = [Bud::LatticeWrapper, Bud::BudCollection,
|
267
|
+
Bud::LatticePushElement, Bud::PushElement]
|
268
|
+
|
269
|
+
def initialize(bud_instance, recv, meth, args, blk)
|
270
|
+
super(bud_instance)
|
271
|
+
@recv = recv
|
272
|
+
@meth = meth
|
273
|
+
@blk = blk
|
274
|
+
@args = args.dup
|
275
|
+
@is_morph = Bud::Lattice.global_morphs.include? @meth
|
276
|
+
@recv_is_scanner = @recv.kind_of? Bud::LatticeScanner
|
277
|
+
|
278
|
+
recv.wire_to(self, :output)
|
279
|
+
bud_instance.push_elems[[self.object_id, recv, meth, blk]] = self
|
280
|
+
|
281
|
+
# Arguments that are normal Ruby values are assumed to remain invariant as
|
282
|
+
# rule evaluation progresses; hence, we just pass along those values when
|
283
|
+
# invoking the function. Arguments that are derived from lattices or
|
284
|
+
# collections might change; hence, we need to wire up the push dataflow to
|
285
|
+
# have the current values of the function's arguments passed to this node.
|
286
|
+
|
287
|
+
# Map from input node to a list of indexes; the indexes identify the
|
288
|
+
# positions in the args array that should be filled with the node's value
|
289
|
+
@input_sources = {}
|
290
|
+
|
291
|
+
# Similarly, map from input node to a cached value -- this is the last value
|
292
|
+
# we've seen from this input. If the input gave us a delta, we merge
|
293
|
+
# together all the deltas we've seen and cache the resulting value. XXX: In
|
294
|
+
# the common case that the input is a scanner over a lattice wrapper, this
|
295
|
+
# means we do redundant work merging together deltas.
|
296
|
+
@input_caches = {}
|
297
|
+
|
298
|
+
# Inputs for which we haven't seen a value yet.
|
299
|
+
@waiting_for_input = Set.new
|
300
|
+
@recv_cache = nil
|
301
|
+
@seen_recv = false
|
302
|
+
|
303
|
+
@args.each_with_index do |a, i|
|
304
|
+
if SOURCE_TYPES.any?{|s| a.kind_of? s}
|
305
|
+
if a.kind_of? Bud::LatticeWrapper
|
306
|
+
a = a.to_push_elem
|
307
|
+
end
|
308
|
+
a.wire_to(self, :output)
|
309
|
+
@input_sources[a] ||= []
|
310
|
+
@input_sources[a] << i
|
311
|
+
@waiting_for_input << a
|
312
|
+
@args[i] = nil # Substitute actual value before calling method
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
@seen_all_inputs = @waiting_for_input.empty?
|
317
|
+
end
|
318
|
+
|
319
|
+
def insert(v, source)
|
320
|
+
if source == @recv
|
321
|
+
if @seen_recv
|
322
|
+
# Update the cached value for the method receiver. Note that if we're
|
323
|
+
# applying a method directly to a LatticeScanner (i.e., method applied
|
324
|
+
# to lattice wrapper), we can avoid maintaining a separate cache and
|
325
|
+
# instead use the wrapper's current value.
|
326
|
+
if @recv_is_scanner
|
327
|
+
@recv_cache = @recv.collection.current_value
|
328
|
+
else
|
329
|
+
@recv_cache = @recv_cache.merge(v)
|
330
|
+
end
|
331
|
+
else
|
332
|
+
@recv_cache = v
|
333
|
+
end
|
334
|
+
@seen_recv = true
|
335
|
+
if @seen_all_inputs
|
336
|
+
if @is_morph
|
337
|
+
recv_val = v
|
338
|
+
else
|
339
|
+
recv_val = @recv_cache
|
340
|
+
end
|
341
|
+
res = recv_val.send(@meth, *@args, &@blk)
|
342
|
+
push_out(res)
|
343
|
+
end
|
344
|
+
else
|
345
|
+
arg_indexes = @input_sources[source]
|
346
|
+
raise Bud::Error, "unknown input #{source}" if arg_indexes.nil?
|
347
|
+
arg_val = v
|
348
|
+
unless @is_morph
|
349
|
+
if @input_caches[source]
|
350
|
+
arg_val = @input_caches[source].merge(arg_val)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
arg_indexes.each do |i|
|
354
|
+
@args[i] = arg_val
|
355
|
+
end
|
356
|
+
|
357
|
+
unless @seen_all_inputs
|
358
|
+
@waiting_for_input.delete(source)
|
359
|
+
@seen_all_inputs = @waiting_for_input.empty?
|
360
|
+
end
|
361
|
+
|
362
|
+
if @seen_all_inputs && @seen_recv
|
363
|
+
res = @recv_cache.send(@meth, *@args, &@blk)
|
364
|
+
push_out(res)
|
365
|
+
end
|
366
|
+
|
367
|
+
if @input_caches.has_key? source
|
368
|
+
@input_caches[source] = @input_caches[source].merge(v)
|
369
|
+
else
|
370
|
+
@input_caches[source] = v
|
371
|
+
end
|
372
|
+
arg_indexes.each do |i|
|
373
|
+
@args[i] = @input_caches[source]
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def inspect
|
379
|
+
"#{super} [#{@meth}]"
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
class Bud::LatticeWrapper
|
384
|
+
attr_reader :tabname, :wired_by, :rescan_on_merge
|
385
|
+
attr_accessor :accumulate_tick_deltas
|
386
|
+
|
387
|
+
def initialize(tabname, klass, bud_i)
|
388
|
+
@tabname = tabname
|
389
|
+
@klass = klass
|
390
|
+
@bud_instance = bud_i
|
391
|
+
@wired_by = []
|
392
|
+
@rescan_on_merge = Set.new
|
393
|
+
end
|
394
|
+
|
395
|
+
def qualified_tabname
|
396
|
+
@qualified_tabname ||= @bud_instance.toplevel? ? @tabname : "#{@bud_instance.qualified_name}.#{@tabname}".to_sym
|
397
|
+
end
|
398
|
+
|
399
|
+
def invalidate_at_tick
|
400
|
+
false
|
401
|
+
end
|
402
|
+
|
403
|
+
def current_value
|
404
|
+
@storage ||= @klass.new
|
405
|
+
@storage
|
406
|
+
end
|
407
|
+
|
408
|
+
def current_delta
|
409
|
+
@delta ||= @klass.new
|
410
|
+
@delta
|
411
|
+
end
|
412
|
+
|
413
|
+
def current_new_delta
|
414
|
+
@new_delta ||= @klass.new
|
415
|
+
@new_delta
|
416
|
+
end
|
417
|
+
|
418
|
+
def current_pending
|
419
|
+
@pending ||= @klass.new
|
420
|
+
@pending
|
421
|
+
end
|
422
|
+
|
423
|
+
def do_merge(lhs, rhs)
|
424
|
+
unless lhs.class <= Bud::Lattice
|
425
|
+
raise Bud::Error, "unexpected merge input: #{lhs.class}"
|
426
|
+
end
|
427
|
+
return lhs if rhs.nil?
|
428
|
+
|
429
|
+
unless rhs.class <= @klass
|
430
|
+
rhs = @klass.new(rhs)
|
431
|
+
end
|
432
|
+
rv = lhs.merge(rhs)
|
433
|
+
unless rv.class <= Bud::Lattice
|
434
|
+
raise Bud::Error, "#{lhs.class}\#merge did not return lattice value: #{rv.inspect}"
|
435
|
+
end
|
436
|
+
rv
|
437
|
+
end
|
438
|
+
|
439
|
+
def setup_wiring(input, kind)
|
440
|
+
if input.class <= Bud::LatticeWrapper
|
441
|
+
input.to_push_elem.wire_to(self, kind)
|
442
|
+
elsif (input.class <= Bud::LatticePushElement || input.class <= Bud::PushElement)
|
443
|
+
input.wire_to(self, kind)
|
444
|
+
elsif input.class <= Bud::BudCollection
|
445
|
+
input.pro.wire_to(self, kind)
|
446
|
+
elsif input.class <= Proc
|
447
|
+
tbl = register_coll_expr(input)
|
448
|
+
tbl.pro.wire_to(self, kind)
|
449
|
+
else
|
450
|
+
raise Bud::Error, "unrecognized wiring input: #{input}"
|
451
|
+
end
|
452
|
+
|
453
|
+
add_merge_target
|
454
|
+
end
|
455
|
+
|
456
|
+
private
|
457
|
+
def register_coll_expr(expr)
|
458
|
+
name = "expr_#{expr.object_id}".to_sym
|
459
|
+
@bud_instance.coll_expr(name, expr, nil)
|
460
|
+
@bud_instance.send(name)
|
461
|
+
end
|
462
|
+
|
463
|
+
# Merge "i" into @new_delta
|
464
|
+
public
|
465
|
+
def insert(i, source)
|
466
|
+
@new_delta = do_merge(current_new_delta, i)
|
467
|
+
end
|
468
|
+
|
469
|
+
def <=(i)
|
470
|
+
if @bud_instance.wiring?
|
471
|
+
setup_wiring(i, :output)
|
472
|
+
else
|
473
|
+
@new_delta = do_merge(current_new_delta, i)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
superator "<+" do |i|
|
478
|
+
if @bud_instance.wiring?
|
479
|
+
setup_wiring(i, :pending)
|
480
|
+
else
|
481
|
+
@pending = do_merge(current_pending, i)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
# XXX: refactor with BudCollection to avoid duplication of code
|
486
|
+
def add_merge_target
|
487
|
+
toplevel = @bud_instance.toplevel
|
488
|
+
if toplevel.done_bootstrap
|
489
|
+
toplevel.merge_targets[toplevel.this_stratum] << self
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
def to_push_elem
|
494
|
+
toplevel = @bud_instance.toplevel
|
495
|
+
this_stratum = toplevel.this_stratum
|
496
|
+
oid = self.object_id
|
497
|
+
unless toplevel.scanners[this_stratum][[oid, @tabname]]
|
498
|
+
scanner = Bud::LatticeScanner.new(@bud_instance, self)
|
499
|
+
toplevel.scanners[this_stratum][[oid, @tabname]] = scanner
|
500
|
+
toplevel.push_sources[this_stratum][[oid, @tabname]] = scanner
|
501
|
+
end
|
502
|
+
return toplevel.scanners[this_stratum][[oid, @tabname]]
|
503
|
+
end
|
504
|
+
|
505
|
+
def flush_deltas
|
506
|
+
end
|
507
|
+
|
508
|
+
def add_rescan_invalidate(rescan, invalidate)
|
509
|
+
end
|
510
|
+
|
511
|
+
def method_missing(meth, *args, &blk)
|
512
|
+
# If we're invoking a lattice method and we're currently wiring up the
|
513
|
+
# dataflow, wire up a dataflow element to invoke the given method.
|
514
|
+
if @bud_instance.wiring?
|
515
|
+
pusher = to_push_elem
|
516
|
+
Bud::PushApplyMethod.new(@bud_instance, pusher, meth, args, blk)
|
517
|
+
else
|
518
|
+
super
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
def bootstrap
|
523
|
+
@storage = do_merge(current_value, @pending)
|
524
|
+
@pending = nil
|
525
|
+
end
|
526
|
+
|
527
|
+
def tick
|
528
|
+
if @new_delta
|
529
|
+
raise Bud::Error, "orphaned delta value for lattice #{@tabname}: #{@new_delta.inspect}"
|
530
|
+
end
|
531
|
+
merge_to_storage(@pending)
|
532
|
+
@pending = nil
|
533
|
+
@delta = nil
|
534
|
+
end
|
535
|
+
|
536
|
+
def merge_to_storage(v)
|
537
|
+
m = do_merge(current_value, v)
|
538
|
+
if m != current_value
|
539
|
+
@storage = m
|
540
|
+
@rescan_on_merge.each do |e|
|
541
|
+
if e.kind_of? Bud::ScannerElement
|
542
|
+
e.force_rescan = true
|
543
|
+
else
|
544
|
+
e.rescan = true
|
545
|
+
end
|
546
|
+
end
|
547
|
+
return true
|
548
|
+
else
|
549
|
+
return false
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def tick_deltas
|
554
|
+
result = merge_to_storage(@new_delta)
|
555
|
+
@delta = @new_delta
|
556
|
+
@new_delta = nil
|
557
|
+
return result
|
558
|
+
end
|
559
|
+
|
560
|
+
def inspect
|
561
|
+
"{#{@tabname}, #{current_value.inspect}}"
|
562
|
+
end
|
563
|
+
end
|