bud 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|