'''Code for indexing to subexpressions.'''
from ....common import T_SETGRD, T_FWD
from ....exceptions import CurryIndexError, CurryTypeError
from .... import inspect
from .node import Node
import collections, collections.abc, itertools, numbers
__all__ = ['compress_fwd_chain', 'logical_subexpr', 'realpath', 'subexpr']
[docs]
def logical_subexpr(root, path, update_fwd_nodes=True):
'''
Like subexpr, but assumes a logical path. That is, steps through forward
nodes and set guards do not appear in the path.
'''
return realpath_parts(root, path, update_fwd_nodes)[0]
[docs]
def compress_fwd_chain(end):
'''Compresses a chain of forward nodes.'''
chain = []
while inspect.tag_of(end) == T_FWD:
chain.append(end)
end = inspect.fwd_target(end)
for node in chain:
# set_successor writes through on both backends; on the C++ backend
# ``successors`` is a fresh list, so assigning into it would be lost.
node.set_successor(0, end)
return end
def _is_path_sequence(path):
'''Tells whether ``path`` is a sequence or iterator of path components.'''
return isinstance(path, (list, tuple)) or (
isinstance(path, (collections.abc.Sequence, collections.abc.Iterator))
and not isinstance(path, str)
)
def _components(path):
'''
Generates the integer components of ``path``. A nested sequence is
flattened. A component that is neither an integer nor a sequence raises
CurryIndexError when it is reached.
'''
if isinstance(path, int) or isinstance(path, numbers.Integral):
yield path
elif _is_path_sequence(path):
for part in path:
yield from _components(part)
else:
raise CurryIndexError(
'path must be an integer or sequence of integers, not %r'
% type(path).__name__
)
# Marks the end of a path.
_END = object()
# The result of a call to ``realpath``.
Realpath = collections.namedtuple('Realpath', ['target', 'realpath', 'guards'])
def realpath_parts(root, path, update_fwd_nodes):
'''
Implements ``realpath``. Returns the target, the real path, and the guards
as a plain tuple. The evaluator creates a variable with this function for
every inductive position, so it runs as one loop without helper objects.
'''
if not inspect.isa_curry_expr(root):
raise CurryTypeError('invalid Curry expression %r' % root)
if type(path) is int:
components = iter((path,))
elif type(path) is list or type(path) is tuple:
components = iter(path)
else:
components = _components(path)
target = root
parent = None
realpath = []
guards = set()
while True:
# Skip over forward nodes and set guards.
while True:
if isinstance(target, Node):
tag = target.info.tag
else:
tag = inspect.tag_of(target)
if tag == T_FWD:
if update_fwd_nodes and realpath:
end = compress_fwd_chain(target)
parent.set_successor(realpath[-1], end)
target = end
else:
parent = target
realpath.append(0)
target = target.successors[0]
elif tag == T_SETGRD:
guards.add(target.successors[0])
parent = target
realpath.append(1)
target = target.successors[1]
else:
break
# Step to the next successor.
i = next(components, _END)
if i is _END:
return target, realpath, guards
if type(i) is not int:
if _is_path_sequence(i):
components = itertools.chain(_components(i), components)
continue
if not isinstance(i, numbers.Integral):
raise CurryIndexError(
'path must be an integer or sequence of integers, not %r'
% type(i).__name__
)
parent = target
try:
target = parent.successors[i]
except (IndexError, AttributeError):
raise CurryIndexError('node index out of range')
realpath.append(i)
[docs]
def realpath(root, path, update_fwd_nodes=True):
'''
Gets the real path from ``root`` to the subexpression along the logical path
``path``. The real path is formed by skipping over forward nodes and set
guards. These implicit steps are inserted into the path so that the result
can be passed to ``index``. Optionally, forward nodes can be spliced out
instead of added to the real path.
Args:
root:
A Curry expression. Can be an instance of graph.Node or a built-in such
as an ``int``, ``str``, or ``float``.
path:
A sequence of integers specifying the logical path to the intended
subexpression.
update_fwd_nodes:
When True, forward nodes are spliced out, where possible, and chains of
forward nodes are short-cut to their end.
Returns:
A ``namedtuple`` (target, realpath, guards), where ``target`` is the
subexpression at ``root[path]``; ``realpath`` is the actual path used to
reach the target, including entries for any forward nodes or set guards
skipped over; and ``guards`` is a set containing the IDs for each guard
crossed.
'''
return Realpath(*realpath_parts(root, path, update_fwd_nodes))
[docs]
def subexpr(root, path):
'''
Performs straightforward indexing into a Curry expression. Returns the
subexpression at ``root[path]``. No special handling of any kind is
performed. More specifically, neither forward nodes nor set guards are
skipped, and boxed fundamental types can be indexed to find their unboxed
contents.
Args:
root:
The expression at which to begin indexing. This is usually an instance
of ``Node``, though unboxed values are accepted if the path is empty.
path:
An integral number or sequence of such numbers specifying the path.
Raises:
CurryTypeError:
``root`` is not a Curry expression.
CurryIndexError:
A path component is invalid.
Returns:
The subexpression at ``root[path]``.
'''
if isinstance(path, int) or isinstance(path, numbers.Integral):
if not inspect.isa_curry_expr(root):
raise CurryTypeError('invalid Curry expression %r' % root)
try:
return root.successors[path]
except (IndexError, AttributeError):
raise CurryIndexError('node index out of range')
elif _is_path_sequence(path):
target = root
for i in path:
target = subexpr(target, i)
return target
else:
raise CurryIndexError(
'node index must be an integer or sequence of integers, not %r'
% type(path).__name__
)