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