bud 0.9.7 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +13 -5
- data/History.txt +18 -0
- data/Rakefile +91 -0
- data/bin/budplot +7 -2
- data/docs/README.md +8 -17
- data/docs/cheat.md +1 -1
- data/docs/getstarted.md +95 -82
- data/docs/operational.md +3 -3
- data/examples/basics/paths.rb +2 -2
- data/examples/chat/chat_protocol.rb +1 -1
- data/lib/bud.rb +67 -51
- data/lib/bud/bud_meta.rb +64 -42
- data/lib/bud/collections.rb +29 -26
- data/lib/bud/executor/elements.rb +6 -6
- data/lib/bud/executor/join.rb +63 -52
- data/lib/bud/lattice-core.rb +5 -0
- data/lib/bud/monkeypatch.rb +38 -11
- data/lib/bud/rebl.rb +2 -2
- data/lib/bud/rewrite.rb +22 -11
- data/lib/bud/state.rb +2 -2
- data/lib/bud/storage/zookeeper.rb +7 -0
- data/lib/bud/version.rb +3 -0
- data/lib/bud/viz.rb +3 -3
- metadata +84 -82
data/docs/operational.md
CHANGED
@@ -58,7 +58,7 @@ Have a look at the following classic "transitive closure" example, which compute
|
|
58
58
|
|
59
59
|
state do
|
60
60
|
table :link, [:from, :to, :cost]
|
61
|
-
table :path, [:from, :to,
|
61
|
+
table :path, [:from, :to, :cost]
|
62
62
|
end
|
63
63
|
|
64
64
|
bloom :make_paths do
|
@@ -67,7 +67,7 @@ Have a look at the following classic "transitive closure" example, which compute
|
|
67
67
|
|
68
68
|
# recurse: path of length n+1 made by a link to a path of length n
|
69
69
|
path <= (link*path).pairs(:to => :from) do |l,p|
|
70
|
-
[l.from, p.to, l.cost+p.cost]
|
70
|
+
[l.from, p.to, l.cost + p.cost]
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -97,4 +97,4 @@ Note that it is possible to write a program in Bloom that is *unstratifiable*: t
|
|
97
97
|
|
98
98
|
glass <= one_item {|t| ['full'] if glass.empty? }
|
99
99
|
|
100
|
-
Consider the case where we start out with glass being empty. Then we know the fact `glass.empty?`, and the bloom statement says that `(glass.empty? => not glass.empty?)` which is equivalent to `(glass.empty? and not glass.empty?)` which is a contradiction. The Bud runtime detects cycles through non-monotonicity for you automatically when you instantiate your class.
|
100
|
+
Consider the case where we start out with glass being empty. Then we know the fact `glass.empty?`, and the bloom statement says that `(glass.empty? => not glass.empty?)` which is equivalent to `(glass.empty? and not glass.empty?)` which is a contradiction. The Bud runtime detects cycles through non-monotonicity for you automatically when you instantiate your class.
|
data/examples/basics/paths.rb
CHANGED
@@ -36,7 +36,7 @@ program = ShortestPaths.new
|
|
36
36
|
|
37
37
|
# populate our little example. we put two links between 'a' and 'b'
|
38
38
|
# to see whether our shortest-paths code does the right thing.
|
39
|
-
program.link
|
39
|
+
program.link <+ [['a', 'b', 1],
|
40
40
|
['a', 'b', 4],
|
41
41
|
['b', 'c', 1],
|
42
42
|
['c', 'd', 1],
|
@@ -48,6 +48,6 @@ program.shortest.to_a.sort.each {|t| puts t.inspect}
|
|
48
48
|
puts "----"
|
49
49
|
|
50
50
|
# now lets add an extra link and recompute
|
51
|
-
program.link
|
51
|
+
program.link <+ [['e', 'f', 1]]
|
52
52
|
program.tick
|
53
53
|
program.shortest.to_a.sort.each {|t| puts t.inspect}
|
data/lib/bud.rb
CHANGED
@@ -70,11 +70,11 @@ module Bud
|
|
70
70
|
attr_reader :budtime, :inbound, :options, :meta_parser, :viz, :rtracer, :dsock
|
71
71
|
attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :app_tables, :lattices
|
72
72
|
attr_reader :push_sources, :push_elems, :push_joins, :scanners, :merge_targets
|
73
|
-
attr_reader :this_stratum, :
|
73
|
+
attr_reader :this_stratum, :this_rule_context, :done_bootstrap
|
74
74
|
attr_reader :inside_tick
|
75
75
|
attr_accessor :stratified_rules
|
76
76
|
attr_accessor :metrics, :periodics
|
77
|
-
attr_accessor :
|
77
|
+
attr_accessor :qualified_name
|
78
78
|
attr_reader :running_async
|
79
79
|
|
80
80
|
# options to the Bud runtime are passed in a hash, with the following keys
|
@@ -109,9 +109,6 @@ module Bud
|
|
109
109
|
# * <tt>:dbm_dir</tt> filesystem directory to hold DBM-backed collections
|
110
110
|
# * <tt>:dbm_truncate</tt> if true, DBM-backed collections are opened with +OTRUNC+
|
111
111
|
def initialize(options={})
|
112
|
-
# capture the binding for a subsequent 'eval'. This ensures that local
|
113
|
-
# variable names introduced later in this method don't interfere with
|
114
|
-
# table names used in the eval block.
|
115
112
|
options[:dump_rewrite] ||= ENV["BUD_DUMP_REWRITE"].to_i > 0
|
116
113
|
options[:dump_ast] ||= ENV["BUD_DUMP_AST"].to_i > 0
|
117
114
|
options[:print_wiring] ||= ENV["BUD_PRINT_WIRING"].to_i > 0
|
@@ -139,7 +136,8 @@ module Bud
|
|
139
136
|
@instance_id = ILLEGAL_INSTANCE_ID # Assigned when we start running
|
140
137
|
@metrics = {}
|
141
138
|
@endtime = nil
|
142
|
-
@this_stratum =
|
139
|
+
@this_stratum = -1
|
140
|
+
@this_rule_id = -1
|
143
141
|
@push_sorted_elems = nil
|
144
142
|
@running_async = false
|
145
143
|
@bud_started = false
|
@@ -158,8 +156,6 @@ module Bud
|
|
158
156
|
resolve_imports
|
159
157
|
call_state_methods
|
160
158
|
|
161
|
-
@declarations = self.class.instance_methods.select {|m| m =~ /^__bloom__.+$/}.map {|m| m.to_s}
|
162
|
-
|
163
159
|
@viz = VizOnline.new(self) if @options[:trace]
|
164
160
|
@rtracer = RTrace.new(self) if @options[:rtrace]
|
165
161
|
|
@@ -500,8 +496,6 @@ module Bud
|
|
500
496
|
rescan_invalidate_tc(stratum, rescan, invalidate)
|
501
497
|
end
|
502
498
|
|
503
|
-
prune_rescan_invalidate(rescan, invalidate)
|
504
|
-
# transitive closure
|
505
499
|
@default_rescan = rescan.to_a
|
506
500
|
@default_invalidate = invalidate.to_a
|
507
501
|
|
@@ -526,7 +520,7 @@ module Bud
|
|
526
520
|
rescan = dflt_rescan.clone
|
527
521
|
invalidate = dflt_invalidate + [scanner.collection]
|
528
522
|
rescan_invalidate_tc(stratum, rescan, invalidate)
|
529
|
-
|
523
|
+
prune_rescan_set(rescan)
|
530
524
|
|
531
525
|
# Make sure we reset the rescan/invalidate flag for this scanner at
|
532
526
|
# end-of-tick, but we can remove the scanner from its own
|
@@ -599,7 +593,7 @@ module Bud
|
|
599
593
|
end
|
600
594
|
end
|
601
595
|
|
602
|
-
def
|
596
|
+
def prune_rescan_set(rescan)
|
603
597
|
rescan.delete_if {|e| e.rescan_at_tick}
|
604
598
|
end
|
605
599
|
|
@@ -616,7 +610,7 @@ module Bud
|
|
616
610
|
end
|
617
611
|
|
618
612
|
def do_rewrite
|
619
|
-
@meta_parser = BudMeta.new(self
|
613
|
+
@meta_parser = BudMeta.new(self)
|
620
614
|
@stratified_rules = @meta_parser.meta_rewrite
|
621
615
|
end
|
622
616
|
|
@@ -737,16 +731,30 @@ module Bud
|
|
737
731
|
# method blocks until Bud has been shutdown. If +stop_em+ is true, the
|
738
732
|
# EventMachine event loop is also shutdown; this will interfere with the
|
739
733
|
# execution of any other Bud instances in the same process (as well as
|
740
|
-
# anything else that happens to use EventMachine).
|
734
|
+
# anything else that happens to use EventMachine). We always shutdown the EM
|
735
|
+
# loop if there are no more running Bud instances (this does interfere with
|
736
|
+
# other EM-using apps, but it is necessary).
|
741
737
|
def stop(stop_em=false, do_shutdown_cb=true)
|
742
738
|
schedule_and_wait do
|
743
739
|
do_shutdown(do_shutdown_cb)
|
744
740
|
end
|
745
741
|
|
742
|
+
# If we're shutting down the last active Bud instance, shutdown the EM event
|
743
|
+
# loop as well. This is probably good practice in general, but it also
|
744
|
+
# prevents weird EM behavior -- it seems as though EM::ConnectionNotBound
|
745
|
+
# exceptions can be raised if the EM event loop is left running and
|
746
|
+
# subsequent events arrive.
|
747
|
+
$signal_lock.synchronize {
|
748
|
+
stop_em = true if $bud_instances.empty? and EventMachine::reactor_running?
|
749
|
+
}
|
750
|
+
|
746
751
|
if stop_em
|
747
752
|
Bud.stop_em_loop
|
748
|
-
EventMachine::reactor_thread
|
753
|
+
unless Thread.current == EventMachine::reactor_thread
|
754
|
+
EventMachine::reactor_thread.join
|
755
|
+
end
|
749
756
|
end
|
757
|
+
|
750
758
|
report_metrics if options[:metrics]
|
751
759
|
end
|
752
760
|
alias :stop_bg :stop
|
@@ -1065,6 +1073,35 @@ module Bud
|
|
1065
1073
|
@done_bootstrap = true
|
1066
1074
|
end
|
1067
1075
|
|
1076
|
+
def do_invalidate_rescan
|
1077
|
+
@default_rescan.each {|elem| elem.rescan = true}
|
1078
|
+
@default_invalidate.each {|elem|
|
1079
|
+
elem.invalidated = true
|
1080
|
+
# Call tick on tables here itself. The rest below
|
1081
|
+
elem.invalidate_cache unless elem.class <= PushElement
|
1082
|
+
}
|
1083
|
+
|
1084
|
+
# The following loop invalidates additional (non-default) elements and
|
1085
|
+
# tables that depend on the run-time invalidation state of a table. Loop
|
1086
|
+
# once to set the flags.
|
1087
|
+
each_scanner do |scanner, stratum|
|
1088
|
+
if scanner.rescan
|
1089
|
+
scanner.rescan_set.each {|e| e.rescan = true}
|
1090
|
+
scanner.invalidate_set.each {|e|
|
1091
|
+
e.invalidated = true
|
1092
|
+
e.invalidate_cache unless e.class <= PushElement
|
1093
|
+
}
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
# Loop a second time to actually call invalidate_cache. We can't merge this
|
1098
|
+
# with the loops above because some versions of invalidate_cache (e.g.,
|
1099
|
+
# join) depend on the rescan state of other elements.
|
1100
|
+
@num_strata.times do |stratum|
|
1101
|
+
@push_sorted_elems[stratum].each {|e| e.invalidate_cache if e.invalidated}
|
1102
|
+
end
|
1103
|
+
end
|
1104
|
+
|
1068
1105
|
# One timestep of Bloom execution. This MUST be invoked from the EventMachine
|
1069
1106
|
# thread; it is not intended to be called directly by client code.
|
1070
1107
|
def tick_internal
|
@@ -1084,32 +1121,7 @@ module Bud
|
|
1084
1121
|
else
|
1085
1122
|
# inform tables and elements about beginning of tick.
|
1086
1123
|
@app_tables.each {|t| t.tick}
|
1087
|
-
|
1088
|
-
@default_invalidate.each {|elem|
|
1089
|
-
elem.invalidated = true
|
1090
|
-
# Call tick on tables here itself. The rest below
|
1091
|
-
elem.invalidate_cache unless elem.class <= PushElement
|
1092
|
-
}
|
1093
|
-
|
1094
|
-
# The following loop invalidates additional (non-default) elements and
|
1095
|
-
# tables that depend on the run-time invalidation state of a table.
|
1096
|
-
# Loop once to set the flags.
|
1097
|
-
each_scanner do |scanner, stratum|
|
1098
|
-
if scanner.rescan
|
1099
|
-
scanner.rescan_set.each {|e| e.rescan = true}
|
1100
|
-
scanner.invalidate_set.each {|e|
|
1101
|
-
e.invalidated = true
|
1102
|
-
e.invalidate_cache unless e.class <= PushElement
|
1103
|
-
}
|
1104
|
-
end
|
1105
|
-
end
|
1106
|
-
|
1107
|
-
# Loop a second time to actually call invalidate_cache. We can't merge
|
1108
|
-
# this with the loops above because some versions of invalidate_cache
|
1109
|
-
# (e.g., join) depend on the rescan state of other elements.
|
1110
|
-
@num_strata.times do |stratum|
|
1111
|
-
@push_sorted_elems[stratum].each {|e| e.invalidate_cache if e.invalidated}
|
1112
|
-
end
|
1124
|
+
do_invalidate_rescan
|
1113
1125
|
end
|
1114
1126
|
|
1115
1127
|
receive_inbound
|
@@ -1201,6 +1213,7 @@ module Bud
|
|
1201
1213
|
table :t_cycle, [:predicate, :via, :neg, :temporal]
|
1202
1214
|
table :t_depends, [:bud_obj, :rule_id, :lhs, :op, :body] => [:nm, :in_body]
|
1203
1215
|
table :t_provides, [:interface] => [:input]
|
1216
|
+
table :t_rule_stratum, [:bud_obj, :rule_id] => [:stratum]
|
1204
1217
|
table :t_rules, [:bud_obj, :rule_id] => [:lhs, :op, :src, :orig_src, :unsafe_funcs_called]
|
1205
1218
|
table :t_stratum, [:predicate] => [:stratum]
|
1206
1219
|
table :t_table_info, [:tab_name, :tab_type]
|
@@ -1211,12 +1224,12 @@ module Bud
|
|
1211
1224
|
@builtin_tables = @tables.clone if toplevel
|
1212
1225
|
end
|
1213
1226
|
|
1214
|
-
# Handle
|
1215
|
-
# directly into the storage of the appropriate local
|
1216
|
-
# queue is cleared at the end of the tick.
|
1227
|
+
# Handle external inputs: channels, terminals, and periodics. Received
|
1228
|
+
# messages are placed directly into the storage of the appropriate local
|
1229
|
+
# collection. The inbound queue is cleared at the end of the tick.
|
1217
1230
|
def receive_inbound
|
1218
1231
|
@inbound.each do |tbl_name, msg_buf|
|
1219
|
-
puts "
|
1232
|
+
puts "recv via #{tbl_name}: #{msg_buf}" if $BUD_DEBUG
|
1220
1233
|
msg_buf.each do |b|
|
1221
1234
|
tables[tbl_name] << b
|
1222
1235
|
end
|
@@ -1241,7 +1254,8 @@ module Bud
|
|
1241
1254
|
# of PushElements
|
1242
1255
|
@this_stratum = strat_num
|
1243
1256
|
rules.each_with_index do |rule, i|
|
1244
|
-
|
1257
|
+
# user-supplied code blocks will be evaluated in this context at run-time
|
1258
|
+
@this_rule_context = rule.bud_obj
|
1245
1259
|
begin
|
1246
1260
|
eval_rule(rule.bud_obj, rule.src)
|
1247
1261
|
rescue Exception => e
|
@@ -1252,6 +1266,8 @@ module Bud
|
|
1252
1266
|
raise new_e
|
1253
1267
|
end
|
1254
1268
|
end
|
1269
|
+
@this_rule_context = nil
|
1270
|
+
@this_stratum = -1
|
1255
1271
|
end
|
1256
1272
|
|
1257
1273
|
######## ids and timers
|
@@ -1282,10 +1298,10 @@ module Bud
|
|
1282
1298
|
EventMachine::release_machine
|
1283
1299
|
EventMachine::instance_variable_set('@reactor_running', false)
|
1284
1300
|
end
|
1301
|
+
|
1285
1302
|
# Shutdown all the Bud instances inherited from the parent process, but
|
1286
1303
|
# don't invoke their shutdown callbacks
|
1287
1304
|
Bud.shutdown_all_instances(false)
|
1288
|
-
|
1289
1305
|
$got_shutdown_signal = false
|
1290
1306
|
$signal_handler_setup = false
|
1291
1307
|
|
@@ -1305,16 +1321,16 @@ module Bud
|
|
1305
1321
|
end
|
1306
1322
|
|
1307
1323
|
# Signal handling. If multiple Bud instances are running inside a single
|
1308
|
-
# process, we want a SIGINT or SIGTERM signal to cleanly shutdown all of
|
1324
|
+
# process, we want a SIGINT or SIGTERM signal to cleanly shutdown all of
|
1325
|
+
# them. Note that we don't try to do any significant work in the signal
|
1326
|
+
# handlers themselves: we just set a flag that is checked by a periodic timer.
|
1309
1327
|
def self.init_signal_handlers(b)
|
1310
1328
|
$signal_lock.synchronize {
|
1311
|
-
#
|
1312
|
-
# reinitialize the signal handler in the child process.
|
1329
|
+
# Initialize or re-initialize signal handlers if necessary.
|
1313
1330
|
unless b.options[:signal_handling] == :none || $signal_handler_setup
|
1314
1331
|
EventMachine::PeriodicTimer.new(SIGNAL_CHECK_PERIOD) do
|
1315
1332
|
if $got_shutdown_signal
|
1316
1333
|
Bud.shutdown_all_instances
|
1317
|
-
Bud.stop_em_loop
|
1318
1334
|
$got_shutdown_signal = false
|
1319
1335
|
end
|
1320
1336
|
end
|
data/lib/bud/bud_meta.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'bud/rewrite'
|
2
2
|
|
3
3
|
class BudMeta #:nodoc: all
|
4
|
-
def initialize(
|
5
|
-
@bud_instance =
|
6
|
-
@declarations =
|
7
|
-
@
|
4
|
+
def initialize(bud_i)
|
5
|
+
@bud_instance = bud_i
|
6
|
+
@declarations = bud_i.methods.select {|m| m =~ /^__bloom__.+$/}.map {|m| m.to_s}
|
7
|
+
@rule_idx = 0
|
8
|
+
|
9
|
+
# The results of bud_meta are analyzed further using a helper Bloom
|
10
|
+
# instance. See depanalysis().
|
11
|
+
@dependency_analysis = nil
|
8
12
|
end
|
9
13
|
|
10
14
|
def meta_rewrite
|
@@ -12,20 +16,30 @@ class BudMeta #:nodoc: all
|
|
12
16
|
|
13
17
|
stratified_rules = []
|
14
18
|
if @bud_instance.toplevel == @bud_instance
|
15
|
-
nodes
|
19
|
+
nodes = compute_node_graph
|
20
|
+
analyze_dependencies(nodes)
|
21
|
+
|
22
|
+
stratum_map = stratify_preds(nodes)
|
23
|
+
top_stratum = stratum_map.values.max
|
24
|
+
top_stratum ||= -1
|
16
25
|
|
17
26
|
# stratum_map = {fully qualified pred => stratum}. Copy stratum_map data
|
18
27
|
# into t_stratum format.
|
19
28
|
raise unless @bud_instance.t_stratum.to_a.empty?
|
20
29
|
@bud_instance.t_stratum.merge(stratum_map.to_a)
|
21
30
|
|
22
|
-
# slot each rule into the stratum corresponding to its lhs pred (from stratum_map)
|
23
31
|
stratified_rules = Array.new(top_stratum + 2) { [] } # stratum -> [ rules ]
|
24
32
|
@bud_instance.t_rules.each do |rule|
|
25
33
|
if rule.op == '<='
|
26
34
|
# Deductive rules are assigned to strata based on the basic Datalog
|
27
|
-
# stratification algorithm
|
28
|
-
|
35
|
+
# stratification algorithm. Note that we don't place all the rules
|
36
|
+
# with a given lhs relation in the same strata; rather, we place a
|
37
|
+
# rule in the lowest strata we can, as determined by the rule's rhs
|
38
|
+
# relations (and whether the rel is used in a non-monotonic context).
|
39
|
+
body_rels = find_body_rels(rule)
|
40
|
+
body_strata = body_rels.map {|r,is_nm| stratum_map[r] + (is_nm ? 1 : 0) || 0}
|
41
|
+
belongs_in = body_strata.max
|
42
|
+
|
29
43
|
# If the rule body doesn't reference any collections, it won't be
|
30
44
|
# assigned a stratum, so just place it in stratum zero
|
31
45
|
belongs_in ||= 0
|
@@ -35,28 +49,39 @@ class BudMeta #:nodoc: all
|
|
35
49
|
stratified_rules[top_stratum + 1] << rule
|
36
50
|
end
|
37
51
|
end
|
38
|
-
|
39
|
-
# stratified_rules[
|
40
|
-
#
|
41
|
-
|
52
|
+
|
53
|
+
# stratified_rules[0] may be empty if none of the nodes at stratum 0 are
|
54
|
+
# on the lhs stratified_rules[top_stratum+1] will be empty if there are no
|
55
|
+
# temporal rules.
|
56
|
+
stratified_rules.reject! {|r| r.empty?}
|
57
|
+
|
58
|
+
stratified_rules.each_with_index do |strat,strat_num|
|
59
|
+
strat.each do |rule|
|
60
|
+
@bud_instance.t_rule_stratum << [rule.bud_obj, rule.rule_id, strat_num]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
42
64
|
dump_rewrite(stratified_rules) if @bud_instance.options[:dump_rewrite]
|
43
65
|
end
|
44
66
|
return stratified_rules
|
45
67
|
end
|
46
68
|
|
69
|
+
def find_body_rels(rule)
|
70
|
+
@bud_instance.t_depends.map do |d|
|
71
|
+
[d.body, d.nm] if d.rule_id == rule.rule_id and d.bud_obj == rule.bud_obj
|
72
|
+
end.compact
|
73
|
+
end
|
74
|
+
|
47
75
|
def shred_rules
|
48
|
-
#
|
49
|
-
#
|
50
|
-
# after making this pass, we no longer care about the names of methods.
|
51
|
-
# we are shredding down to the granularity of rule heads.
|
52
|
-
seed = 0
|
76
|
+
# After making this pass, we no longer care about the names of methods. We
|
77
|
+
# are shredding down to the granularity of rule heads.
|
53
78
|
rulebag = {}
|
54
79
|
@bud_instance.class.ancestors.reverse.each do |anc|
|
55
80
|
@declarations.each do |meth_name|
|
56
|
-
rw = rewrite_rule_block(anc, meth_name
|
81
|
+
rw = rewrite_rule_block(anc, meth_name)
|
57
82
|
if rw
|
58
|
-
seed = rw.rule_indx
|
59
83
|
rulebag[meth_name] = rw
|
84
|
+
@rule_idx = rw.rule_idx
|
60
85
|
end
|
61
86
|
end
|
62
87
|
end
|
@@ -67,7 +92,7 @@ class BudMeta #:nodoc: all
|
|
67
92
|
end
|
68
93
|
end
|
69
94
|
|
70
|
-
def rewrite_rule_block(klass, block_name
|
95
|
+
def rewrite_rule_block(klass, block_name)
|
71
96
|
return unless klass.respond_to? :__bloom_asts__
|
72
97
|
|
73
98
|
pt = klass.__bloom_asts__[block_name]
|
@@ -103,7 +128,7 @@ class BudMeta #:nodoc: all
|
|
103
128
|
end
|
104
129
|
raise Bud::CompileError, "#{error_msg} in rule block \"#{block_name}\"#{src_msg}"
|
105
130
|
end
|
106
|
-
rewriter = RuleRewriter.new(
|
131
|
+
rewriter = RuleRewriter.new(@bud_instance, @rule_idx)
|
107
132
|
rewriter.process(pt)
|
108
133
|
return rewriter
|
109
134
|
end
|
@@ -176,64 +201,61 @@ class BudMeta #:nodoc: all
|
|
176
201
|
end
|
177
202
|
|
178
203
|
|
179
|
-
Node = Struct.new :name, :status, :stratum, :edges, :in_lhs, :in_body, :in_cycle, :is_neg_head
|
180
204
|
# Node.status is one of :init, :in_progress, :done
|
205
|
+
Node = Struct.new :name, :status, :stratum, :edges, :in_lhs, :in_body, :already_neg
|
181
206
|
Edge = Struct.new :to, :op, :neg, :temporal
|
182
207
|
|
183
|
-
def
|
184
|
-
bud = @bud_instance.toplevel
|
208
|
+
def compute_node_graph
|
185
209
|
nodes = {}
|
186
|
-
|
187
|
-
#t_depends [:bud_instance, :rule_id, :lhs, :op, :body] => [:nm, :in_body]
|
188
|
-
lhs = (nodes[d.lhs] ||= Node.new(d.lhs, :init, 0, [], true, false
|
210
|
+
@bud_instance.toplevel.t_depends.each do |d|
|
211
|
+
# t_depends [:bud_instance, :rule_id, :lhs, :op, :body] => [:nm, :in_body]
|
212
|
+
lhs = (nodes[d.lhs] ||= Node.new(d.lhs, :init, 0, [], true, false))
|
189
213
|
lhs.in_lhs = true
|
190
|
-
body = (nodes[d.body] ||= Node.new(d.body, :init, 0, [], false, true
|
191
|
-
temporal = d.op != "<="
|
192
|
-
lhs.edges << Edge.new(body, d.op, d.nm, temporal)
|
214
|
+
body = (nodes[d.body] ||= Node.new(d.body, :init, 0, [], false, true))
|
193
215
|
body.in_body = true
|
216
|
+
temporal = (d.op != "<=")
|
217
|
+
lhs.edges << Edge.new(body, d.op, d.nm, temporal)
|
194
218
|
end
|
195
219
|
|
220
|
+
return nodes
|
221
|
+
end
|
222
|
+
|
223
|
+
def stratify_preds(nodes)
|
196
224
|
nodes.each_value {|n| calc_stratum(n, false, false, [n.name])}
|
225
|
+
|
197
226
|
# Normalize stratum numbers because they may not be 0-based or consecutive
|
198
227
|
remap = {}
|
199
228
|
# if the nodes stratum numbers are [2, 3, 2, 4], remap = {2 => 0, 3 => 1, 4 => 2}
|
200
229
|
nodes.values.map {|n| n.stratum}.uniq.sort.each_with_index do |num, i|
|
201
230
|
remap[num] = i
|
202
231
|
end
|
232
|
+
|
203
233
|
stratum_map = {}
|
204
|
-
top_stratum = -1
|
205
234
|
nodes.each_pair do |name, n|
|
206
235
|
n.stratum = remap[n.stratum]
|
207
236
|
stratum_map[n.name] = n.stratum
|
208
|
-
top_stratum = max(top_stratum, n.stratum)
|
209
237
|
end
|
210
|
-
|
211
|
-
return nodes, stratum_map, top_stratum
|
238
|
+
return stratum_map
|
212
239
|
end
|
213
240
|
|
214
|
-
def max(a, b) ; a > b ? a : b ; end
|
215
|
-
|
216
241
|
def calc_stratum(node, neg, temporal, path)
|
217
242
|
if node.status == :in_process
|
218
|
-
node.
|
219
|
-
if neg and !temporal and node.is_neg_head
|
243
|
+
if neg and not temporal and not node.already_neg
|
220
244
|
raise Bud::CompileError, "unstratifiable program: #{path.uniq.join(',')}"
|
221
245
|
end
|
222
246
|
elsif node.status == :init
|
223
247
|
node.status = :in_process
|
224
248
|
node.edges.each do |edge|
|
225
|
-
node.is_neg_head = edge.neg
|
226
249
|
next unless edge.op == "<="
|
250
|
+
node.already_neg = neg
|
227
251
|
body_stratum = calc_stratum(edge.to, (neg or edge.neg), (edge.temporal or temporal), path + [edge.to.name])
|
228
|
-
node.
|
229
|
-
node.stratum = max(node.stratum, body_stratum + (edge.neg ? 1 : 0))
|
252
|
+
node.stratum = [node.stratum, body_stratum + (edge.neg ? 1 : 0)].max
|
230
253
|
end
|
231
254
|
node.status = :done
|
232
255
|
end
|
233
256
|
node.stratum
|
234
257
|
end
|
235
258
|
|
236
|
-
|
237
259
|
def analyze_dependencies(nodes) # nodes = {node name => node}
|
238
260
|
preds_in_lhs = nodes.select {|_, node| node.in_lhs}.map {|name, _| name}.to_set
|
239
261
|
preds_in_body = nodes.select {|_, node| node.in_body}.map {|name, _| name}.to_set
|