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/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
|