bud 0.0.3 → 0.0.4

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