bud 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README +33 -16
  2. data/bin/budplot +42 -65
  3. data/bin/budtimelines +235 -0
  4. data/bin/budvis +24 -122
  5. data/bin/rebl +1 -0
  6. data/docs/README.md +21 -10
  7. data/docs/bfs.md +4 -6
  8. data/docs/c.html +251 -0
  9. data/docs/cheat.md +45 -30
  10. data/docs/deploy.md +26 -26
  11. data/docs/getstarted.md +6 -4
  12. data/docs/visualizations.md +43 -31
  13. data/examples/chat/chat.rb +4 -9
  14. data/examples/chat/chat_server.rb +1 -8
  15. data/examples/deploy/deploy_ip_port +1 -0
  16. data/examples/deploy/keys.rb +5 -0
  17. data/examples/deploy/tokenring-ec2.rb +9 -9
  18. data/examples/deploy/{tokenring-local.rb → tokenring-fork.rb} +3 -5
  19. data/examples/deploy/tokenring-thread.rb +15 -0
  20. data/examples/deploy/tokenring.rb +25 -17
  21. data/lib/bud/aggs.rb +87 -25
  22. data/lib/bud/bud_meta.rb +48 -31
  23. data/lib/bud/bust/bust.rb +16 -15
  24. data/lib/bud/collections.rb +207 -232
  25. data/lib/bud/depanalysis.rb +1 -0
  26. data/lib/bud/deploy/countatomicdelivery.rb +8 -20
  27. data/lib/bud/deploy/deployer.rb +16 -16
  28. data/lib/bud/deploy/ec2deploy.rb +34 -35
  29. data/lib/bud/deploy/forkdeploy.rb +90 -0
  30. data/lib/bud/deploy/threaddeploy.rb +38 -0
  31. data/lib/bud/graphs.rb +103 -199
  32. data/lib/bud/joins.rb +190 -41
  33. data/lib/bud/monkeypatch.rb +84 -0
  34. data/lib/bud/rebl.rb +8 -1
  35. data/lib/bud/rewrite.rb +152 -49
  36. data/lib/bud/server.rb +1 -0
  37. data/lib/bud/state.rb +24 -10
  38. data/lib/bud/storage/dbm.rb +170 -0
  39. data/lib/bud/storage/tokyocabinet.rb +5 -1
  40. data/lib/bud/stratify.rb +6 -7
  41. data/lib/bud/viz.rb +31 -17
  42. data/lib/bud/viz_util.rb +204 -0
  43. data/lib/bud.rb +271 -244
  44. data/lib/bud.rb.orig +806 -0
  45. metadata +43 -22
  46. data/docs/bfs.raw +0 -251
  47. data/docs/diffs +0 -181
  48. data/examples/basics/out +0 -1103
  49. data/examples/basics/out.new +0 -856
  50. data/lib/bud/deploy/localdeploy.rb +0 -53
@@ -1,55 +1,67 @@
1
1
  # Visualizations
2
2
 
3
- Bud programs compile naturally to dataflows, and dataflows have a natural graphical representation. Plotting a program as a graph can be useful to developers at various stages of program design, implementation and debugging. Bud predicate dependency graphs (or PDGs) are described on [[PDGs]].
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, __plotter__ and __visualizer__. Both use _GraphViz_ to draw a directed graph representing the program state and logic. __plotter__ provides a static analysis of the program, identifying sources and sinks of the dataflow, unconnected components, and points of order corresponding to logically nonmonotonic path edges. __visualizer__ is an offline debugging tool that analyses the trace of a (local) Bud execution and provides an interactive representation of runtime state over time.
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
- ## The Plotter
15
+ ## Using budplot
8
16
 
9
- [[https://github.com/bloom-lang/bud/blob/master/util/budplot]]
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 [[modules]].
16
- 3. Identifying dead code sections
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
- $ ruby budplot
22
- USAGE:
23
- ruby budplot LIST_OF_FILES LIST_OF_MODULES
30
+ $ budplot
31
+ Usage: budplot LIST_OF_FILES LIST_OF_MODULES
24
32
 
25
- As its usage message indicates, __plotter__ expects a list of ruby input files, followed by a list of Bud modules to mix in.
33
+ For example:
26
34
 
27
- $ ruby budplot kvs/kvs.rb ReplicatedKVS
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 -a /Applications/Google\ Chrome.app/ ReplicatedKVS_viz_collapsed.svg
41
+ $ open bud_doc/index.html
34
42
 
35
- __ReplicatedKVS__ includes the __MulticastProtocol__ and __MembershipProtocol__ protocols, but does not specify which implementation of these abstractions to use. The program is underspecified, and this is represented in the resulting graph (ReplicatedKVS_viz_collapsed.svg) by a node labeled "??" in the dataflow.
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
- $ ruby budplot kvs/kvs.rb ReplicatedKVS BestEffortMulticast StaticMembership
49
+ $ budplot kvs/kvs.rb ReplicatedKVS BestEffortMulticast StaticMembership
38
50
  fn is ReplicatedKVS_BestEffortMulticast_StaticMembership_viz_collapsed.svg
39
- $ open -a /Applications/Google\ Chrome.app/ ReplicatedKVS_BestEffortMulticast_StaticMembership_viz_collapsed.svg
40
-
41
-
42
- ## The Visualizer
51
+ $ open bud_doc/index.html
43
52
 
44
- [[https://github.com/bloom-lang/bud/blob/master/util/budvis]]
53
+ ## Using budvis
45
54
 
46
- To enable tracing, we need to set __:trace => true__ in the __Bud__ constructor, and optionally provide a __:tag__ to differentiate between traces by a human-readable name (rather than by object_id). I modified the unit test `test/tc_kvs.rb` as follows:
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(:tag => 'dist_primary', :port => 12345, :trace => true))
52
- + v2 = BestEffortReplicatedKVS.new(@opts.merge(:tag => 'dist_backup', :port => 12346, :trace => true))
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: TC_BestEffortReplicatedKVS_dist_primary_2160259460_
61
- Created directory: TC_BestEffortReplicatedKVS_dist_primary_2160259460_/bud_
62
- Created directory: TC_BestEffortReplicatedKVS_dist_backup_2159579740_
63
- Created directory: TC_BestEffortReplicatedKVS_dist_backup_2159579740_/bud_
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
- $ ruby budvis TC_BestEffortReplicatedKVS_dist_primary_2160259460_/
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/ TC_BestEffortReplicatedKVS_dist_primary_2160259460_/tm_0_expanded.svg
87
+ $ open -a /Applications/Google\ Chrome.app/ DBM_BestEffortReplicatedKVS_dist_primary_2160259460_/tm_0_expanded.svg
@@ -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
- if ARGV.length == 2
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, :read_stdin => true)
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
- if ARGV.first
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
@@ -0,0 +1,5 @@
1
+ access_key_id <= [["AKIAJZHUX3MXJLQULOLQ"]]
2
+ secret_access_key <= [['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']]
3
+ key_name <= [["nrc"]]
4
+ ec2_key_location <= [["/Users/neilc/.ssh/nrc.pem"]]
5
+
@@ -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 RingLocal
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
- RingLocal.new(:ip => ip,
23
- :ext_ip => ext_ip,
24
- :port => port,
25
- :ext_port => ext_port,
26
- :deploy => ARGV[2]).run_fg
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 RingLocal
5
+ class RingFork
7
6
  include Bud
8
7
  include TokenRing
9
- include LocalDeploy
8
+ include ForkDeploy
10
9
 
11
10
  deploystrap do
12
11
  node_count << [10]
13
12
  end
14
-
15
13
  end
16
14
 
17
- RingLocal.new(:deploy => true).run_fg
15
+ RingFork.new(:deploy => true).run_fg
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'bud'
3
+ require 'tokenring'
4
+
5
+ class RingThread
6
+ include Bud
7
+ include TokenRing
8
+ include ThreadDeploy
9
+
10
+ deploystrap do
11
+ node_count << [10]
12
+ end
13
+ end
14
+
15
+ RingThread.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, [] => [: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
- initial_data <= node.map do |n|
14
- # Calculate the successor node (splice deployer into the list)
15
- succ_id = (n.uid + 1) % (node_count[[]].num + 1)
16
- succ_node = (succ_id == node_count[[]].num) ? [ip_port] :
17
- [node[[succ_id]].node]
18
- [ n.uid, :next_node, [ succ_node ] ]
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
- # Initial data for deployer (not part of "node" set)
22
- next_node <= node.map do
23
- [node[[0]].node] if @options[:deploy]
24
- end
25
- token <~ node.map do
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 <- join([token_persist, next_node]).map {|t,_| [t.loc]}
37
+ token_persist <- (token_persist * next_node).lefts
34
38
  # Pass on the token
35
- token <~ join([token_persist, next_node]).map {[next_node[[]].node]}
36
- stdio <~ token.map {["#{ip_port}: Got token!"]}
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(state)
9
- state
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(state, val)
22
- (state == val)
33
+ def tie(the_state, val)
34
+ (the_state == val)
23
35
  end
24
- def final(state)
25
- state
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(state, val)
31
- state < val ? state : val
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(state, val)
42
- state > val ? state : val
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(state, val)
53
- state.nil? ? val : state
54
- end
55
- def tie(state, val)
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(state, val)
68
- state + val
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(state, x=nil)
83
- state + 1
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(state, val)
98
- retval = [state[0] + val]
99
- retval << (state[1] + 1)
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(state)
102
- state[0]*1.0 / state[1]
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(state, val)
117
- state << val
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
- # deductive rules are assigned to strata based on
23
- # the basic datalog stratification algorithm
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
- # all temporal rules are placed in the last stratum
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
- begin
86
- check_rule_ast(pt)
87
- rescue Exception => e
88
- # try to "generate" the source code associated with the problematic
89
- # block, so as to generate a more meaningful error message.
90
- # if this parse fails, return the original exception (not the new one).
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(pt)
93
- rescue Exception => sub_e
94
- raise e, "Error parsing rule block #{block_name}. Could not extract source."
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 e, "Error parsing rule block #{block_name}:\n#{code}"
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
- raise Bud::CompileError if pt.sexp_type != :defn
120
+ return pt if pt.sexp_type != :defn
110
121
  scope = pt[3]
111
- raise Bud::CompileError if scope.sexp_type != :scope
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
- raise Bud::CompileError if n != :block
127
+ return pt if n != :block
117
128
  next
118
129
  end
119
130
 
120
- raise Bud::CompileError if n.sexp_type != :call
121
- raise Bud::CompileError unless n.length == 4
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
- raise Bud::CompileError if lhs.nil? or lhs.sexp_type != :call
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
- raise Bud::CompileError, "Table does not exist: '#{lhs_name}'"
144
+ return [n, "Table does not exist: '#{lhs_name}'"]
131
145
  end
132
146
 
133
- # Check that op is a legal Bloom operator
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
- raise Bud::CompileError unless rhs.sexp_type == :arglist
157
+ return n unless rhs.sexp_type == :arglist
147
158
  body = rhs[1]
148
- raise Bud::CompileError unless body.sexp_type == :call
159
+ return n unless body.sexp_type == :call
149
160
  op_tail = body[2]
150
- raise Bud::CompileError unless [:~, :-@, :+@].include? op_tail
161
+ return n unless [:~, :-@, :+@].include? op_tail
151
162
  rhs_args = body[3]
152
- raise Bud::CompileError unless rhs_args.sexp_type == :arglist
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