Source code for curry.backends.py.graph.copy

'''
Code to copy Curry expressions.
'''

from ....common import T_FREE, T_FWD, T_SETGRD
from copy import copy
from .... import inspect
from . import node

__all__ = ['copygraph', 'copynode', 'GraphCopier', 'Skipper']

[docs] class GraphCopier(object): ''' Deep-copies an expression. The skipper can be used to remove nodes. This can be used to remove forward nodes and set guards from values. The traversal keeps its own stack, so a deep graph such as a long list cannot exhaust the Python stack. The copy of a node is created, and recorded in the memo, before its successors are copied. So a node reached by several paths is copied once, the copy keeps the sharing, and a cycle in the graph becomes a cycle in the copy. ''' def __init__(self, skipper=None): self.skipper = skipper # The number of free variables the copy shares. See # rts_control.make_value. self.freevars = 0 def __call__(self, expr, memo=None): ''' Copies ``expr``. The memo maps the ID of a node to its copy, as in ``copy.deepcopy``. Pass one memo to several calls to keep the sharing between the copies. The memo keeps the copied nodes alive, so their IDs stay unique. ''' if memo is None: memo = {} keep = None else: keep = memo.setdefault(id(memo), []) skip = self.skipper # The nodes whose copies are under construction. Each frame holds the # node, its copy, and the index of the successor copied next. stack = [] cur = expr while True: # Descend from ``cur`` until a value is at hand. while True: if not isinstance(cur, node.Node): # An unboxed value: an int, a float, a str of length one, a # memoryview of code points (a string literal), or an iterator (the # argument of a generator node). The copy shares it: the first four # cannot change, and an iterator cannot be copied. copy.deepcopy # rejects a memoryview and an iterator. value = cur break value = memo.get(id(cur)) if value is not None: break if skip is not None: target = skip(cur) if target is not None: cur = target continue if cur.info.tag == T_FREE: # A free variable is shared, not copied: one node stands for one # id, the one in the free-variable table (RuntimeState.vtable). # A copy with the same id would narrow apart from the variable: # the generator made on demand on the copy is not on the node the # table names (issue #120). A value handed to Python would hold # a node the runtime cannot find by its id as well. The variable # still counts: a later goal that holds the value must find it in # the table (see rts_control.make_value). The C++ copier has the # same rule (graph/copy.cpp). self.freevars += 1 value = cur break # The copy starts as a shallow copy. The copies of the successors # replace the originals one by one. A valid node needs no check of # its arity or its successors, so ``new_node`` is not called. value = object.__new__(type(cur)) value.info = cur.info value.successors = list(cur.successors) memo[id(cur)] = value if keep is not None: keep.append(cur) if not value.successors: break stack.append([cur, value, 0]) cur = value.successors[0] # Deliver ``value`` to the innermost frame. A finished frame produces # the value for its parent. while True: if not stack: return value frame = stack[-1] successors = frame[1].successors i = frame[2] successors[i] = value i += 1 if i < len(successors): frame[2] = i cur = successors[i] break value = frame[1] stack.pop()
[docs] class Skipper(object): ''' Indicates which nodes to skip. If a node should be skipped, the __call__ method should return its replacement. ''' def __init__(self, skipfwd=False, skipgrds=None): self.skipfwd = skipfwd self.skipgrds = set() if skipgrds is None else skipgrds def __call__(self, expr): tag = expr.info.tag if tag == T_FWD: if self.skipfwd: return inspect.fwd_target(expr) elif tag == T_SETGRD: if inspect.get_set_id(expr) in self.skipgrds: return inspect.get_setguard_value(expr)
[docs] def copygraph(expr, memo=None, **kwds): ''' Copies a Curry expression with the option to remove certain nodes. Args: expr: An instance of ``graph.Node`` or a built-in type such as ``int``, ``str``, or ``float``. memo: A dictionary that maps the ID of a node to its copy. See ``GraphCopier``. skipfwd: Indicates whether to skip FWD nodes. skipgrds: A container of set identifer indicating which set guards to skip. Returns: A deep copy of ``expr``. ''' copier = GraphCopier(skipper=Skipper(**kwds)) return copier(expr, memo=memo)
[docs] def copynode(expr, mapf=None): ''' Makes a shallow copy of a Curry expression. Args: expr The expression to copy. Can be an instance of ``graph.Node`` or a built-in type such as ``int``, ``str``, or ``float``. mapf An optional map function. If supplied, this function will be applied to the successors. Returns: A shallow copy of ``expr``. ''' if isinstance(expr, node.Node): info = expr.info partial = info.arity > len(expr.successors) if mapf is None: successors = expr.successors else: successors = map(mapf, expr.successors) return node.Node(info, *successors, partial=partial) else: return copy(expr)