Source code for pygritia.core
"""
Core functionality for lazy expressions
"""
from typing import Any, Mapping, Optional, Sequence, Type, TypeVar, Union, cast
from .util import setattr_
LazyNamespace = Mapping[Union[str, 'LazyMixin'], Any]
LazyNS = LazyNamespace
[docs]class LazyAction:
"""
Lazy Expression Handler
Every derived LazyAction classes must implements :py:meth:`evaluate` method.
It is called by :py:func:`evaluate` function with given namespace
"""
__slots__: Sequence[str] = ('owner',)
owner: 'LazyMixin'
"""Owner lazy expression of this action"""
def __repr__(self) -> str:
return f"<{self.__class__.__name__}: {str(self)}>"
[docs] def evaluate(self, namespace: LazyNamespace) -> Any:
"""Evaluate expression
To substitute actual value for specific symbol, give value with keyword argument.
"""
raise NotImplementedError
[docs] def update(self, val: Any, namespace: LazyNamespace) -> None: # pylint: disable=no-self-use
"""Update value of expression
If the expression is readonly, it raises AttributeError
"""
raise AttributeError("expr cannot be updated")
[docs]class LazyMixin(metaclass=LazyMeta):
"""
Base class of all lazy expression and mixin classes
It provides *Protocol* for lazy expression
All derived classes of this class have ``__hash__`` automatically, because namespace for
evaluate is defined as lazy expression (normally symbol only) to value mapping. Key of
mapping must be hashable.
"""
__slots__: Sequence[str] = ('__action__', '__weakref__')
__action__: LazyAction
def __init_subclass__(cls) -> None:
if '__hash__' not in cls.__dict__:
def __hash__(self: LazyMixin) -> int:
return hash(self.__action__)
setattr(cls, '__hash__', __hash__)
def __init__(self, action: LazyAction, origin: Optional['LazyMixin'] = None) -> None:
del origin
setattr_(self, '__action__', action)
action.owner = self
def __str__(self) -> str:
return str(self.__action__)
def __repr__(self) -> str:
return f"<{self.__class__.__name__}: {str(self)}>"
Lazy = TypeVar('Lazy', bound=LazyMixin)
LazyType = Type[LazyMixin]
_T = TypeVar('_T')
[docs]def evaluate(expr: _T, namespace: LazyNamespace) -> _T:
"""
Evaluate lazy expression
Evaluate expression with symbol substitution according to given ``namespace``
:param expr: Lazy expression or evaluated value
If given value is not a lazy expression, this function returns it immediately
:param namespace: Symbol table which will be used in substitution
The key of table can be both of string and symbol expression
:return: Evaluated value
"""
if isinstance(expr, LazyMixin):
return cast(_T, expr.__action__.evaluate(namespace))
return expr
[docs]def update(expr: _T, val: _T, namespace: LazyNamespace) -> None:
"""
Update the value of lazy expression
Set the value of lazy expression to the given ``val``
Only *assignable expression* can be updated.
ex) ``this[3]``, ``this.spam``
:param expr: Assignable lazy expression
:param val: New value
:param namespace: Symbol table which will be used in substitution
The key of table can be both of string and symbol expression
"""
if not isinstance(expr, LazyMixin):
raise TypeError("Expr must be a lazy expression")
val = evaluate(val, namespace)
if isinstance(val, LazyMixin):
raise TypeError("Val is not fully evaluated")
expr.__action__.update(val, namespace)
[docs]def repr_(expr: Any) -> str:
"""
``repr()`` for lazy expression
Native ``repr`` returns string like ``'<Lazy: this>'``. If you want to get the repr string
like other objects, use this instead of native one. It returns expression only for lazy
expression and ``repr()`` for other object.
:param expr: object for repr
:return: repr string of given object
"""
if isinstance(expr, LazyMixin):
return str(expr)
return repr(expr)