bud 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README +33 -16
- data/bin/budplot +42 -65
- data/bin/budtimelines +235 -0
- data/bin/budvis +24 -122
- data/bin/rebl +1 -0
- data/docs/README.md +21 -10
- data/docs/bfs.md +4 -6
- data/docs/c.html +251 -0
- data/docs/cheat.md +45 -30
- data/docs/deploy.md +26 -26
- data/docs/getstarted.md +6 -4
- data/docs/visualizations.md +43 -31
- data/examples/chat/chat.rb +4 -9
- data/examples/chat/chat_server.rb +1 -8
- data/examples/deploy/deploy_ip_port +1 -0
- data/examples/deploy/keys.rb +5 -0
- data/examples/deploy/tokenring-ec2.rb +9 -9
- data/examples/deploy/{tokenring-local.rb → tokenring-fork.rb} +3 -5
- data/examples/deploy/tokenring-thread.rb +15 -0
- data/examples/deploy/tokenring.rb +25 -17
- data/lib/bud/aggs.rb +87 -25
- data/lib/bud/bud_meta.rb +48 -31
- data/lib/bud/bust/bust.rb +16 -15
- data/lib/bud/collections.rb +207 -232
- data/lib/bud/depanalysis.rb +1 -0
- data/lib/bud/deploy/countatomicdelivery.rb +8 -20
- data/lib/bud/deploy/deployer.rb +16 -16
- data/lib/bud/deploy/ec2deploy.rb +34 -35
- data/lib/bud/deploy/forkdeploy.rb +90 -0
- data/lib/bud/deploy/threaddeploy.rb +38 -0
- data/lib/bud/graphs.rb +103 -199
- data/lib/bud/joins.rb +190 -41
- data/lib/bud/monkeypatch.rb +84 -0
- data/lib/bud/rebl.rb +8 -1
- data/lib/bud/rewrite.rb +152 -49
- data/lib/bud/server.rb +1 -0
- data/lib/bud/state.rb +24 -10
- data/lib/bud/storage/dbm.rb +170 -0
- data/lib/bud/storage/tokyocabinet.rb +5 -1
- data/lib/bud/stratify.rb +6 -7
- data/lib/bud/viz.rb +31 -17
- data/lib/bud/viz_util.rb +204 -0
- data/lib/bud.rb +271 -244
- data/lib/bud.rb.orig +806 -0
- metadata +43 -22
- data/docs/bfs.raw +0 -251
- data/docs/diffs +0 -181
- data/examples/basics/out +0 -1103
- data/examples/basics/out.new +0 -856
- data/lib/bud/deploy/localdeploy.rb +0 -53
data/docs/visualizations.md
CHANGED
@@ -1,55 +1,67 @@
|
|
1
1
|
# Visualizations
|
2
2
|
|
3
|
-
Bud programs compile naturally to dataflows, and dataflows have a natural
|
3
|
+
Bud programs compile naturally to dataflows, and dataflows have a natural
|
4
|
+
graphical representation. Viewing the dataflow graph of a program can be useful
|
5
|
+
to developers at various stages of program design, implementation and debugging.
|
4
6
|
|
5
|
-
Bud ships with two visualization utilities,
|
7
|
+
Bud ships with two visualization utilities, __budplot__ and __budvis__. Both
|
8
|
+
use _GraphViz_ to draw a directed graph representing the program state and
|
9
|
+
logic. __budplot__ provides a static analysis of the program, identifying
|
10
|
+
sources and sinks of the dataflow, unconnected components, and "points of order"
|
11
|
+
that correspond to logically nonmonotonic path edges. __budvis__ is an offline
|
12
|
+
debugging tool that analyses the trace of a (local) Bud execution and provides
|
13
|
+
an interactive representation of runtime state over time.
|
6
14
|
|
7
|
-
##
|
15
|
+
## Using budplot
|
8
16
|
|
9
|
-
|
10
|
-
|
11
|
-
The __plotter__ is a visual static analysis tool that aids in design and early implementation. The most common uses of the __plotter__ are:
|
17
|
+
__budplot__ is a visual static analysis tool that aids in design and early
|
18
|
+
implementation. The most common uses of __budplot__ are:
|
12
19
|
|
13
20
|
1. Visual sanity checking: does the dataflow look like I expected it to look?
|
14
21
|
2. Ensuring that a particular set of mixins is fully specified: e.g., did I forget to include a concrete implementation of a protocol required by other modules?
|
15
|
-
The Bloom module system, abstract interfaces and concrete implementations are described in more detail in [
|
16
|
-
3. Identifying dead code
|
22
|
+
The Bloom module system, abstract interfaces and concrete implementations are described in more detail in [modules.md](modules.md).
|
23
|
+
3. Identifying dead code
|
17
24
|
4. Experimenting with different module compositions
|
18
|
-
5. Identifying and iteratively refining a program's points of order
|
25
|
+
5. Identifying and iteratively refining a program's "points of order"
|
19
26
|
|
27
|
+
To run __budplot__, specify a list of Ruby input files, followed by a list of
|
28
|
+
Bud modules to be "mixed in" in the visualization.
|
20
29
|
|
21
|
-
$
|
22
|
-
|
23
|
-
ruby budplot LIST_OF_FILES LIST_OF_MODULES
|
30
|
+
$ budplot
|
31
|
+
Usage: budplot LIST_OF_FILES LIST_OF_MODULES
|
24
32
|
|
25
|
-
|
33
|
+
For example:
|
26
34
|
|
27
|
-
$
|
35
|
+
$ budplot kvs/kvs.rb ReplicatedKVS
|
28
36
|
Warning: underspecified dataflow: ["my_id", true]
|
29
37
|
Warning: underspecified dataflow: ["add_member", true]
|
30
38
|
Warning: underspecified dataflow: ["send_mcast", true]
|
31
39
|
Warning: underspecified dataflow: ["mcast_done", false]
|
32
40
|
fn is ReplicatedKVS_viz_collapsed.svg
|
33
|
-
$ open
|
41
|
+
$ open bud_doc/index.html
|
34
42
|
|
35
|
-
|
43
|
+
`ReplicatedKVS` includes the `MulticastProtocol` and `MembershipProtocol`
|
44
|
+
protocols, but does not specify which implementation of these abstractions to
|
45
|
+
use. The program is underspecified, and this is represented in the resulting
|
46
|
+
graph (`ReplicatedKVS_viz_collapsed.svg`) by a node labeled "??" in the
|
47
|
+
dataflow.
|
36
48
|
|
37
|
-
$
|
49
|
+
$ budplot kvs/kvs.rb ReplicatedKVS BestEffortMulticast StaticMembership
|
38
50
|
fn is ReplicatedKVS_BestEffortMulticast_StaticMembership_viz_collapsed.svg
|
39
|
-
$ open
|
40
|
-
|
41
|
-
|
42
|
-
## The Visualizer
|
51
|
+
$ open bud_doc/index.html
|
43
52
|
|
44
|
-
|
53
|
+
## Using budvis
|
45
54
|
|
46
|
-
To enable tracing, we need to set
|
55
|
+
To enable tracing, we need to set `:trace => true` in the `Bud` constructor, and
|
56
|
+
optionally provide a `:tag` to differentiate between traces by a human-readable
|
57
|
+
name (rather than by `object_id`). I modified the unit test `test/DBM_kvs.rb` as
|
58
|
+
follows:
|
47
59
|
|
48
60
|
- v = BestEffortReplicatedKVS.new(@opts.merge(:port => 12345))
|
49
61
|
- v2 = BestEffortReplicatedKVS.new(@opts.merge(:port => 12346))
|
50
62
|
|
51
|
-
+ v = BestEffortReplicatedKVS.new(@opts.merge(:
|
52
|
-
+ v2 = BestEffortReplicatedKVS.new(@opts.merge(:
|
63
|
+
+ v = BestEffortReplicatedKVS.new(@opts.merge(:port => 12345, :tag => 'dist_primary', :trace => true))
|
64
|
+
+ v2 = BestEffortReplicatedKVS.new(@opts.merge(:port => 12346, :tag => 'dist_backup', :trace => true))
|
53
65
|
|
54
66
|
|
55
67
|
Then I ran the unit test:
|
@@ -57,10 +69,10 @@ Then I ran the unit test:
|
|
57
69
|
$ ruby test/tc_kvs.rb
|
58
70
|
Loaded suite test/tc_kvs
|
59
71
|
Started
|
60
|
-
.Created directory:
|
61
|
-
Created directory:
|
62
|
-
Created directory:
|
63
|
-
Created directory:
|
72
|
+
.Created directory: DBM_BestEffortReplicatedKVS_dist_primary_2160259460_
|
73
|
+
Created directory: DBM_BestEffortReplicatedKVS_dist_primary_2160259460_/bud_
|
74
|
+
Created directory: DBM_BestEffortReplicatedKVS_dist_backup_2159579740_
|
75
|
+
Created directory: DBM_BestEffortReplicatedKVS_dist_backup_2159579740_/bud_
|
64
76
|
..
|
65
77
|
Finished in 4.366793 seconds.
|
66
78
|
|
@@ -68,8 +80,8 @@ Then I ran the unit test:
|
|
68
80
|
|
69
81
|
Then I ran the visualization utility:
|
70
82
|
|
71
|
-
$
|
83
|
+
$ budvis DBM_BestEffortReplicatedKVS_dist_primary_2160259460_/
|
72
84
|
|
73
85
|
And finally opened the (chronological) first output file:
|
74
86
|
|
75
|
-
$ open -a /Applications/Google\ Chrome.app/
|
87
|
+
$ open -a /Applications/Google\ Chrome.app/ DBM_BestEffortReplicatedKVS_dist_primary_2160259460_/tm_0_expanded.svg
|
data/examples/chat/chat.rb
CHANGED
@@ -24,9 +24,9 @@ class ChatClient
|
|
24
24
|
stdio <~ mcast { |m| [pretty_print(m.val)] }
|
25
25
|
end
|
26
26
|
|
27
|
-
# format chat messages with timestamp on the right of the screen
|
27
|
+
# format chat messages with color and timestamp on the right of the screen
|
28
28
|
def pretty_print(val)
|
29
|
-
str = val[1].to_s + ": " + (val[3].to_s || '')
|
29
|
+
str = "\033[34m"+val[1].to_s + ": " + "\033[31m" + (val[3].to_s || '') + "\033[0m"
|
30
30
|
pad = "(" + val[2].to_s + ")"
|
31
31
|
return str + " "*[66 - str.length,2].max + pad
|
32
32
|
end
|
@@ -34,12 +34,7 @@ end
|
|
34
34
|
|
35
35
|
|
36
36
|
|
37
|
-
|
38
|
-
server = ARGV[1]
|
39
|
-
else
|
40
|
-
server = ChatProtocol::DEFAULT_ADDR
|
41
|
-
end
|
42
|
-
|
37
|
+
server = (ARGV.length == 2) ? ARGV[1] : ChatProtocol::DEFAULT_ADDR
|
43
38
|
puts "Server address: #{server}"
|
44
|
-
program = ChatClient.new(ARGV[0], server, :
|
39
|
+
program = ChatClient.new(ARGV[0], server, :stdin => $stdin)
|
45
40
|
program.run_fg
|
@@ -14,15 +14,8 @@ class ChatServer
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
17
|
# ruby command-line wrangling
|
20
|
-
|
21
|
-
addr = ARGV.first
|
22
|
-
else
|
23
|
-
addr = ChatProtocol::DEFAULT_ADDR
|
24
|
-
end
|
25
|
-
|
18
|
+
addr = ARGV.first ? ARGV.first : ChatProtocol::DEFAULT_ADDR
|
26
19
|
ip, port = addr.split(":")
|
27
20
|
puts "Server address: #{ip}:#{port}"
|
28
21
|
program = ChatServer.new(:ip => ip, :port => port.to_i)
|
@@ -0,0 +1 @@
|
|
1
|
+
67.188.223.50:5555
|
@@ -1,26 +1,26 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bud'
|
3
|
-
require 'tokenring'
|
4
3
|
require 'bud/deploy/ec2deploy'
|
4
|
+
require 'tokenring'
|
5
5
|
|
6
|
-
class
|
6
|
+
class RingEC2
|
7
7
|
include Bud
|
8
8
|
include TokenRing
|
9
9
|
include EC2Deploy
|
10
10
|
|
11
11
|
deploystrap do
|
12
|
+
raise "keys.rb must exist in the current directory" unless File.exists? "keys.rb"
|
13
|
+
eval(IO.read('keys.rb'), binding)
|
12
14
|
node_count << [10]
|
13
|
-
eval(IO.read('keys.rb'), binding) if File.exists?('keys.rb')
|
14
15
|
ruby_command << ["ruby tokenring-ec2.rb"]
|
15
16
|
init_dir << ["."]
|
16
17
|
end
|
17
|
-
|
18
18
|
end
|
19
19
|
|
20
20
|
ip, port = ARGV[0].split(':')
|
21
21
|
ext_ip, ext_port = ARGV[1].split(':')
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
RingEC2.new(:ip => ip,
|
23
|
+
:port => port,
|
24
|
+
:ext_ip => ext_ip,
|
25
|
+
:ext_port => ext_port,
|
26
|
+
:deploy => ARGV[2]).run_fg
|
@@ -1,17 +1,15 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bud'
|
3
3
|
require 'tokenring'
|
4
|
-
require 'bud/deploy/localdeploy'
|
5
4
|
|
6
|
-
class
|
5
|
+
class RingFork
|
7
6
|
include Bud
|
8
7
|
include TokenRing
|
9
|
-
include
|
8
|
+
include ForkDeploy
|
10
9
|
|
11
10
|
deploystrap do
|
12
11
|
node_count << [10]
|
13
12
|
end
|
14
|
-
|
15
13
|
end
|
16
14
|
|
17
|
-
|
15
|
+
RingFork.new(:deploy => true).run_fg
|
@@ -2,38 +2,46 @@ require 'rubygems'
|
|
2
2
|
require 'bud'
|
3
3
|
|
4
4
|
module TokenRing
|
5
|
-
|
6
5
|
state do
|
7
|
-
table :next_node, [] => [:
|
6
|
+
table :next_node, [] => [:addr]
|
7
|
+
channel :next_node_chan, [:@loc] => [:next]
|
8
|
+
scratch :send_next_node, [:node, :next]
|
9
|
+
table :sent_next_node, [:addr]
|
8
10
|
channel :token, [:@loc]
|
9
11
|
table :token_persist, [:loc]
|
10
12
|
end
|
11
13
|
|
12
14
|
bloom :make_ring do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
# Once a node and its successor have both been computed, send a message to
|
16
|
+
# the node with the address of its successor
|
17
|
+
send_next_node <= (node * node * node_ready).combos do |n1, n2, nr|
|
18
|
+
succ_id = (n1.uid + 1) % node_count[[]].num
|
19
|
+
if n2.uid == succ_id and not sent_next_node.has_key? [n1.uid]
|
20
|
+
[n1.addr, n2.addr]
|
21
|
+
end
|
19
22
|
end
|
23
|
+
next_node_chan <~ send_next_node
|
24
|
+
sent_next_node <+ send_next_node {|n| [n.node]}
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
[node[[0]].node] if @options [:deploy]
|
26
|
+
next_node <= next_node_chan {|n| [n.next]}
|
27
|
+
|
28
|
+
# The deployer sends an initial message to the node with ID 0
|
29
|
+
token <~ (node_ready * node).rights(:uid => :uid) do |n|
|
30
|
+
[n.addr] if (@options[:deploy] and n.uid == 0)
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
30
34
|
bloom :pass_token do
|
31
35
|
# Persist the token for as long as necessary
|
32
36
|
token_persist <= token
|
33
|
-
token_persist <-
|
37
|
+
token_persist <- (token_persist * next_node).lefts
|
34
38
|
# Pass on the token
|
35
|
-
token <~
|
36
|
-
|
39
|
+
token <~ (token_persist * next_node).rights do |nn|
|
40
|
+
[nn.addr]
|
41
|
+
end
|
37
42
|
end
|
38
43
|
|
44
|
+
bloom :print_token do
|
45
|
+
stdio <~ token {["#{@node_id}: Got token! (@ #{ip_port})"]}
|
46
|
+
end
|
39
47
|
end
|
data/lib/bud/aggs.rb
CHANGED
@@ -4,9 +4,21 @@ module Bud
|
|
4
4
|
def init(val)
|
5
5
|
val
|
6
6
|
end
|
7
|
+
|
8
|
+
# In order to support argagg, trans must return a pair:
|
9
|
+
# 1. the running aggregate state
|
10
|
+
# 2. a flag to indicate what the caller should do with the input tuple for argaggs
|
11
|
+
# a. :ignore tells the caller to ignore this input
|
12
|
+
# b. :keep tells the caller to save this input
|
13
|
+
# c. :replace tells the caller to keep this input alone
|
14
|
+
# d. [:delete, t1, t2, ...] tells the caller to delete the remaining tuples
|
15
|
+
# For things that do not descend from ArgExemplary, the 2nd part can simply be nil.
|
16
|
+
def trans(the_state, val)
|
17
|
+
return the_state, :ignore
|
18
|
+
end
|
7
19
|
|
8
|
-
def final(
|
9
|
-
|
20
|
+
def final(the_state)
|
21
|
+
the_state
|
10
22
|
end
|
11
23
|
end
|
12
24
|
|
@@ -18,17 +30,21 @@ module Bud
|
|
18
30
|
# is discarded in favor of another, v can never be the final result
|
19
31
|
|
20
32
|
class ArgExemplary < Agg #:nodoc: all
|
21
|
-
def tie(
|
22
|
-
(
|
33
|
+
def tie(the_state, val)
|
34
|
+
(the_state == val)
|
23
35
|
end
|
24
|
-
def final(
|
25
|
-
|
36
|
+
def final(the_state)
|
37
|
+
the_state
|
26
38
|
end
|
27
39
|
end
|
28
40
|
|
29
41
|
class Min < ArgExemplary #:nodoc: all
|
30
|
-
def trans(
|
31
|
-
|
42
|
+
def trans(the_state, val)
|
43
|
+
if the_state < val
|
44
|
+
return the_state, :ignore
|
45
|
+
else
|
46
|
+
return val, :replace
|
47
|
+
end
|
32
48
|
end
|
33
49
|
end
|
34
50
|
# exemplary aggregate method to be used in Bud::BudCollection.group.
|
@@ -38,8 +54,12 @@ module Bud
|
|
38
54
|
end
|
39
55
|
|
40
56
|
class Max < ArgExemplary #:nodoc: all
|
41
|
-
def trans(
|
42
|
-
|
57
|
+
def trans(the_state, val)
|
58
|
+
if the_state > val
|
59
|
+
return the_state, :ignore
|
60
|
+
else
|
61
|
+
return val, :replace
|
62
|
+
end
|
43
63
|
end
|
44
64
|
end
|
45
65
|
# exemplary aggregate method to be used in Bud::BudCollection.group.
|
@@ -49,10 +69,14 @@ module Bud
|
|
49
69
|
end
|
50
70
|
|
51
71
|
class Choose < ArgExemplary #:nodoc: all
|
52
|
-
def trans(
|
53
|
-
|
54
|
-
|
55
|
-
|
72
|
+
def trans(the_state, val)
|
73
|
+
if the_state.nil?
|
74
|
+
return val, :replace
|
75
|
+
else
|
76
|
+
return the_state, :ignore
|
77
|
+
end
|
78
|
+
end
|
79
|
+
def tie(the_state, val)
|
56
80
|
false
|
57
81
|
end
|
58
82
|
end
|
@@ -62,10 +86,46 @@ module Bud
|
|
62
86
|
def choose(x)
|
63
87
|
[Choose.new, x]
|
64
88
|
end
|
89
|
+
|
90
|
+
class ChooseRand < ArgExemplary #:nodoc: all
|
91
|
+
@@reservoir_size = 1 # Vitter's reservoir sampling, k = 1
|
92
|
+
def init(x=nil)
|
93
|
+
the_state = {:cnt => 1, :vals => [x]}
|
94
|
+
end
|
95
|
+
|
96
|
+
def trans(the_state, val)
|
97
|
+
the_state[:cnt] += 1
|
98
|
+
if the_state[:cnt] < @@reservoir_size
|
99
|
+
the_state[:vals] << val
|
100
|
+
return the_state, :keep
|
101
|
+
else
|
102
|
+
j = rand(the_state[:cnt])
|
103
|
+
if j < @@reservoir_size
|
104
|
+
old_tup = the_state[:vals][j]
|
105
|
+
the_state[:vals][j] = val
|
106
|
+
return the_state, [:delete, old_tup]
|
107
|
+
else
|
108
|
+
return the_state, :keep
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
def tie(the_state, val)
|
113
|
+
true
|
114
|
+
end
|
115
|
+
def final(the_state)
|
116
|
+
the_state[:vals][rand(the_state[@@reservoir_size])]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# exemplary aggregate method to be used in Bud::BudCollection.group.
|
121
|
+
# randomly chooses among x entries being aggregated.
|
122
|
+
def choose_rand(x=nil)
|
123
|
+
[ChooseRand.new]
|
124
|
+
end
|
65
125
|
|
66
126
|
class Sum < Agg #:nodoc: all
|
67
|
-
def trans(
|
68
|
-
|
127
|
+
def trans(the_state, val)
|
128
|
+
return the_state + val, nil
|
69
129
|
end
|
70
130
|
end
|
71
131
|
|
@@ -79,8 +139,8 @@ module Bud
|
|
79
139
|
def init(x=nil)
|
80
140
|
1
|
81
141
|
end
|
82
|
-
def trans(
|
83
|
-
|
142
|
+
def trans(the_state, x=nil)
|
143
|
+
return the_state + 1, nil
|
84
144
|
end
|
85
145
|
end
|
86
146
|
|
@@ -94,12 +154,13 @@ module Bud
|
|
94
154
|
def init(val)
|
95
155
|
[val, 1]
|
96
156
|
end
|
97
|
-
def trans(
|
98
|
-
retval = [
|
99
|
-
retval << (
|
157
|
+
def trans(the_state, val)
|
158
|
+
retval = [the_state[0] + val]
|
159
|
+
retval << (the_state[1] + 1)
|
160
|
+
return retval, nil
|
100
161
|
end
|
101
|
-
def final(
|
102
|
-
|
162
|
+
def final(the_state)
|
163
|
+
the_state[0]*1.0 / the_state[1]
|
103
164
|
end
|
104
165
|
end
|
105
166
|
|
@@ -113,8 +174,9 @@ module Bud
|
|
113
174
|
def init(x)
|
114
175
|
[x]
|
115
176
|
end
|
116
|
-
def trans(
|
117
|
-
|
177
|
+
def trans(the_state, val)
|
178
|
+
the_state << val
|
179
|
+
return the_state, nil
|
118
180
|
end
|
119
181
|
end
|
120
182
|
|
data/lib/bud/bud_meta.rb
CHANGED
@@ -17,16 +17,19 @@ class BudMeta #:nodoc: all
|
|
17
17
|
stratum_map = binaryrel2map(@bud_instance.t_stratum)
|
18
18
|
|
19
19
|
rewritten_strata = Array.new(top_stratum + 2) { [] }
|
20
|
+
no_attr_rewrite_strata = Array.new(top_stratum + 2) { [] }
|
20
21
|
@bud_instance.t_rules.each do |d|
|
21
22
|
if d.op.to_s == '<='
|
22
|
-
#
|
23
|
-
#
|
23
|
+
# Deductive rules are assigned to strata based on the basic Datalog
|
24
|
+
# stratification algorithm
|
24
25
|
belongs_in = stratum_map[d.lhs]
|
25
26
|
belongs_in ||= 0
|
26
27
|
rewritten_strata[belongs_in] << d.src
|
28
|
+
no_attr_rewrite_strata[belongs_in] << d.orig_src
|
27
29
|
else
|
28
|
-
#
|
30
|
+
# All temporal rules are placed in the last stratum
|
29
31
|
rewritten_strata[top_stratum + 1] << d.src
|
32
|
+
no_attr_rewrite_strata[top_stratum + 1] << d.orig_src
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
@@ -41,7 +44,7 @@ class BudMeta #:nodoc: all
|
|
41
44
|
end
|
42
45
|
dump_rewrite(rewritten_strata) if @bud_instance.options[:dump_rewrite]
|
43
46
|
|
44
|
-
return rewritten_strata
|
47
|
+
return rewritten_strata, no_attr_rewrite_strata
|
45
48
|
end
|
46
49
|
|
47
50
|
def binaryrel2map(rel)
|
@@ -82,18 +85,25 @@ class BudMeta #:nodoc: all
|
|
82
85
|
|
83
86
|
pt = Unifier.new.process(parse_tree)
|
84
87
|
pp pt if @bud_instance.options[:dump_ast]
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
88
|
+
|
89
|
+
rv = check_rule_ast(pt)
|
90
|
+
unless rv.nil?
|
91
|
+
if rv.class <= Sexp
|
92
|
+
error_pt = rv
|
93
|
+
error_msg = "Parse error"
|
94
|
+
else
|
95
|
+
error_pt, error_msg = rv
|
96
|
+
end
|
97
|
+
|
98
|
+
# try to "generate" the source code associated with the problematic block,
|
99
|
+
# so as to generate a more meaningful error message.
|
91
100
|
begin
|
92
|
-
code = Ruby2Ruby.new.process(
|
93
|
-
|
94
|
-
|
101
|
+
code = Ruby2Ruby.new.process(Marshal.load(Marshal.dump(error_pt)))
|
102
|
+
src_msg = "\nCode: #{code}"
|
103
|
+
rescue Exception
|
104
|
+
src_msg = ""
|
95
105
|
end
|
96
|
-
raise
|
106
|
+
raise Bud::CompileError, "#{error_msg} in rule block \"#{block_name}\"#{src_msg}"
|
97
107
|
end
|
98
108
|
|
99
109
|
rewriter = RuleRewriter.new(seed, @bud_instance)
|
@@ -103,37 +113,38 @@ class BudMeta #:nodoc: all
|
|
103
113
|
|
104
114
|
# Perform some basic sanity checks on the AST of a rule block. We expect a
|
105
115
|
# rule block to consist of a :defn, a nested :scope, and then a sequence of
|
106
|
-
# statements. Each statement is a :call node.
|
116
|
+
# statements. Each statement is a :call node. Returns nil (no error found), a
|
117
|
+
# Sexp (containing an error), or a pair of [Sexp, error message].
|
107
118
|
def check_rule_ast(pt)
|
108
119
|
# :defn format: node tag, block name, args, nested scope
|
109
|
-
|
120
|
+
return pt if pt.sexp_type != :defn
|
110
121
|
scope = pt[3]
|
111
|
-
|
122
|
+
return pt if scope.sexp_type != :scope
|
112
123
|
block = scope[1]
|
113
124
|
|
114
125
|
block.each_with_index do |n,i|
|
115
126
|
if i == 0
|
116
|
-
|
127
|
+
return pt if n != :block
|
117
128
|
next
|
118
129
|
end
|
119
130
|
|
120
|
-
|
121
|
-
|
131
|
+
# Check for a common case
|
132
|
+
if n.sexp_type == :lasgn
|
133
|
+
return [n, "Illegal operator: '='"]
|
134
|
+
end
|
135
|
+
return pt unless n.sexp_type == :call and n.length == 4
|
122
136
|
|
123
137
|
# Rule format: call tag, lhs, op, rhs
|
124
138
|
tag, lhs, op, rhs = n
|
125
139
|
|
126
140
|
# Check that LHS references a named collection
|
127
|
-
|
141
|
+
return n if lhs.nil? or lhs.sexp_type != :call
|
128
142
|
lhs_name = lhs[2]
|
129
143
|
unless @bud_instance.tables.has_key? lhs_name.to_sym
|
130
|
-
|
144
|
+
return [n, "Table does not exist: '#{lhs_name}'"]
|
131
145
|
end
|
132
146
|
|
133
|
-
|
134
|
-
unless [:<, :<=].include? op
|
135
|
-
raise Bud::CompileError, "Illegal operator: '#{op}'"
|
136
|
-
end
|
147
|
+
return [n, "Illegal operator: '#{op}'"] unless [:<, :<=].include? op
|
137
148
|
|
138
149
|
# Check superator invocation. A superator that begins with "<" is parsed
|
139
150
|
# as a call to the binary :< operator. The right operand to :< is a :call
|
@@ -143,16 +154,17 @@ class BudMeta #:nodoc: all
|
|
143
154
|
# XXX: We don't check for illegal superators (e.g., "<--"). That would be
|
144
155
|
# tricky, because they are encoded as a nested unary op in the rule body.
|
145
156
|
if op == :<
|
146
|
-
|
157
|
+
return n unless rhs.sexp_type == :arglist
|
147
158
|
body = rhs[1]
|
148
|
-
|
159
|
+
return n unless body.sexp_type == :call
|
149
160
|
op_tail = body[2]
|
150
|
-
|
161
|
+
return n unless [:~, :-@, :+@].include? op_tail
|
151
162
|
rhs_args = body[3]
|
152
|
-
|
153
|
-
raise Bud::CompileError if rhs_args.length != 1
|
163
|
+
return n if rhs_args.sexp_type != :arglist or rhs_args.length != 1
|
154
164
|
end
|
155
165
|
end
|
166
|
+
|
167
|
+
return nil # No errors found
|
156
168
|
end
|
157
169
|
|
158
170
|
def stratify
|
@@ -162,6 +174,11 @@ class BudMeta #:nodoc: all
|
|
162
174
|
|
163
175
|
# Copy computed data back into Bud runtime
|
164
176
|
strat.stratum.each {|s| @bud_instance.t_stratum << s}
|
177
|
+
@bud_instance.stratum_collection_map = strat.stratum.inject({}) do |memo, t|
|
178
|
+
memo[t[1]] ||= []
|
179
|
+
memo[t[1]] << t[0]
|
180
|
+
memo
|
181
|
+
end
|
165
182
|
strat.depends_tc.each {|d| @bud_instance.t_depends_tc << d}
|
166
183
|
strat.cycle.each {|c| @bud_instance.t_cycle << c}
|
167
184
|
if strat.top_strat.length > 0
|