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/rebl.rb
CHANGED
@@ -4,7 +4,9 @@ require 'rubygems'
|
|
4
4
|
require 'bud'
|
5
5
|
require 'abbrev'
|
6
6
|
require 'tempfile'
|
7
|
-
|
7
|
+
|
8
|
+
TABLE_TYPES = ["table", "scratch", "channel", "loopback", "periodic",
|
9
|
+
"sync", "store", "interface", "interfaces"]
|
8
10
|
|
9
11
|
# The class to which rebl adds user-specified rules and declarations.
|
10
12
|
class ReblBase
|
@@ -93,10 +95,13 @@ class ReblShell
|
|
93
95
|
|
94
96
|
# One step of the rebl shell loop: processes one rebl shell line from stdin
|
95
97
|
# and returns. May raise an Exception.
|
96
|
-
def self.rebl_loop(lib,noreadline=false)
|
98
|
+
def self.rebl_loop(lib, noreadline=false)
|
97
99
|
begin
|
98
|
-
|
99
|
-
|
100
|
+
if noreadline
|
101
|
+
line = gets
|
102
|
+
else
|
103
|
+
line = Readline::readline('rebl> ')
|
104
|
+
end
|
100
105
|
do_exit if line.nil?
|
101
106
|
line.strip!
|
102
107
|
return if line.empty?
|
@@ -110,7 +115,7 @@ class ReblShell
|
|
110
115
|
else
|
111
116
|
puts "invalid command or ambiguous command prefix"
|
112
117
|
end
|
113
|
-
elsif
|
118
|
+
elsif is_collection? split_line[0]
|
114
119
|
# Collection
|
115
120
|
lib.add_collection(line)
|
116
121
|
else
|
@@ -186,6 +191,12 @@ class ReblShell
|
|
186
191
|
puts "\n" + @@exit_message
|
187
192
|
exit!
|
188
193
|
end
|
194
|
+
|
195
|
+
# Checks if a given string refers to a collection type (one of the builtin
|
196
|
+
# collection types or a wrapper_name for a lattice).
|
197
|
+
def self.is_collection?(c)
|
198
|
+
TABLE_TYPES.include?(c) || Bud::Lattice.lattice_kinds.has_key?(c.to_sym)
|
199
|
+
end
|
189
200
|
end
|
190
201
|
|
191
202
|
|
@@ -196,8 +207,8 @@ class LibRebl
|
|
196
207
|
attr_accessor :rules, :state
|
197
208
|
attr_reader :ip, :port, :rebl_class_inst
|
198
209
|
@@builtin_tables = [:stdio, :periodics_tbl, :halt, :localtick,
|
199
|
-
:t_depends, :t_cycle, :t_provides, :
|
200
|
-
:t_stratum, :t_underspecified,
|
210
|
+
:t_depends, :t_cycle, :t_provides, :t_rule_stratum,
|
211
|
+
:t_rules, :t_stratum, :t_underspecified,
|
201
212
|
:t_table_info, :t_table_schema, :rebl_breakpoint]
|
202
213
|
@@classid = 0
|
203
214
|
|
@@ -233,11 +244,14 @@ class LibRebl
|
|
233
244
|
def dump(c)
|
234
245
|
if c.nil?
|
235
246
|
puts "Error: dump must be passed a collection name"
|
236
|
-
elsif
|
237
|
-
puts "Error: non-existent collection \"#{c}\""
|
238
|
-
else
|
247
|
+
elsif @rebl_class_inst.tables.has_key? c.to_sym
|
239
248
|
tups = @rebl_class_inst.tables[c.to_sym].to_a.sort
|
240
249
|
puts(tups.empty? ? "(empty)" : tups.sort.map{|t| "#{t}"}.join("\n"))
|
250
|
+
elsif @rebl_class_inst.lattices.has_key? c.to_sym
|
251
|
+
val = @rebl_class_inst.lattices[c.to_sym].current_value
|
252
|
+
puts val.inspect
|
253
|
+
else
|
254
|
+
puts "Error: non-existent collection \"#{c}\""
|
241
255
|
end
|
242
256
|
end
|
243
257
|
|
@@ -334,11 +348,15 @@ class LibRebl
|
|
334
348
|
end)
|
335
349
|
@rebl_class_inst.dbm_tables.merge! @old_inst.dbm_tables
|
336
350
|
@rebl_class_inst.zk_tables.merge! @old_inst.zk_tables
|
351
|
+
@rebl_class_inst.lattices.merge! @old_inst.lattices
|
337
352
|
|
338
353
|
# Fix the bud instance pointers from copied tables.
|
339
354
|
@rebl_class_inst.tables.each_value do |v|
|
340
355
|
v.bud_instance = @rebl_class_inst
|
341
356
|
end
|
357
|
+
@rebl_class_inst.lattices.each_value do |v|
|
358
|
+
v.bud_instance = @rebl_class_inst
|
359
|
+
end
|
342
360
|
end
|
343
361
|
|
344
362
|
# Run lazily in background, shutting down old instance.
|
data/lib/bud/rewrite.rb
CHANGED
@@ -1,27 +1,26 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'ruby2ruby'
|
3
|
-
require 'set'
|
4
2
|
|
5
3
|
class RuleRewriter < Ruby2Ruby # :nodoc: all
|
6
|
-
attr_accessor :
|
4
|
+
attr_accessor :rule_idx, :rules, :depends
|
7
5
|
|
8
|
-
OP_LIST =
|
9
|
-
TEMP_OP_LIST =
|
10
|
-
MONOTONE_WHITELIST =
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
OP_LIST = [:<<, :<, :<=].to_set
|
7
|
+
TEMP_OP_LIST = [:-@, :~, :+@].to_set
|
8
|
+
MONOTONE_WHITELIST = [:==, :+, :<=, :-, :<, :>, :*, :~, :+@,
|
9
|
+
:pairs, :matches, :combos, :flatten, :new,
|
10
|
+
:lefts, :rights, :map, :flat_map, :pro, :merge,
|
11
|
+
:schema, :cols, :key_cols, :val_cols, :payloads, :lambda,
|
12
|
+
:tabname, :current_value].to_set
|
15
13
|
|
16
|
-
def initialize(
|
14
|
+
def initialize(bud_instance, rule_idx)
|
17
15
|
@bud_instance = bud_instance
|
18
16
|
@tables = {}
|
19
17
|
@nm = false
|
20
|
-
@
|
18
|
+
@rule_idx = rule_idx
|
21
19
|
@collect = false
|
22
20
|
@rules = []
|
23
21
|
@depends = []
|
24
|
-
@
|
22
|
+
@iter_stack = []
|
23
|
+
@refs_in_body = Set.new
|
25
24
|
super()
|
26
25
|
end
|
27
26
|
|
@@ -29,6 +28,7 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
29
28
|
def resolve(obj, prefix, name)
|
30
29
|
qn = prefix ? prefix + "." + name.to_s : name.to_s
|
31
30
|
return [:collection, qn, obj.tables[name]] if obj.tables.has_key? name
|
31
|
+
return [:lattice, qn, obj.lattices[name]] if obj.lattices.has_key? name
|
32
32
|
|
33
33
|
# does name refer to an import name?
|
34
34
|
iobj = obj.import_instance name
|
@@ -38,12 +38,13 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def exp_id_type(recv, name, args) # call only if sexp type is :call
|
41
|
-
return $not_id unless args.
|
41
|
+
return $not_id unless args.empty?
|
42
42
|
ty = $not_id
|
43
43
|
if recv
|
44
44
|
if recv.first == :call
|
45
|
-
# possibly nested reference
|
46
|
-
|
45
|
+
# possibly nested reference
|
46
|
+
# rty, rqn, .. = receiver's type, qual name etc.
|
47
|
+
rty, rqn, robj = exp_id_type(recv[1], recv[2], recv[3..-1])
|
47
48
|
ty = resolve(robj, rqn, name) if rty == :import
|
48
49
|
end
|
49
50
|
else
|
@@ -56,27 +57,94 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
56
57
|
def call_to_id(exp)
|
57
58
|
# convert a series of nested calls, a sexp of the form
|
58
59
|
# s(:call,
|
59
|
-
# s(:call, s(:call, nil, :a
|
60
|
-
# :bar
|
61
|
-
# s(:arglist)))
|
60
|
+
# s(:call, s(:call, nil, :a), :b),
|
61
|
+
# :bar))
|
62
62
|
# to the string "a.b.bar"
|
63
|
-
raise "
|
64
|
-
_, recv, op
|
63
|
+
raise Bud::CompileError, "malformed expression: #{exp}" unless exp.sexp_type == :call
|
64
|
+
_, recv, op = exp
|
65
65
|
return recv.nil? ? op.to_s : call_to_id(recv) + "." + op.to_s
|
66
66
|
end
|
67
67
|
|
68
|
+
# We want to distinguish between collection dependencies that occur in
|
69
|
+
# top-level expressions versus collections that are referenced inside rule
|
70
|
+
# bodies. We just want to set a flag when processing the :iter body, but
|
71
|
+
# annoyingly it seems that is hard to do without duplicating the
|
72
|
+
# implementation of process_iter().
|
73
|
+
#
|
74
|
+
# XXX: the whole RuleRewriter approach is wrong because it conflates
|
75
|
+
# converting ASTs to strings with doing analysis on ASTs. Those should be
|
76
|
+
# split into two separate passes.
|
77
|
+
def process_iter(exp)
|
78
|
+
# first field of exp is tag; shift it
|
79
|
+
exp.shift
|
80
|
+
iter = process exp.shift
|
81
|
+
args = exp.shift
|
82
|
+
|
83
|
+
@iter_stack.push(true)
|
84
|
+
body = exp.empty? ? nil : process(exp.shift)
|
85
|
+
@iter_stack.pop
|
86
|
+
|
87
|
+
do_process_iter(iter, args, body)
|
88
|
+
end
|
89
|
+
|
90
|
+
def do_process_iter(iter, args, body)
|
91
|
+
args = case args
|
92
|
+
when 0 then
|
93
|
+
" ||"
|
94
|
+
else
|
95
|
+
a = process(args)[1..-2]
|
96
|
+
a = " |#{a}|" unless a.empty?
|
97
|
+
a
|
98
|
+
end
|
99
|
+
|
100
|
+
b, e = if iter == "END" then
|
101
|
+
[ "{", "}" ]
|
102
|
+
else
|
103
|
+
[ "do", "end" ]
|
104
|
+
end
|
105
|
+
|
106
|
+
iter.sub!(/\(\)$/, '')
|
107
|
+
|
108
|
+
# REFACTOR: ugh
|
109
|
+
result = []
|
110
|
+
result << "#{iter} {"
|
111
|
+
result << args
|
112
|
+
if body then
|
113
|
+
result << " #{body.strip} "
|
114
|
+
else
|
115
|
+
result << ' '
|
116
|
+
end
|
117
|
+
result << "}"
|
118
|
+
result = result.join
|
119
|
+
return result if result !~ /\n/ and result.size < LINE_LENGTH
|
120
|
+
|
121
|
+
result = []
|
122
|
+
result << "#{iter} #{b}"
|
123
|
+
result << args
|
124
|
+
result << "\n"
|
125
|
+
if body then
|
126
|
+
result << indent(body.strip)
|
127
|
+
result << "\n"
|
128
|
+
end
|
129
|
+
result << e
|
130
|
+
result.join
|
131
|
+
end
|
132
|
+
|
68
133
|
def process_call(exp)
|
69
|
-
recv, op, args = exp
|
70
|
-
if OP_LIST.include?(op) and @context[1] == :
|
71
|
-
# NB: context.length is
|
134
|
+
tag, recv, op, *args = exp
|
135
|
+
if OP_LIST.include?(op) and @context[1] == :defn and @context.length == 2
|
136
|
+
# NB: context.length is 2 when see a method call at the top-level of a
|
72
137
|
# :defn block -- this is where we expect Bloom statements to appear
|
73
138
|
do_rule(exp)
|
74
139
|
elsif op == :notin
|
75
140
|
# Special case. In the rule "z <= x.notin(y)", z depends positively on x,
|
76
141
|
# but negatively on y. See further explanation in the "else" section for
|
77
142
|
# why this is a special case.
|
78
|
-
|
79
|
-
|
143
|
+
if args.first.sexp_type != :call
|
144
|
+
raise Bud::CompileError, "illegal argument to notin: #{args.first}"
|
145
|
+
end
|
146
|
+
notintab = call_to_id(args[0]) # args expected to be of the form (:call nil :y ...)
|
147
|
+
@tables[notintab] = true # "true" denotes non-monotonic dependency
|
80
148
|
super
|
81
149
|
else
|
82
150
|
# Parse a call of the form a.b.c.foo
|
@@ -88,15 +156,16 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
88
156
|
# a.b.c.notin(d.e.f), we register a non-monotonic dependency of lhs on
|
89
157
|
# "d.e.f", not with "a.b.c"
|
90
158
|
ty, qn, _ = exp_id_type(recv, op, args) # qn = qualified name
|
91
|
-
if ty == :collection
|
159
|
+
if ty == :collection or ty == :lattice
|
92
160
|
(@tables[qn] = @nm if @collect) unless @tables[qn]
|
161
|
+
@refs_in_body << qn unless @iter_stack.empty?
|
93
162
|
#elsif ty == :import .. do nothing
|
94
163
|
elsif ty == :not_coll_id
|
95
164
|
# Check if receiver is a collection, and further if the current exp
|
96
165
|
# represents a field lookup
|
97
166
|
op_is_field_name = false
|
98
167
|
if recv and recv.first == :call
|
99
|
-
rty, _, robj = exp_id_type(recv[1], recv[2], recv[3])
|
168
|
+
rty, _, robj = exp_id_type(recv[1], recv[2], recv[3..-1])
|
100
169
|
if rty == :collection
|
101
170
|
cols = robj.cols
|
102
171
|
op_is_field_name = true if cols and cols.include?(op)
|
@@ -104,18 +173,14 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
104
173
|
end
|
105
174
|
# For CALM analysis, mark deletion rules as non-monotonic
|
106
175
|
@nm = true if op == :-@
|
176
|
+
|
177
|
+
# Don't worry about monotone ops, table names, table.attr calls, or
|
178
|
+
# accessors of iterator variables
|
107
179
|
if recv
|
108
|
-
# Don't worry about monotone ops, table names, table.attr calls, or
|
109
|
-
# accessors of iterator variables
|
110
180
|
unless RuleRewriter.is_monotone(op) or op_is_field_name or
|
111
181
|
recv.first == :lvar or op.to_s.start_with?("__")
|
112
182
|
@nm = true
|
113
183
|
end
|
114
|
-
else
|
115
|
-
# Function called (implicit receiver = Bud instance) in a user-defined
|
116
|
-
# code block. Check if it is non-monotonic (like budtime, that
|
117
|
-
# produces a new value every time it is called)
|
118
|
-
@nm_funcs_called = true unless RuleRewriter.is_monotone(op)
|
119
184
|
end
|
120
185
|
end
|
121
186
|
if TEMP_OP_LIST.include? op
|
@@ -126,25 +191,37 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
126
191
|
end
|
127
192
|
|
128
193
|
def self.is_monotone(op)
|
129
|
-
MONOTONE_WHITELIST.include?(op)
|
194
|
+
MONOTONE_WHITELIST.include?(op) ||
|
195
|
+
is_morphism(op) ||
|
196
|
+
Bud::Lattice.global_mfuncs.include?(op)
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.is_morphism(op)
|
200
|
+
Bud::Lattice.global_morphs.include?(op)
|
130
201
|
end
|
131
202
|
|
132
|
-
# Rewrite top-level rhs
|
133
|
-
# turned into coll_expr collections.
|
203
|
+
# Rewrite top-level rhs literal expressions into lambdas. During wiring, these
|
204
|
+
# are turned into coll_expr collections. For normal relational Bloom, the only
|
205
|
+
# literal we expect to see is an array literal, but lattices can be
|
206
|
+
# initialized with other kinds of literals (e.g., integers for lmax).
|
134
207
|
def lambda_rewrite(rhs)
|
135
208
|
# the <= case
|
136
|
-
if rhs[0]
|
137
|
-
return s(:iter, s(:call, nil, :lambda, s(:
|
209
|
+
if is_rhs_literal(rhs[0])
|
210
|
+
return s(:iter, s(:call, nil, :lambda), s(:args), rhs)
|
138
211
|
# the superator case
|
139
212
|
elsif rhs[0] == :call \
|
140
|
-
and rhs[1] and rhs[1][0] and rhs[1][0]
|
213
|
+
and rhs[1] and rhs[1][0] and is_rhs_literal(rhs[1][0]) \
|
141
214
|
and rhs[2] and (rhs[2] == :+@ or rhs[2] == :-@ or rhs[2] == :~@)
|
142
|
-
return s(rhs[0], s(:iter, s(:call, nil, :lambda, s(:
|
215
|
+
return s(rhs[0], s(:iter, s(:call, nil, :lambda), s(:args), rhs[1]), rhs[2], *rhs[3..-1])
|
143
216
|
else
|
144
217
|
return rhs
|
145
218
|
end
|
146
219
|
end
|
147
220
|
|
221
|
+
def is_rhs_literal(e)
|
222
|
+
[:array, :hash, :lit].include? e
|
223
|
+
end
|
224
|
+
|
148
225
|
def collect_rhs(exp)
|
149
226
|
exp = lambda_rewrite(exp)
|
150
227
|
|
@@ -155,13 +232,13 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
155
232
|
end
|
156
233
|
|
157
234
|
def reset_instance_vars
|
235
|
+
@refs_in_body = Set.new
|
158
236
|
@tables = {}
|
159
237
|
@nm = false
|
160
|
-
@nm_funcs_called = false
|
161
238
|
@temp_op = nil
|
162
239
|
end
|
163
240
|
|
164
|
-
def record_rule(lhs, op, rhs_pos, rhs)
|
241
|
+
def record_rule(lhs, op, rhs_pos, rhs, unsafe_funcs_called)
|
165
242
|
rule_txt_orig = "#{lhs} #{op} (#{rhs})"
|
166
243
|
rule_txt = "#{lhs} #{op} (#{rhs_pos})"
|
167
244
|
if op == :<
|
@@ -170,27 +247,26 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
170
247
|
op = op.to_s
|
171
248
|
end
|
172
249
|
|
173
|
-
@rules << [@bud_instance, @
|
250
|
+
@rules << [@bud_instance, @rule_idx, lhs, op, rule_txt,
|
251
|
+
rule_txt_orig, unsafe_funcs_called]
|
174
252
|
@tables.each_pair do |t, nm|
|
175
|
-
|
253
|
+
in_rule_body = @refs_in_body.include? t
|
254
|
+
@depends << [@bud_instance, @rule_idx, lhs, op, t, nm, in_rule_body]
|
176
255
|
end
|
177
256
|
|
178
257
|
reset_instance_vars
|
179
|
-
@
|
258
|
+
@rule_idx += 1
|
180
259
|
end
|
181
260
|
|
182
261
|
def do_rule(exp)
|
183
|
-
lhs =
|
184
|
-
|
185
|
-
rhs_ast = map2pro(exp[2])
|
186
|
-
|
187
|
-
# Remove the outer s(:arglist) from the rhs AST. An AST subtree rooted with
|
188
|
-
# s(:arglist) is not really sensible and it causes Ruby2Ruby < 1.3.1 to
|
189
|
-
# misbehave (for example, s(:arglist, s(:hash, ...)) is misparsed.
|
190
|
-
raise Bud::CompileError unless rhs_ast.sexp_type == :arglist
|
191
|
-
rhs_ast = rhs_ast[1]
|
262
|
+
tag, lhs, op, rhs_ast = exp
|
263
|
+
lhs = process(lhs)
|
192
264
|
|
265
|
+
rhs_ast = MapRewriter.new.process(rhs_ast)
|
193
266
|
rhs_ast = RenameRewriter.new(@bud_instance).process(rhs_ast)
|
267
|
+
rhs_ast = LatticeRefRewriter.new(@bud_instance).process(rhs_ast)
|
268
|
+
ufr = UnsafeFuncRewriter.new(@bud_instance)
|
269
|
+
rhs_ast = ufr.process(rhs_ast)
|
194
270
|
|
195
271
|
if @bud_instance.options[:no_attr_rewrite]
|
196
272
|
rhs = collect_rhs(rhs_ast)
|
@@ -203,35 +279,37 @@ class RuleRewriter < Ruby2Ruby # :nodoc: all
|
|
203
279
|
reset_instance_vars
|
204
280
|
rhs_pos = collect_rhs(AttrNameRewriter.new(@bud_instance).process(rhs_ast_dup))
|
205
281
|
end
|
206
|
-
record_rule(lhs, op, rhs_pos, rhs)
|
282
|
+
record_rule(lhs, op, rhs_pos, rhs, ufr.unsafe_func_called)
|
207
283
|
drain(exp)
|
208
284
|
end
|
209
285
|
|
210
|
-
# We want to rewrite "map" calls on BudCollections to "pro" calls. It is hard
|
211
|
-
# to do this precisely (issue #225), so we just replace map calls liberally
|
212
|
-
# and define Enumerable#pro as an alias for "map".
|
213
|
-
def map2pro(exp)
|
214
|
-
# the non-superator case
|
215
|
-
if exp[1] and exp[1][0] and exp[1][0] == :iter \
|
216
|
-
and exp[1][1] and exp[1][1][1] and exp[1][1][1][0] == :call
|
217
|
-
if exp[1][1][2] == :map
|
218
|
-
exp[1][1][2] = :pro
|
219
|
-
end
|
220
|
-
# the superator case
|
221
|
-
elsif exp[1] and exp[1][0] == :call and (exp[1][2] == :~@ or exp[1][2] == :+@ or exp[1][2] == :-@)
|
222
|
-
if exp[1][1] and exp[1][1][1] and exp[1][1][1][2] == :map
|
223
|
-
exp[1][1][1][2] = :pro
|
224
|
-
end
|
225
|
-
end
|
226
|
-
exp
|
227
|
-
end
|
228
|
-
|
229
286
|
def drain(exp)
|
230
287
|
exp.shift until exp.empty?
|
231
288
|
return ""
|
232
289
|
end
|
233
290
|
end
|
234
291
|
|
292
|
+
# We want to rewrite "map" calls on BudCollections to "pro" calls. It is hard
|
293
|
+
# to do this precisely (issue #225), so we just replace map calls liberally
|
294
|
+
# and define Enumerable#pro as an alias for "map".
|
295
|
+
class MapRewriter < SexpProcessor
|
296
|
+
def initialize
|
297
|
+
super
|
298
|
+
self.require_empty = false
|
299
|
+
self.expected = Sexp
|
300
|
+
end
|
301
|
+
|
302
|
+
def process_call(exp)
|
303
|
+
tag, recv, op, *args = exp
|
304
|
+
|
305
|
+
if op == :map and args.empty?
|
306
|
+
op = :pro
|
307
|
+
end
|
308
|
+
|
309
|
+
s(tag, process(recv), op, *(args.map{|a| process(a)}))
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
235
313
|
# Look for rename statements and define the necessary scratch collections
|
236
314
|
class RenameRewriter < SexpProcessor
|
237
315
|
def initialize(bud_instance)
|
@@ -252,14 +330,135 @@ class RenameRewriter < SexpProcessor
|
|
252
330
|
end
|
253
331
|
|
254
332
|
def process_call(exp)
|
255
|
-
|
333
|
+
tag, recv, op, *args = exp
|
256
334
|
|
257
335
|
if op == :rename
|
258
|
-
|
336
|
+
raise Bud::CompileError, "reduce takes two arguments" unless args.size == 2
|
337
|
+
namelit, schemahash = args
|
259
338
|
register_scratch(namelit[1], schemahash)
|
260
339
|
end
|
261
340
|
|
262
|
-
return s(
|
341
|
+
return s(tag, process(recv), op, *(args.map{|a| process(a)}))
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Check for whether the rule invokes any "unsafe" functions (functions that
|
346
|
+
# might return a different value every time they are called, e.g., budtime). The
|
347
|
+
# test for "unsafe" functions is pretty naive: any function call with a nil
|
348
|
+
# receiver is treated as unsafe unless it is belongs to a list of "safe"
|
349
|
+
# functions (below) or it denotes a lattice identifier. In the latter case, the
|
350
|
+
# rule is akin to an implicit join with the lattice, so we only rescan it on
|
351
|
+
# deltas to the lattice (see "rescan_on_merge" in LatticeWrapper).
|
352
|
+
#
|
353
|
+
# Although this is called a rewriter, it doesn't modify the input AST.
|
354
|
+
class UnsafeFuncRewriter < SexpProcessor
|
355
|
+
SAFE_FUNC_LIST = [:int_ip_port, :ip_port, :ip, :port].to_set
|
356
|
+
|
357
|
+
attr_reader :unsafe_func_called
|
358
|
+
|
359
|
+
def initialize(bud_instance)
|
360
|
+
super()
|
361
|
+
self.require_empty = false
|
362
|
+
self.expected = Sexp
|
363
|
+
@bud_instance = bud_instance
|
364
|
+
@unsafe_func_called = false
|
365
|
+
@elem_stack = []
|
366
|
+
end
|
367
|
+
|
368
|
+
def process_call(exp)
|
369
|
+
tag, recv, op, *args = exp
|
370
|
+
|
371
|
+
# We assume that unsafe funcs have a nil receiver (Bud instance is implicit
|
372
|
+
# receiver).
|
373
|
+
if recv.nil? and @elem_stack.size > 0
|
374
|
+
unless is_safe_func(op) || is_collection_name?(op)
|
375
|
+
@unsafe_func_called = true
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
return s(tag, process(recv), op, *(args.map{|a| process(a)}))
|
380
|
+
end
|
381
|
+
|
382
|
+
def process_iter(exp)
|
383
|
+
tag, recv, iter_args, body = exp
|
384
|
+
if (iter_args == 0)
|
385
|
+
iter_args = s(:args)
|
386
|
+
end
|
387
|
+
new_body = push_and_process(body)
|
388
|
+
return s(tag, process(recv), process(iter_args), new_body)
|
389
|
+
end
|
390
|
+
|
391
|
+
def push_and_process(exp)
|
392
|
+
obj_id = exp.object_id
|
393
|
+
@elem_stack.push(obj_id)
|
394
|
+
rv = process(exp)
|
395
|
+
raise Bud::Error unless @elem_stack.pop == obj_id
|
396
|
+
return rv
|
397
|
+
end
|
398
|
+
|
399
|
+
def is_collection_name?(op)
|
400
|
+
@bud_instance.tables.has_key?(op.to_sym) || @bud_instance.lattices.has_key?(op.to_sym)
|
401
|
+
end
|
402
|
+
|
403
|
+
def is_safe_func(op)
|
404
|
+
SAFE_FUNC_LIST.include? op
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# Rewrite references to lattice identifiers that appear in rule bodies. A
|
409
|
+
# reference to a lattice identifier returns the associated lattice wrapper. When
|
410
|
+
# the identifier appears at the top-level of the rule RHS, that is fine (since
|
411
|
+
# we want the wrapper to do wiring). But for references that appear inside rule
|
412
|
+
# bodies, we want to instead fetch the current value associated with the lattice
|
413
|
+
# wrapper.
|
414
|
+
class LatticeRefRewriter < SexpProcessor
|
415
|
+
def initialize(bud_instance)
|
416
|
+
super()
|
417
|
+
self.require_empty = false
|
418
|
+
self.expected = Sexp
|
419
|
+
@bud_instance = bud_instance
|
420
|
+
@elem_stack = []
|
421
|
+
end
|
422
|
+
|
423
|
+
def process_iter(exp)
|
424
|
+
tag, recv, iter_args, body = exp
|
425
|
+
new_body = push_and_process(body)
|
426
|
+
if (iter_args == 0)
|
427
|
+
iter_args = s(:args)
|
428
|
+
end
|
429
|
+
return s(tag, process(recv), process(iter_args), new_body)
|
430
|
+
end
|
431
|
+
|
432
|
+
def process_array(exp)
|
433
|
+
new_body = exp.sexp_body.map {|t| push_and_process(t)}
|
434
|
+
return s(:array, *new_body)
|
435
|
+
end
|
436
|
+
|
437
|
+
def process_hash(exp)
|
438
|
+
new_body = exp.sexp_body.map {|t| push_and_process(t)}
|
439
|
+
return s(:hash, *new_body)
|
440
|
+
end
|
441
|
+
|
442
|
+
def process_call(exp)
|
443
|
+
tag, recv, op, *args = exp
|
444
|
+
|
445
|
+
if recv.nil? and args.empty? and is_lattice?(op) and @elem_stack.size > 0
|
446
|
+
return s(:call, exp, :current_value)
|
447
|
+
else
|
448
|
+
return s(tag, process(recv), op, *(args.map{|a| process(a)}))
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
def push_and_process(exp)
|
453
|
+
obj_id = exp.object_id
|
454
|
+
@elem_stack.push(obj_id)
|
455
|
+
rv = process(exp)
|
456
|
+
raise Bud::Error unless @elem_stack.pop == obj_id
|
457
|
+
return rv
|
458
|
+
end
|
459
|
+
|
460
|
+
def is_lattice?(op)
|
461
|
+
@bud_instance.lattices.has_key? op.to_sym
|
263
462
|
end
|
264
463
|
end
|
265
464
|
|
@@ -278,32 +477,43 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
|
|
278
477
|
# iter vars
|
279
478
|
def process_iter(exp)
|
280
479
|
if exp[1] and exp[1][0] == :call
|
480
|
+
return exp unless exp[2]
|
281
481
|
gather_collection_names(exp[1])
|
482
|
+
meth_name = exp[1][2]
|
282
483
|
|
283
484
|
# now find iter vars and match up
|
284
|
-
if exp[2]
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
485
|
+
if exp[2][0] == :args and @collnames.size == 1 # single-table iter
|
486
|
+
if @iterhash[exp[2][1]]
|
487
|
+
raise Bud::CompileError, "redefinition of block variable \"#{exp[2][1]}\" not allowed"
|
488
|
+
end
|
489
|
+
|
490
|
+
# XXX: The BudChannel#payloads method assigns the correct schema to
|
491
|
+
# tuples that pass through it (i.e., it omits the location specifier);
|
492
|
+
# hence we don't want to apply the location rewrite to the code block
|
493
|
+
# that is passed to payloads(). This is a dirty hack.
|
494
|
+
unless meth_name == :payloads
|
495
|
+
@iterhash[exp[2][1]] = @collnames[0]
|
496
|
+
end
|
497
|
+
elsif exp[2][0] == :args and not @collnames.empty? # join iter with lefts/rights
|
498
|
+
case meth_name
|
289
499
|
when :lefts
|
290
500
|
@iterhash[exp[2][1]] = @collnames[0]
|
291
501
|
when :rights
|
292
502
|
@iterhash[exp[2][1]] = @collnames[1]
|
293
|
-
|
294
|
-
raise Bud::CompileError, "nested redefinition of block variable \"#{exp[2][1]}\" not allowed" if @iterhash[exp[2][1]]
|
295
|
-
end
|
296
|
-
elsif exp[2] and exp[2][0] == :masgn and not @collnames.empty? # join or reduce iter
|
297
|
-
return unless exp[2][1] and exp[2][1][0] == :array
|
298
|
-
if exp[1][2] == :reduce
|
503
|
+
when :reduce
|
299
504
|
unless @collnames.length == 1
|
300
|
-
raise Bud::
|
505
|
+
raise Bud::CompileError, "reduce should only have one associated collection, but has #{@collnames.inspect}"
|
301
506
|
end
|
302
|
-
@iterhash[exp[2][1]
|
303
|
-
else
|
304
|
-
|
305
|
-
|
306
|
-
|
507
|
+
@iterhash[exp[2][1]] = @collnames[0]
|
508
|
+
else
|
509
|
+
# join
|
510
|
+
if @iterhash[exp[2][1]]
|
511
|
+
raise Bud::CompileError, "redefinition of block variable \"#{exp[2][1]}\" not allowed"
|
512
|
+
end
|
513
|
+
|
514
|
+
@collnames.each_with_index do |c,i|
|
515
|
+
next unless exp[2][i+1]
|
516
|
+
@iterhash[exp[2][i+1]] = c
|
307
517
|
end
|
308
518
|
end
|
309
519
|
end
|
@@ -313,36 +523,43 @@ class AttrNameRewriter < SexpProcessor # :nodoc: all
|
|
313
523
|
end
|
314
524
|
|
315
525
|
def gather_collection_names(exp)
|
316
|
-
|
526
|
+
# We expect a reference to a collection name to look like a function call
|
527
|
+
# (nil receiver) with no arguments.
|
528
|
+
if exp.sexp_type == :call and exp[1].nil? and exp.length == 3
|
317
529
|
@collnames << exp[2]
|
318
|
-
elsif exp
|
319
|
-
|
530
|
+
elsif exp.sexp_type == :call and exp[2] == :rename
|
531
|
+
namelit = exp[3]
|
320
532
|
@collnames << namelit[1]
|
533
|
+
elsif exp.sexp_type == :call and [:group, :argagg].include?(exp[2])
|
534
|
+
# For grouping and argagg expressions, only look at the receiver (the
|
535
|
+
# collection we're grouping on); otherwise, we might mistakenly think some
|
536
|
+
# of the arguments to the grouping operation are collection names.
|
537
|
+
gather_collection_names(exp[1])
|
321
538
|
else
|
322
|
-
exp.each { |e| gather_collection_names(e) if e
|
539
|
+
exp.each { |e| gather_collection_names(e) if e.class <= Sexp }
|
323
540
|
end
|
324
541
|
end
|
325
542
|
|
326
543
|
def process_call(exp)
|
327
|
-
call, recv, op, args = exp
|
544
|
+
call, recv, op, *args = exp
|
328
545
|
|
329
|
-
if recv
|
546
|
+
if recv.class == Sexp and recv.sexp_type == :lvar and @iterhash[recv[1]]
|
330
547
|
if @bud_instance.respond_to?(@iterhash[recv[1]])
|
331
548
|
if @bud_instance.send(@iterhash[recv[1]]).class <= Bud::BudCollection
|
332
549
|
cols = @bud_instance.send(@iterhash[recv[1]]).cols
|
333
550
|
if op != :[] and @bud_instance.send(@iterhash[recv[1]]).respond_to?(op)
|
334
|
-
# if the op is an attribute name in the schema,
|
335
|
-
|
336
|
-
unless
|
551
|
+
# if the op is an attribute name in the schema, col_idx is its index
|
552
|
+
col_idx = cols.index(op) unless cols.nil?
|
553
|
+
unless col_idx.nil?
|
337
554
|
op = :[]
|
338
|
-
args = s(:
|
555
|
+
args = [s(:lit, col_idx)]
|
339
556
|
end
|
340
557
|
end
|
341
558
|
end
|
342
|
-
return s(call, recv, op, args)
|
559
|
+
return s(call, recv, op, *args)
|
343
560
|
end
|
344
561
|
end
|
345
|
-
return s(call, process(recv), op, process(
|
562
|
+
return s(call, process(recv), op, *(args.map{|a| process(a)}))
|
346
563
|
end
|
347
564
|
end
|
348
565
|
|
@@ -354,7 +571,7 @@ class TempExpander < SexpProcessor # :nodoc: all
|
|
354
571
|
attr_reader :tmp_tables
|
355
572
|
attr_accessor :did_work
|
356
573
|
|
357
|
-
|
574
|
+
TEMP_KEYWORD = :temp
|
358
575
|
|
359
576
|
def initialize
|
360
577
|
super()
|
@@ -365,71 +582,63 @@ class TempExpander < SexpProcessor # :nodoc: all
|
|
365
582
|
end
|
366
583
|
|
367
584
|
def process_defn(exp)
|
368
|
-
tag, name, args,
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
# correct the misparsing.
|
381
|
-
if n.sexp_type == :iter
|
382
|
-
iter_body = n.sexp_body
|
383
|
-
new_n = fix_temp_decl(iter_body)
|
384
|
-
unless new_n.nil?
|
385
|
-
block[i] = n = new_n
|
386
|
-
@did_work = true
|
387
|
-
end
|
388
|
-
end
|
389
|
-
|
390
|
-
_, recv, meth, meth_args = n
|
391
|
-
if meth == KEYWORD and recv.nil?
|
392
|
-
block[i] = rewrite_me(n)
|
585
|
+
tag, name, args, *body = exp
|
586
|
+
return exp unless name.to_s =~ /^__bloom__.+/
|
587
|
+
|
588
|
+
body.each_with_index do |n,i|
|
589
|
+
# temp declarations are misparsed if the RHS contains certain constructs
|
590
|
+
# (e.g., group, "do |f| ... end" rather than "{|f| ... }"). Rewrite to
|
591
|
+
# correct the misparsing.
|
592
|
+
if n.sexp_type == :iter
|
593
|
+
iter_body = n.sexp_body
|
594
|
+
new_n = fix_temp_decl(iter_body)
|
595
|
+
unless new_n.nil?
|
596
|
+
body[i] = n = new_n
|
393
597
|
@did_work = true
|
394
598
|
end
|
395
599
|
end
|
600
|
+
|
601
|
+
_, recv, meth, meth_args = n
|
602
|
+
if meth == TEMP_KEYWORD and recv.nil?
|
603
|
+
body[i] = rewrite_temp(n)
|
604
|
+
@did_work = true
|
605
|
+
end
|
396
606
|
end
|
397
|
-
s(tag, name, args,
|
607
|
+
s(tag, name, args, *body)
|
398
608
|
end
|
399
609
|
|
400
610
|
private
|
401
611
|
def fix_temp_decl(iter_body)
|
402
612
|
if iter_body.first.sexp_type == :call
|
403
613
|
call_node = iter_body.first
|
614
|
+
_, recv, meth, *meth_args = call_node
|
404
615
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
old_rhs_body = rhs.sexp_body
|
410
|
-
new_rhs_body = [:iter]
|
411
|
-
new_rhs_body += old_rhs_body
|
412
|
-
new_rhs_body += iter_body[1..-1]
|
413
|
-
rhs[1] = Sexp.from_array(new_rhs_body)
|
616
|
+
if meth == TEMP_KEYWORD and recv.nil?
|
617
|
+
_, lhs, op, rhs = meth_args.first
|
618
|
+
new_rhs = s(:iter, rhs, *(iter_body[1..-1]))
|
619
|
+
meth_args.first[3] = new_rhs
|
414
620
|
return call_node
|
415
621
|
end
|
416
622
|
end
|
417
623
|
return nil
|
418
624
|
end
|
419
625
|
|
420
|
-
def
|
421
|
-
_, recv, meth, args = exp
|
626
|
+
def rewrite_temp(exp)
|
627
|
+
_, recv, meth, *args = exp
|
422
628
|
|
423
|
-
raise Bud::CompileError unless recv
|
424
|
-
nest_call = args.
|
629
|
+
raise Bud::CompileError unless recv.nil?
|
630
|
+
nest_call = args.first
|
425
631
|
raise Bud::CompileError unless nest_call.sexp_type == :call
|
426
632
|
|
427
|
-
nest_recv, nest_op, nest_args = nest_call.sexp_body
|
428
|
-
|
633
|
+
nest_recv, nest_op, *nest_args = nest_call.sexp_body
|
634
|
+
unless nest_recv.sexp_type == :lit
|
635
|
+
recv_src = Ruby2Ruby.new.process(Marshal.load(Marshal.dump(nest_recv)))
|
636
|
+
raise Bud::CompileError, "argument to temp must be a symbol: #{recv_src}"
|
637
|
+
end
|
429
638
|
|
430
639
|
tmp_name = nest_recv.sexp_body.first
|
431
640
|
@tmp_tables << tmp_name
|
432
|
-
new_recv = s(:call, nil, tmp_name
|
433
|
-
return s(:call, new_recv, nest_op, nest_args)
|
641
|
+
new_recv = s(:call, nil, tmp_name)
|
642
|
+
return s(:call, new_recv, nest_op, *nest_args)
|
434
643
|
end
|
435
644
|
end
|