Source code for prodigy.pgcl.util.ref

"""
------------------
Mutable References
------------------
"""
from typing import (Callable, Dict, Generic, Iterable, List, Tuple, TypeVar,
                    Union)

import attr

M = TypeVar("M")
Key = TypeVar("Key")


[docs] @attr.s(repr=False) class Mut(Generic[M]): """ A mutable reference to a value of type M, represented a getter and a setter. Use :attr:`val` to read and write the value. Mut is useful to transform an AST while walking it. """ read: Callable[[], M] = attr.ib() write: Callable[[M], None] = attr.ib() @property def val(self) -> M: """Property that wraps ``read`` and ``write``.""" return self.read() @val.setter def val(self, value: M): self.write(value) def __repr__(self): return f'Mut(val={self.val!r})'
[docs] @staticmethod def alloc(value: M) -> "Mut[M]": """Create a new Mut in a new anonmyous location.""" mut = Mut(lambda: value, lambda v: None) def setter(new_value: M): nonlocal value value = new_value mut.write = setter return mut
[docs] @staticmethod def wrap(ref: Union["Mut[M]", M]) -> "Mut[M]": """If ``ref`` isn' already a Mut, wrap it with :meth:`alloc`.""" if isinstance(ref, Mut): return ref return Mut.alloc(ref)
[docs] @staticmethod def list(values: List[M]) -> Iterable["Mut[M]"]: """ Create a Mut for each element in the list. .. doctest:: >>> my_list = [1, 2, 3] >>> ref_list = list(Mut.list(my_list)) >>> ref_list[0].val = 15 >>> my_list [15, 2, 3] """ for i in range(len(values)): def getter(ii=i): return values[ii] def setter(new_value: M, ii=i): values[ii] = new_value yield Mut(getter, setter)
[docs] @staticmethod def dict_items(values: Dict[Key, M]) -> Iterable[Tuple[Key, "Mut[M]"]]: """ Create a Mut for each key-value pair in the dict. .. doctest:: >>> my_dict = {'hello': 42} >>> refs = list(Mut.dict_items(my_dict)) >>> refs[0][1].val = 43 >>> my_dict {'hello': 43} """ for key in values.keys(): def getter(keyy=key): return values[keyy] def setter(new_value: M, keyy=key): values[keyy] = new_value yield (key, Mut(getter, setter))
[docs] @staticmethod def dict_values(values: Dict[Key, M]) -> Iterable["Mut[M]"]: """ A Mut reference to each value in the map. See :meth:`dict_items`. """ for _, value_ref in Mut.dict_items(values): yield value_ref
[docs] @staticmethod def tuple(values: Tuple[M, ...]) -> Iterable["Mut[M]"]: """ A Mut reference to each value in the tuple. Note that tuples are immutable, meaning assignment to its items will result in an exception. .. doctest:: >>> my_tuple = ('hello', 42, 3.14) >>> refs = list(Mut.tuple(my_tuple)) >>> [mut.val for mut in refs] ['hello', 42, 3.14] >>> refs[1].val = 43 Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment """ for i in range(len(values)): def getter(ii=i): return values[ii] def setter(new_value: M, ii=i): # this always raises an exception, which is what we want here values[ii] = new_value # type: ignore yield Mut(getter, setter)
[docs] @staticmethod def iterate( reference: Union["Mut[M]", "Mut[List[M]]"]) -> Iterable["Mut[M]"]: """ If this is a reference to a list of elements, apply :meth:`list`. Otherwise return only the reference itself. .. doctest:: >>> list(Mut.iterate(Mut.alloc('a'))) [Mut(val='a')] >>> list(Mut.iterate(Mut.alloc(['a', 'b']))) [Mut(val='a'), Mut(val='b')] """ if isinstance(reference.val, list): yield from Mut.list(reference.val) else: # mypy can't yet infer the type of reference based on the isinstance check above res: "Mut[M]" = reference # type:ignore yield res
Ref = Union[Mut, M] """A Ref is either a Mut or a value."""