bud 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|