Class: BudMeta
- Inherits:
-
Object
- Object
- BudMeta
- Defined in:
- lib/bud/bud_meta.rb
Overview
:nodoc: all
Defined Under Namespace
Instance Method Summary collapse
-
#analyze_dependencies(nodes) ⇒ Object
nodes = name => node.
- #calc_stratum(node, neg, temporal, path) ⇒ Object
-
#check_rule_ast(pt) ⇒ Object
Perform some basic sanity checks on the AST of a rule block.
- #compute_node_graph ⇒ Object
- #depanalysis ⇒ Object
- #dump_rewrite(strata) ⇒ Object
- #find_body_rels(rule) ⇒ Object
- #get_qual_name(pt) ⇒ Object
-
#initialize(bud_i) ⇒ BudMeta
constructor
A new instance of BudMeta.
- #meta_rewrite ⇒ Object
- #rewrite_rule_block(klass, block_name) ⇒ Object
- #shred_rules ⇒ Object
- #stratify_preds(nodes) ⇒ Object
Constructor Details
#initialize(bud_i) ⇒ BudMeta
Returns a new instance of BudMeta.
4 5 6 7 8 9 10 11 12 |
# File 'lib/bud/bud_meta.rb', line 4 def initialize(bud_i) @bud_instance = bud_i @declarations = bud_i.methods.select {|m| m =~ /^__bloom__.+$/}.map {|m| m.to_s} @rule_idx = 0 # The results of bud_meta are analyzed further using a helper Bloom # instance. See depanalysis(). @dependency_analysis = nil end |
Instance Method Details
#analyze_dependencies(nodes) ⇒ Object
nodes = name => node
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/bud/bud_meta.rb', line 259 def analyze_dependencies(nodes) # nodes = {node name => node} preds_in_lhs = nodes.select {|_, node| node.in_lhs}.map {|name, _| name}.to_set preds_in_body = nodes.select {|_, node| node.in_body}.map {|name, _| name}.to_set bud = @bud_instance out = bud.[:stdout] out ||= $stdout bud.t_provides.each do |p| pred, input = p.interface, p.input if input unless preds_in_body.include? pred # input interface is underspecified if not used in any rule body bud.t_underspecified << [pred, true] # true indicates input mode out.puts "Warning: input interface #{pred} not used" end else unless preds_in_lhs.include? pred # output interface underspecified if not in any rule's lhs bud.t_underspecified << [pred, false] # false indicates output mode out.puts "Warning: output interface #{pred} not used" end end end end |
#calc_stratum(node, neg, temporal, path) ⇒ Object
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/bud/bud_meta.rb', line 241 def calc_stratum(node, neg, temporal, path) if node.status == :in_process if neg and not temporal and not node.already_neg raise Bud::CompileError, "unstratifiable program: #{path.uniq.join(',')}" end elsif node.status == :init node.status = :in_process node.edges.each do |edge| next unless edge.op == "<=" node.already_neg = neg body_stratum = calc_stratum(edge.to, (neg or edge.neg), (edge.temporal or temporal), path + [edge.to.name]) node.stratum = [node.stratum, body_stratum + (edge.neg ? 1 : 0)].max end node.status = :done end node.stratum end |
#check_rule_ast(pt) ⇒ Object
Perform some basic sanity checks on the AST of a rule block. We expect a rule block to consist of a :defn whose body consists of a sequence of statements. Each statement is a :call node. Returns nil (no error found), a Sexp (containing an error), or a pair of [Sexp, error message].
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/bud/bud_meta.rb', line 157 def check_rule_ast(pt) # :defn format: node tag, block name, args, body_0, ..., body_n tag, name, args, *body = pt return pt if tag != :defn body.each_with_index do |n,i| next if i == 0 and n == s(:nil) # a block got rewritten to an empty block # Check for a common case if n.sexp_type == :lasgn return [n, "illegal operator: '='"] end return pt unless n.sexp_type == :call and n.length == 4 # Rule format: call tag, lhs, op, rhs _, lhs, op, rhs = n # Check that LHS references a named collection lhs_name = get_qual_name(lhs) return [n, "unexpected lhs format: #{lhs}"] if lhs_name.nil? unless @bud_instance.tables.has_key? lhs_name.to_sym or @bud_instance.lattices.has_key? lhs_name.to_sym return [n, "Collection does not exist: '#{lhs_name}'"] end return [n, "illegal operator: '#{op}'"] unless [:<, :<=].include? op # Check superator invocation. A superator that begins with "<" is parsed # as a call to the binary :< operator. The right operand to :< is a :call # node; the LHS of the :call is the actual rule body, the :call's oper is # the rest of the superator (unary ~, -, +), and the RHS is empty. Note # that ParseTree encodes unary "-" and "+" as :-@ and :-+, respectively. # XXX: We don't check for illegal superators (e.g., "<--"). That would be # tricky, because they are encoded as a nested unary op in the rule body. if op == :< return n unless rhs.sexp_type == :call op_tail = rhs[2] return n unless [:~, :-@, :+@].include? op_tail rhs_args = rhs[3..-1] return n unless rhs_args.empty? end end return nil # No errors found end |
#compute_node_graph ⇒ Object
208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/bud/bud_meta.rb', line 208 def compute_node_graph nodes = {} @bud_instance.toplevel.t_depends.each do |d| # t_depends [:bud_instance, :rule_id, :lhs, :op, :body] => [:nm, :in_body] lhs = (nodes[d.lhs] ||= Node.new(d.lhs, :init, 0, [], true, false)) lhs.in_lhs = true body = (nodes[d.body] ||= Node.new(d.body, :init, 0, [], false, true)) body.in_body = true temporal = (d.op != "<=") lhs.edges << Edge.new(body, d.op, d.nm, temporal) end return nodes end |
#depanalysis ⇒ Object
284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/bud/bud_meta.rb', line 284 def depanalysis if @dependency_analysis.nil? require 'bud/depanalysis' da = ::DepAnalysis.new da.providing <+ @bud_instance.tables[:t_provides].to_a da.depends <+ @bud_instance.t_depends.map{|d| [d.lhs, d.op, d.body, d.nm]} da.tick_internal @dependency_analysis = da end @dependency_analysis end |
#dump_rewrite(strata) ⇒ Object
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/bud/bud_meta.rb', line 296 def dump_rewrite(strata) fout = File.new("#{@bud_instance.class}_rewritten.txt", "w") fout.puts "Declarations:" strata.each_with_index do |rules, i| fout.puts "=================================" fout.puts "Stratum #{i}" rules.each do |r| fout.puts "#{r.bud_obj.class}##{r.bud_obj.object_id} #{r.rule_id}" fout.puts "\tsrc: #{r.src}" fout.puts "\torig src: #{r.orig_src}" end end fout.close end |
#find_body_rels(rule) ⇒ Object
69 70 71 72 73 |
# File 'lib/bud/bud_meta.rb', line 69 def find_body_rels(rule) @bud_instance.t_depends.map do |d| [d.body, d.nm] if d.rule_id == rule.rule_id and d.bud_obj == rule.bud_obj end.compact end |
#get_qual_name(pt) ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/bud/bud_meta.rb', line 136 def get_qual_name(pt) # expect to see a parse tree corresponding to a dotted name # a.b.c == s(:call, s1, :c) # where s1 == s(:call, s2, :b) # where s2 == s(:call, nil, :a) tag, recv, name, *args = pt return nil unless tag == :call and args.empty? if recv qn = get_qual_name(recv) return nil if qn.nil? or qn.size == 0 return "#{qn}.#{name}" else return name.to_s end end |
#meta_rewrite ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/bud/bud_meta.rb', line 14 def shred_rules # capture dependencies, rewrite rules stratified_rules = [] if @bud_instance.toplevel == @bud_instance nodes = compute_node_graph analyze_dependencies(nodes) stratum_map = stratify_preds(nodes) top_stratum = stratum_map.values.max top_stratum ||= -1 # stratum_map = {fully qualified pred => stratum}. Copy stratum_map data # into t_stratum format. raise unless @bud_instance.t_stratum.to_a.empty? @bud_instance.t_stratum.merge(stratum_map.to_a) stratified_rules = Array.new(top_stratum + 2) { [] } # stratum -> [ rules ] @bud_instance.t_rules.each do |rule| if rule.op == '<=' # Deductive rules are assigned to strata based on the basic Datalog # stratification algorithm. Note that we don't place all the rules # with a given lhs relation in the same strata; rather, we place a # rule in the lowest strata we can, as determined by the rule's rhs # relations (and whether the rel is used in a non-monotonic context). body_rels = find_body_rels(rule) body_strata = body_rels.map {|r,is_nm| stratum_map[r] + (is_nm ? 1 : 0) || 0} belongs_in = body_strata.max # If the rule body doesn't reference any collections, it won't be # assigned a stratum, so just place it in stratum zero belongs_in ||= 0 stratified_rules[belongs_in] << rule else # All temporal rules are placed in the last stratum stratified_rules[top_stratum + 1] << rule end end # stratified_rules[0] may be empty if none of the nodes at stratum 0 are # on the lhs stratified_rules[top_stratum+1] will be empty if there are no # temporal rules. stratified_rules.reject! {|r| r.empty?} stratified_rules.each_with_index do |strat,strat_num| strat.each do |rule| @bud_instance.t_rule_stratum << [rule.bud_obj, rule.rule_id, strat_num] end end dump_rewrite(stratified_rules) if @bud_instance.[:dump_rewrite] end return stratified_rules end |
#rewrite_rule_block(klass, block_name) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/bud/bud_meta.rb', line 95 def rewrite_rule_block(klass, block_name) return unless klass.respond_to? :__bloom_asts__ pt = klass.__bloom_asts__[block_name] return if pt.nil? pt = Marshal.load(Marshal.dump(pt)) # deep copy because RuleRewriter mucks up pt if @bud_instance.[:dump_ast] require 'pp' pp pt end = TempExpander.new pt = .process(pt) .tmp_tables.each do |t| @bud_instance.temp(t.to_sym) end rv = check_rule_ast(pt) unless rv.nil? if rv.class <= Sexp error_pt = rv error_msg = "parse error" else error_pt, error_msg = rv end # try to dump the source code associated with the problematic block, so as # to produce a more meaningful error message. begin code = Ruby2Ruby.new.process(Marshal.load(Marshal.dump(error_pt))) src_msg = "\nCode: #{code}" rescue Exception src_msg = "" end raise Bud::CompileError, "#{error_msg} in rule block \"#{block_name}\"#{src_msg}" end rewriter = RuleRewriter.new(@bud_instance, @rule_idx) rewriter.process(pt) return rewriter end |
#shred_rules ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/bud/bud_meta.rb', line 75 def shred_rules # After making this pass, we no longer care about the names of methods. We # are shredding down to the granularity of rule heads. rulebag = {} @bud_instance.class.ancestors.reverse.each do |anc| @declarations.each do |meth_name| rw = rewrite_rule_block(anc, meth_name) if rw rulebag[meth_name] = rw @rule_idx = rw.rule_idx end end end rulebag.each_value do |v| v.rules.each {|r| @bud_instance.t_rules << r} v.depends.each {|d| @bud_instance.t_depends << d} end end |
#stratify_preds(nodes) ⇒ Object
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/bud/bud_meta.rb', line 223 def stratify_preds(nodes) nodes.each_value {|n| calc_stratum(n, false, false, [n.name])} # Normalize stratum numbers because they may not be 0-based or consecutive remap = {} # if the nodes stratum numbers are [2, 3, 2, 4], remap = {2 => 0, 3 => 1, 4 => 2} nodes.values.map {|n| n.stratum}.uniq.sort.each_with_index do |num, i| remap[num] = i end stratum_map = {} nodes.each_pair do |name, n| n.stratum = remap[n.stratum] stratum_map[n.name] = n.stratum end return stratum_map end |