'''
Inspect live Curry objects.
'''
from .common import T_SETGRD, T_FAIL, T_CONSTR, T_FREE, T_FWD, T_CHOICE, T_FUNC, T_CTOR
from . import backends, config, icurry, objects
from .utility import visitation
import abc
import collections.abc
import os
import re
SUBDIR = config.intermediate_subdir()
[docs]
def info_of(arg):
if is_boxed(arg):
return arg.info
[docs]
def tag_of(arg):
if is_boxed(arg):
return arg.info.tag
elif isa_unboxed_primitive(arg):
return T_CTOR
[docs]
def isa(arg, what):
'''
Checks whether the given Curry object is an instance of the given type or
constructor. The second argument may be a sequence to check against.
'''
if not is_boxed(arg):
return False
else:
return _isa(id(arg.info), what)
@visitation.dispatch.on('what')
def _isa(addr, what):
raise TypeError(
'arg 2 must be an instance or sequence of %s.objects.CurryNodeInfo '
'objects.' % __package__
)
@_isa.when(objects.CurryNodeInfo)
def _isa(addr, nodeinfo):
return addr == id(nodeinfo.info)
@_isa.when(objects.CurryDataType)
def _isa(addr, typedef):
return _isa(addr, typedef.constructors)
@_isa.when(collections.abc.Sequence, no=str)
def _isa(addr, seq):
return any(_isa(addr, ti) for ti in seq)
[docs]
def isa_curry_expr(arg):
return is_boxed(arg) or isa_unboxed_primitive(arg)
[docs]
def isa_curry_expr_or_none(arg):
return arg is None or isa_curry_expr(arg)
[docs]
def isa_boxed_primitive(arg):
info = info_of(arg)
return info is not None and info.is_primitive
[docs]
def unboxed_value(arg):
if isa_boxed_primitive(arg):
return arg.successor(0)
elif isa_unboxed_primitive(arg):
return arg
[docs]
def isa_unboxed_primitive(arg):
ty = type(arg)
result = _unboxed_kinds.get(ty)
if result is None or (not result and _cache_token != abc.get_cache_token()):
_refresh_cache_token()
result = _unboxed_kinds[ty] = issubclass(ty, icurry.IUnboxedLiteral)
if not result and arg.__class__ is not ty:
return isinstance(arg, icurry.IUnboxedLiteral)
return result
[docs]
def isa_primitive(arg):
return isa_boxed_primitive(arg) or isa_unboxed_primitive(arg)
[docs]
def isa_boxed_int(arg):
info = info_of(arg)
return info is not None and info.is_int
[docs]
def isa_unboxed_int(arg):
return isinstance(arg, int)
[docs]
def isa_int(arg):
return isa_boxed_int(arg) or isa_unboxed_int(arg)
[docs]
def isa_boxed_char(arg):
info = info_of(arg)
return info is not None and info.is_char
[docs]
def isa_unboxed_char(arg):
return isinstance(arg, str) and len(arg) == 1
[docs]
def isa_char(arg):
return isa_boxed_char(arg) or isa_unboxed_char(arg)
[docs]
def isa_boxed_float(arg):
info = info_of(arg)
return info is not None and info.is_float
[docs]
def isa_unboxed_float(arg):
return isinstance(arg, float)
[docs]
def isa_float(arg):
return isa_boxed_float(arg) or isa_unboxed_float(arg)
[docs]
def isa_io(arg):
info = info_of(arg)
return info is not None and info.is_io
[docs]
def isa_bool(arg):
info = info_of(arg)
return info is not None and info.is_bool
[docs]
def isa_true(arg):
info = info_of(arg)
return info is not None and info.is_bool and info.tag == 1
[docs]
def isa_false(arg):
info = info_of(arg)
return info is not None and info.is_bool and info.tag == 0
[docs]
def isa_list(arg):
info = info_of(arg)
return info is not None and info.is_list
[docs]
def isa_cons(arg):
info = info_of(arg)
return info is not None and info.is_list and info.tag == 0
[docs]
def cons_head(arg):
if isa_cons(arg):
return arg.successor(0)
[docs]
def cons_tail(arg):
if isa_cons(arg):
return arg.successor(1)
[docs]
def isa_nil(arg):
info = info_of(arg)
return info is not None and info.is_list and info.tag == 1
[docs]
def isa_tuple(arg):
info = info_of(arg)
return info is not None and info.is_tuple
_TUPLE_PATTERN = re.compile(r'\(,*\)$')
[docs]
def isa_tuple_name(name):
return re.match(_TUPLE_PATTERN, name)
_OPERATOR_PATTERN = re.compile(r'([^_a-zA-Z0-9\(\)\'\"\[\]])+$')
[docs]
def isa_operator_name(name):
return re.match(_OPERATOR_PATTERN, name)
[docs]
def isa_setguard(arg):
info = info_of(arg)
return info is not None and info.tag == T_SETGRD
[docs]
def isa_failure(arg):
info = info_of(arg)
return info is not None and info.tag == T_FAIL
[docs]
def isa_constraint(arg):
info = info_of(arg)
return info is not None and info.tag == T_CONSTR
[docs]
def isa_freevar(arg):
info = info_of(arg)
return info is not None and info.tag == T_FREE
[docs]
def isa_fwd(arg):
info = info_of(arg)
return info is not None and info.tag == T_FWD
[docs]
def isa_choice(arg):
info = info_of(arg)
return info is not None and info.tag == T_CHOICE
[docs]
def isa_func(arg):
info = info_of(arg)
return info is not None and info.tag == T_FUNC
[docs]
def isa_ctor(arg):
info = info_of(arg)
return info is not None and info.tag >= T_CTOR
[docs]
def is_data(arg):
return isa_ctor(arg) or isa_unboxed_primitive(arg)
# ``is_boxed`` and ``isa_unboxed_primitive`` test against abstract base
# classes. Such an isinstance call costs several Python-level calls, and the
# evaluator makes millions of them. The result depends on the type of the
# object, so it is cached per type. A negative result can change when some
# class registers a new virtual subclass. abc.get_cache_token reports that, so
# a negative entry is checked against the token on every use.
#
# isinstance also consults ``__class__``, which a proxy or a mock may set to a
# class other than its type, and differently per instance. The cache stands
# for the type alone; an object whose ``__class__`` differs from its type gets
# the uncached check when the type says no.
_NODE_TYPE = 1 # instances of backends.Node
_PLAIN_TYPE = 2 # built-in value types: not a Node, and no is_boxed attribute
_OTHER_TYPE = 3 # anything else: consult the is_boxed attribute
_PLAIN_TYPES = frozenset([
bool, bytes, complex, float, int, memoryview, str, type(None)
])
_boxed_kinds = {}
_unboxed_kinds = {}
_cache_token = None
def _refresh_cache_token():
global _cache_token
token = abc.get_cache_token()
if token != _cache_token:
_boxed_kinds.clear()
_unboxed_kinds.clear()
_cache_token = token
def _boxed_kind(ty):
_refresh_cache_token()
if issubclass(ty, backends.Node):
kind = _NODE_TYPE
elif ty in _PLAIN_TYPES:
kind = _PLAIN_TYPE
else:
kind = _OTHER_TYPE
_boxed_kinds[ty] = kind
return kind
[docs]
def is_boxed(node):
ty = type(node)
kind = _boxed_kinds.get(ty)
if kind == _NODE_TYPE:
return True
if kind is None or _cache_token != abc.get_cache_token():
kind = _boxed_kind(ty)
if kind == _NODE_TYPE:
return True
if node.__class__ is not ty and isinstance(node, backends.Node):
return True
if kind == _PLAIN_TYPE:
return False
return getattr(node, 'is_boxed', False)
[docs]
def get_choice_id(arg):
# Note: a variable has a choice ID (which equals its variable ID).
if isa_choice(arg) or isa_freevar(arg):
return arg.successor(0)
[docs]
def get_choice_alternatives(arg):
assert isa_choice(arg)
return arg.successor(1), arg.successor(2)
[docs]
def get_left_alternative(arg):
assert isa_choice(arg)
return arg.successor(1)
[docs]
def get_right_alternative(arg):
assert isa_choice(arg)
return arg.successor(2)
[docs]
def get_freevar_id(arg):
if isa_freevar(arg):
return arg.successor(0)
[docs]
def get_set_id(arg):
if isa_setguard(arg):
return arg.successor(0)
[docs]
def get_setguard_value(arg):
if isa_setguard(arg):
return arg.successor(1)
[docs]
def fwd_target(arg):
if isa_fwd(arg):
return arg.successor(0)
[docs]
def fwd_chain_target(arg):
while True:
after = fwd_target(arg)
if after is None:
return arg
else:
arg = after
[docs]
def getjsonfile(moduleobj):
'''Returns the file containing ICurry-JSON, if one exists, or None.'''
from . import toolchain
for filename in toolchain.jsonfilenames(moduleobj.__file__):
if os.path.exists(filename):
return filename
[docs]
def geticurryfile(moduleobj):
'''Gets the ICurry file associated with a module.'''
from . import toolchain
filename = toolchain.icurryfilename(moduleobj.__file__)
if os.path.exists(filename):
return filename
[docs]
def geticurry(obj):
'''
Gets the ICurry of a module, a type, or a symbol.
A module loaded from its compiled code carries a bill of materials: an
IModule whose functions have no body (``IModule.fromBOM``). For such a
module, and for a function of it, the ICurry is read from the ICurry-JSON
file beside the module, and the function is found there by name. The
file is read once per module object. A module without the file gives
the bill of materials.
'''
if isinstance(obj, objects.CurryModule):
return _module_icurry(obj)
icy = getattr(obj, '.icurry', None)
icy = icy or getattr(obj, 'icurry', None)
if isinstance(obj, objects.CurryNodeInfo) and _is_placeholder(icy):
moduleobj = obj.module
if moduleobj is not None:
imodule = _module_icurry(moduleobj)
return imodule.functions.get(icy.name, icy)
return icy
def _is_placeholder(ifun):
'''Tells whether a function has the empty body of a bill of materials.'''
return isinstance(ifun, icurry.IFunction) and \
isinstance(getattr(ifun.body, 'block', None), icurry.IExempt)
def _module_icurry(moduleobj):
'''
The ICurry of a module object. A bill of materials is replaced by the
ICurry read from the ICurry-JSON file of the module, when there is one.
'''
icy = getattr(moduleobj, '.icurry')
if not isinstance(icy, icurry.IModule) or \
not any(_is_placeholder(f) for f in icy.functions.values()):
return icy
complete = getattr(moduleobj, '.icurry.complete', None)
if complete is None:
if getattr(moduleobj, '__file__', None) is None:
return icy
jsonfile = getjsonfile(moduleobj)
if jsonfile is None:
return icy
from . import toolchain
complete = toolchain.loadjson(jsonfile)
setattr(moduleobj, '.icurry.complete', complete)
return complete
[docs]
def getimpl(obj):
'''
Gets the implementation of a module or symbol as text.
For a module, the code the backend generates for it (see ``curry.save``).
For a function symbol, the code of its step function. The Python backend
gives the generated Python function, or the Python source of a built-in.
The C++ backend gives the generated C++ function, read from the .cpp file
beside the compiled module, or the bytecode of an interpreted function
(flag ``interpret``) with its constants.
Raises:
ValueError:
No code is available: a constructor, a built-in of the C++ runtime, or
a function the backend made without a step.
'''
if isinstance(obj, objects.CurryNodeInfo):
interp = obj.interpreter
if interp is None:
raise ValueError(
'no implementation code available for %r' % obj.fullname
)
return interp.backend.getimpl(obj)
elif isinstance(obj, objects.CurryModule):
curry = __import__(__package__)
return curry.save(obj, module_main=False)
elif hasattr(obj, 'getimpl'):
return obj.getimpl()
raise TypeError(
'cannot get the implementation of a %r object' % type(obj).__name__
)
[docs]
def getsymbol(moduleobj, symbolname):
'''
Looks up a symbol relative to a module.
The name may contain dots. For example, looking up ``'List.nub'`` relative
to the ``Data`` module returns ``Data.List.nub``.
Args:
moduleobj:
An instance of CurryModule.
symbolname:
The symbol name.
Returns:
A Curry symbol.
'''
from .objects.handle import getHandle
h = getHandle(moduleobj)
return h.getsymbol(symbolname)
[docs]
def symbols(moduleobj, private=False):
'''
Gets the name of each function and constructor symbol defined in a module.
Args:
moduleobj:
An instance of CurryModule.
private:
Whether to include private symbols.
Returns:
A dict.
'''
from .objects.handle import getHandle
h = getHandle(moduleobj)
return h.symbols if private else h.public_symbols
[docs]
def gettype(moduleobj, typename):
'''
Looks up a type relative to a module.
The name may contain dots. For example, looking up ``'SetFunctions.Values'``
relative to the ``Control`` module returns ``Control.SetFunctions.Values``.
Args:
moduleobj:
An instance of CurryModule.
typename:
The type name.
Returns:
A Curry type.
'''
from .objects.handle import getHandle
h = getHandle(moduleobj)
return h.gettype(typename)
[docs]
def types(moduleobj):
'''
Gets the name of each type defined in a module.
Args:
moduleobj:
An instance of ``CurryModule``.
Returns:
A dict.
'''
from .objects.handle import getHandle
h = getHandle(moduleobj)
return h.types