from ....common import T_SETGRD, T_CONSTR, T_FREE, T_FWD, T_CHOICE, T_FUNC, T_CTOR
from ....common import F_INT_TYPE
from ....exceptions import CurryTypeError
from .... import backends, icurry, utility
from .... import inspect, show
from .infotable import InfoTable
import collections.abc, numbers, types
[docs]
class Node(object):
'''A node in a Curry expression graph.'''
# A node holds its info table and its successors, nothing else. Without an
# instance dictionary a node is smaller and quicker to create. The weak
# reference slot serves the record of result types of the typed builder
# (typecheck.builder.TypeRecord), which must not outlive the node.
__slots__ = ('info', 'successors', '__weakref__')
def __new__(cls, info, *args, target=None, partial_info=None, **kwds):
if not isinstance(info, InfoTable):
if isinstance(info, (types.GeneratorType, collections.abc.Sequence)):
assert not args
return Node(*info, target=target, partial_info=partial_info, **kwds)
info = getattr(info, 'info', info)
target = getattr(target, 'target', target) # accept target=Variable
if partial_info:
return Node.create_partial_applic(cls, info, *args, target=target, partial_info=partial_info)
else:
return new_node(cls, info, *args, target=target, **kwds)
[docs]
@staticmethod
def create_partial_applic(cls, info, *args, target=None, partial_info=None):
# new_node checks the count first: too many arguments raise TypeError,
# as on the C++ backend.
partexpr = new_node(cls, info, *args, partial=True)
missing = info.arity - len(args)
assert missing > 0
return Node(partial_info, missing, partexpr, target=target)
def __str__(self):
return show.show(self)
def __repr__(self):
return show.show(self, style='repr')
def __copy__(self):
return self.copy()
[docs]
def copy(self):
from .copy import copynode
return copynode(self)
def __deepcopy__(self, memo=None):
from .copy import copygraph
return copygraph(self, memo=memo)
def __getitem__(self, path):
from .indexing import logical_subexpr
return logical_subexpr(self, path, update_fwd_nodes=True)
def __setitem__(self, i, value):
# Used by generated code (INodeAssign) to close a cyclic expression built
# with None placeholders, e.g., a recursive let. Takes one integer index;
# the compiler indexes to the parent node first. Accepts a Variable.
assert isinstance(i, numbers.Integral)
value = getattr(value, 'rvalue', value)
assert inspect.isa_curry_expr_or_none(value)
self.successors[i] = value
[docs]
def successor(self, i):
return self.successors[i]
[docs]
def set_successor(self, i, value):
self.successors[i] = value
def __len__(self):
return len(self.successors)
def __iter__(self):
raise TypeError('Node does not support iteration')
def __eq__(self, rhs):
# A value that is not a Curry expression is never equal to a node.
# NotImplemented lets Python answer False for == and True for !=
# against a foreign object, as its data model expects; the C++ backend
# answers the same way (py::is_operator in the bindings).
if not inspect.isa_curry_expr(rhs):
return NotImplemented
from .equality import logically_equal
return logically_equal(self, rhs)
def __hash__(self):
return hash(id(self)) # for testing
def __ne__(self, rhs):
if not inspect.isa_curry_expr(rhs):
return NotImplemented
return not (self == rhs)
[docs]
def id(self):
return id(self)
[docs]
def rewrite(self, info, *args, **kwds):
Node(info, *args, target=self, **kwds)
[docs]
def forward_to(self, target):
from ..currylib.fundamental import Fwd
self.rewrite(Fwd, target)
[docs]
def walk(self, path=None):
'''See walkexpr.walk.'''
from .walkexpr import walk
return walk(self, path)
backends.Node.register(Node)
[docs]
def new_node(cls, info, *args, target=None, partial=False):
'''
Create or rewrite a node.
If the keyword 'target' is supplied, then the object will be constructed
there. This low-level function is intended for internal use only. To
construct an expression, use ``Interpreter.expr``.
Args:
info:
An instance of ``CurryNodeInfo`` or ``InfoTable`` indicating the kind of node to
create.
*args:
The successors. Each one should be a Node, built-in data object, or
None. The value None is not a valid successor, but is used to construct
cyclic expressions. These must be replaced before the object is used as
an expression. The number of successors must equal info.arity, unless
partial=True, in which case it should be strictly less.
target=None:
Keyword-only argument. If not None, this specifies an existing Node object
to rewrite.
partial=False:
Indicates whether this constructs a partial application. If False,
applying a function to too few arguments will cause ``TypeError`` to be
raised.
Returns:
A ``Node``.
'''
nargs = len(args)
if (nargs >= info.arity) if partial else (nargs != info.arity):
raise TypeError(
'cannot %s %r (arity=%d), with %d arg%s' % (
('curry' if partial else 'construct')
, info.name
, info.arity
, nargs
, '' if nargs == 1 else 's'
)
)
self = object.__new__(cls) if target is None else target
self.info = info
successors = [getattr(arg, 'rvalue', arg) for arg in args]
assert all(map(inspect.isa_curry_expr_or_none, successors))
if (info.flags & 0xf) == F_INT_TYPE and successors:
check_int_range(successors[0])
self.successors = successors
return self
# The range of Int: 64 bits, as on the C++ backend (issue #105). An Int
# node never holds a wider integer, so the two backends agree on every
# value. The message is the one the C++ backend raises
# (backends/cxx/cyrtbindings/graph.cpp).
INT_MIN = -(1 << 63)
INT_MAX = (1 << 63) - 1
[docs]
def check_int_range(value):
if type(value) is int and not INT_MIN <= value <= INT_MAX:
raise CurryTypeError(
'%d is outside the range of Int (%d to %d)' % (value, INT_MIN, INT_MAX)
)