def rewrite(self, match): reason = ByRule(self, match) # If there are nodes in rhs that has no corresponding match from the lhs, we should create # new nodes for them (like x -> and(x, or(x, y)), y is such a node) for n in self._rhs_leaf_nodes: if not n in match: match = dict(match) match[n] = Node() if isinstance(self._rhs, Term): to_add = list_term_elements(self._rhs.apply_map(match), top_node=match[self._lhs], reason=reason) if self.destructive: return {'remove': [match[self._lhs_hyperedge]], 'add': to_add} else: return {'add': to_add} else: to_merge = [(match[self._lhs], self._rhs.apply_map(match), reason)] if self.destructive: return { 'remove': [match[self._lhs_hyperedge]], 'merge': to_merge } else: return {'merge': to_merge}
def smallest_matches(self, node, top_node=None): for p in self._smallest_term_matches(node): yield { k: v for k, v in zip(list_term_elements(p[0], top_node=top_node), p[1]) }
def __init__(self, equality, reverse=False, destructive=False, name=None): equality = parse(equality) if name is None: name = ("(rev)" if reverse else "") + ("(des)" if destructive else "") + equality.name lhs = equality.lhs rhs = equality.rhs if reverse: lhs, rhs = rhs, lhs if isinstance(lhs, Term): node, lhs_hyperedge, *_ = list_term_elements(lhs) lhs = node else: lhs_hyperedge = None if destructive: logging.warning("LHS is a node, destructivity is ignored") destructive = False if not isinstance(lhs, (Term, Node)) or not isinstance(rhs, (Term, Node)): raise ValueError( "Both lhs and rhs of the equality should be Terms or nodes: {} = {}" .format(lhs, rhs)) self.equality = equality self.reverse = reverse self.destructive = destructive self.name = name self.trigger = lhs self._lhs = lhs self._rhs = rhs self._lhs_hyperedge = lhs_hyperedge self._rhs_leaf_nodes = leaf_nodes(rhs)
def add_trigger(self, pattern, callback): if isinstance(pattern, Term): pattern = list_term_elements(pattern)[0] assert isinstance(pattern, Node) if len(pattern.outgoing) == 0: # special case def _on_new_node(node, pattern=pattern, callback=callback): callback({pattern: node}) self._node_callbacks.append(_on_new_node) return multerm, matchlist = self._pattern_to_multerm(pattern) # printind("\n================") # printind(pattern) # printind(multerm) # printind(matchlist) # printind("================\n") pat_to_index = {} index_mergelist = [] for i, e in enumerate(matchlist): if e in pat_to_index: if isinstance(e, Node): index_mergelist.append((pat_to_index[e], i)) else: pat_to_index[e] = i def _on_this_multerm(matches, pat_to_index=pat_to_index, index_mergelist=index_mergelist, self=self, callback=callback): # printind() # printind("Multerm", multerm) # printind("Matched", matches) for matched in self._matches_to_matchlists(matches): # printind(" list", matched) # printind("matchlist", matchlist) need_merged = [(matched[p[0]], matched[p[1]]) for p in index_mergelist] # printind("need merged", need_merged) def _on_pattern_matched(matched=matched, pat_to_index=pat_to_index, self=self, callback=callback): match = { n: matched[i].follow() for n, i in pat_to_index.items() } if all(e in self.hypergraph for e in match.values()): callback(match) self._add_multimerge_callback(need_merged, _on_pattern_matched) self._multerm_callbacks.setdefault(multerm, []).append(_on_this_multerm) # Since existing nodes may match the multerm, we have to trigger the new callback for n in self.hypergraph.nodes(): existing_matches = self._get_node_matches(n, multerm) if existing_matches: _on_this_multerm(existing_matches) # We do this at the end because this will immediately trigger stuff for existing hyperedges self._add_multerm(multerm)
def smallest_term_match_single(self, node, top_node=None): p = self._smallest_term_matches_single(node) return p[0], { k: v for k, v in zip(list_term_elements(p[0], top_node=top_node), p[1]) }
def run_script(hypergraph, script, cache=None): if cache is None: cache = {} if isinstance(script, RunAllScript): result = [run_script(hypergraph, s, cache) for s in script.subscripts] elif isinstance(script, IthElementScript): result = run_script(hypergraph, script.script, cache)[script.index] elif isinstance(script, IncidentNode): result = run_script(hypergraph, script.hyperedge, cache).incident(script.index) elif script in cache: result = cache[script] elif isinstance(script, FreeNodeScript): added = hypergraph.rewrite(add=Node())[0] cache[script] = added result = added elif isinstance(script, MatchScript): match = {} elements = script.elements for e in elements: if isinstance(e, Hyperedge): match[e] = run_script(hypergraph, script.hyperedge_scripts[e], cache) for in1, in2, ms in script.merge_scripts: n1 = match[in1.hyperedge].incident(in1.index).follow() n2 = match[in2.hyperedge].incident(in2.index).follow() if n1 != n2: run_script(hypergraph, ms, cache) for e in elements: if isinstance(e, Node): if not e in match: if e in script.node_scripts: match[e] = run_script(hypergraph, script.node_scripts[e], cache) else: # As noted somewhere else, there may be no e.outgoing and e.incoming for h in match: if isinstance(h, Hyperedge): incs = IncidentNode.all_of(h, e) if incs: match[e] = match[h].incident(incs[0].index) break assert match[e] is not None match = match_follow(match) if not still_match(match, hypergraph): raise RuntimeError("The match built according to the script " "doesn't match the hypergraph") cache[script] = match result = match elif isinstance(script, RuleApplicationScript): match = run_script(hypergraph, script.match_script, cache) match = match_follow(match) rw = script.rule.rewrite(match) added = hypergraph.rewrite(**rw) cache[script] = added result = added elif isinstance(script, AddTermsScript): added = hypergraph.rewrite(add=script.terms)[len(script.terms):] res = [] start = 0 for t in script.terms: tsize = len(list_term_elements(t)) res.append(added[start:start + tsize]) start += tsize cache[script] = res result = res else: raise ValueError("Don't know how to run this script {}".format(script)) return result