Source code for curry.inspect

'''
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