class PushTypeLibrary(dict):
    """A collection of PushTypes which can support a corresponding PushStack.

    Parameters
    ----------
    register_core : bool, optional
        If True, all core types will be registered. Default is True.
    *args
        A collection of PushTypes to register.

    Attributes
    ----------
    register_core : bool, optional
        If True, all core types will be registered. Default is True.
    *args
        A collection of PushTypes to register.

    """

    def __init__(self, register_core: bool = True, *args):
        super().__init__()
        if register_core:
            self.register_core()
        self.register_list(args)
        self.create_and_register("code", (Atom,), force=True)
        self.create_and_register("exec", (Atom,), force=True)

    def register(self, push_type: PushType, _force=False):
        """Register a PushType object.

        Parameters
        ----------
        push_type
            PushType to register.
        _force : bool, optional
            For internal use only. Default is False.

        Returns
        -------
        PushTypeLibrary
            A reference to the ``PushTypeLibrary``.

        """
        name = push_type.name
        if (not _force) and (name in RESERVED_PSEUDO_STACKS):
            raise ValueError("Cannot register PushType with name {nm} because it is reserved.".format(nm=name))
        self[name] = push_type
        return self

    def create_and_register(self,
                            name: str,
                            python_types: Tuple[type, ...],
                            is_collection: bool = False,
                            is_numeric: bool = False,
                            force=False):
        """Create a PushType and register it into the library.

        Parameters
        ----------
        name : str
            A name for the type. Used when referencing the PushType in Instruction
            definitions and will be the key in the PushState for the corresponding
            PushStack.
        python_types : Tuple[type]
            A tuple of python types that correspond to the underlying
            native types which the PushType is representing.
        is_collection : bool, optional
            Indicates if the PushType is a collection. Default is False.
        is_numeric : bool, optional
            Indicates if the PushType is a number. Default is False.
        force : bool
            If True, will register the type even if it will overwrite an
            existing reserved stack typed (eg. exec, stdout, untyped). Default
            is False. It is not recommended this argument be changed unless
            you have a very good reason to do so.

        Returns
        -------
        PushTypeLibrary
            A reference to the PushTypeLibrary.

        """
        p_type = PushType(name, python_types, is_collection=is_collection, is_numeric=is_numeric)
        self.register(p_type, force)
        return self

    def unregister(self, push_type_name: str):
        """Unregister a push type by name.

        Parameters
        ----------
        push_type_name
            The name of the push type to unregister.

        Returns
        -------
        PushTypeLibrary
            A reference to the PushTypeLibrary.

        """
        if push_type_name in RESERVED_PSEUDO_STACKS:
            raise ValueError("Cannot unregister PushType with name {nm} because it is reserved.".format(nm=push_type_name))
        self.pop(push_type_name, None)
        return self

    def register_list(self, list_of_push_types: Sequence[PushType]):
        """Register a list of PushType objects.

        Parameters
        ----------
        list_of_push_types
            List of Instruction objects to register.

        Returns
        -------
        PushTypeLibrary
            A reference to the PushTypeLibrary.

        """
        for push_type in list_of_push_types:
            self.register(push_type)
        return self

    def register_core(self):
        """Register all core PushTypes defined in pyshgp.

        Returns
        -------
        PushTypeLibrary
            A reference to the PushTypeLibrary.

        """
        for push_type in CORE_PUSH_TYPES:
            self.register(push_type)

    def supported_stacks(self) -> Set[str]:
        """All stack names which the PushTypeLibrary can support.

        Returns
        -------
        set[str]
            A set of stacks names which the type library can support.

        """
        return set(self.keys())

    def push_type_of(self, thing: Any, error_on_not_found: bool = False) -> Optional[PushType]:
        """Return the PushType of the given thing.

        Parameters
        ----------
        thing : Any
            Any value to try and find the corresponding PushType.
        error_on_not_found : bool, optional
            If True, will raise error if no PushType found. Default is False.

        Returns
        -------
        Optional[PushType]
            The corresponding PushType of the thing. If no corresponding type, returns None.

        """
        for push_type in self.values():
            if push_type.is_instance(thing):
                return push_type
        if error_on_not_found:
            raise PushError.no_type(thing)
        return None

    def push_type_for_type(self, typ: type, error_on_not_found: bool = False) -> Optional[PushType]:
        """Return the PushType of the given python (or numpy) type.

        Parameters
        ----------
        typ : type
            Any type to try and find the corresponding PushType.
        error_on_not_found : bool, optional
            If True, will raise error if no PushType found. Default is False.

        Returns
        -------
        Optional[PushType]
            The corresponding PushType of the given type. If no corresponding type, returns None.

        """
        for push_type in self.values():
            if typ in push_type.python_types:
                return push_type
        if error_on_not_found:
            raise PushError.no_type(typ)
        return None class Char(str):
class PushType:
class PushBoolType(PushType):
class PushIntType(PushType):
class PushFloatType(PushType):
class PushCharType(PushType):
class PushStrType(PushType):
class PushVectorType(PushType):
class BoolVector(CheckedPVector):
class IntVector(CheckedPVector):
class FloatVector(CheckedPVector):
class CharVector(CheckedPVector):
class StrVector(CheckedPVector):
    def __init__(self, char):
    def __eq__(self, other):
    def __init__(self,
                 name: str,
                 python_types: Tuple[type, ...],
                 is_collection: bool = False,
                 is_numeric: bool = False):
    def is_instance(self, value) -> bool:
    def coerce(self, value):
    def __eq__(self, other):
    def __hash__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self):
    def __init__(self, name: str, p_vec_type):
    def coerce(self, value):
def make_vector_type(scalar_type: PushType):
CORE_SCALAR_PUSH_TYPES: List[PushType] = [
    PushBool,
    PushInt,
    PushChar,
    PushFloat,
    PushStr,
]
CORE_VECTOR_PUSH_TYPES: List[PushVectorType] = [
    PushIntVector,
    PushFloatVector,
    PushStrVector,
    PushBoolVector,
    PushCharVector,
]
CORE_PUSH_TYPES: List[PushType] = CORE_SCALAR_PUSH_TYPES + CORE_VECTOR_PUSH_TYPES class SimpleInstruction(Instruction):
    """A simple instruction implementation.

    A SimpleInstruction uses a standardized way of manipulating PushStates. In
    other words, it handles popping its own function arguments and pushing the
    function return values.

    The first step of evaluating a SimpleInstruction is to pop the arguments
    from the stacks corresponding the instruction's ``input_stacks`` list.
    If multiple occurrences of the same type are in ``input_stacks``, items are
    taken from progressively deeper in that stack. If the stacks of the
    PushState do not contain a sufficient number of items, the instruction does
    not modify the PushState.

    The popped arguments are then passed to the instruction's function to produce
    a tuple of outputs. It is crucial that the instruction's function produce a
    tuple of outputs, even if it only contains a single element. The elements of
    the tuple are then routed to the corresponding stacks specified in the
    instruction's ``output_stacks``.

    Parameters
    ----------
    name : str,
        A unique name for the instruction.
    f : Callable
        A function whose signature matches input_stacks and output_stacks.
    input_stacks : Sequence[str]
        A list of PushType names to use when popping arguments from the PushState.
    output_stacks : Sequence[str]
        A list of PushType names to use when pushing function results to the PushState.
    code_blocks : int
        The number of CodeBlocks to open following the instruction in a Genome.
    docstring : str, optional
        A string describing in the behavior of the Instruction.

    """

    __slots__ = ["name", "f", "input_stacks", "output_stacks", "code_blocks", "docstring"]

    def __init__(self,
                 name: str,
                 f: Callable,
                 input_stacks: Sequence[str],
                 output_stacks: Sequence[str],
                 code_blocks: int,
                 docstring="Write me!"):
        super().__init__(name, code_blocks, docstring)
        self.f = f
        self.input_stacks = input_stacks
        self.output_stacks = output_stacks

    def evaluate(self, push_state: PushState, push_config: PushConfig = None) -> PushState:
        """Evaluate the instruction on the given PushState. Return mutated State.

        A SimpleInstruction infers which values to pop and push from the stack
        based on its `input_stacks` and `output_stacks`.

        Parameters
        ----------
        push_state : PushState
            Push state to modify with the Instruction.
        push_config :  pyshgp.push.interpreter.PushConfig
            Configuration of the interpreter. Used to get various limits.

        Returns
        -------
        PushState
            Return the given state, possibly modified by the Instruction.

        """
        # Pull args, if present.
        args = push_state.observe_stacks(self.input_stacks)
        if Token.no_stack_item in args:
            return push_state

        # Compute result, return if revert or response too big.
        result = self.f(*args)
        if result is Token.revert:
            return push_state
        _check_is_seq(result, self)

        # Remove arguments, push results.
        push_state.pop_from_stacks(self.input_stacks)
        push_state.push_to_stacks(result, self.output_stacks)
        return push_state

    def required_stacks(self) -> Set[str]:
        """Return a list of PushType names relevant to the instruction.

        Based on the the instructions input and output types.
        """
        return set(self.input_stacks + self.output_stacks) - RESERVED_PSEUDO_STACKS class StateToStateInstruction(Instruction):
    """Instruction that takes entire PushState and returns entire PushState."""

    __slots__ = ["name", "f", "stacks_used", "code_blocks", "docstring"]

    def __init__(self,
                 name: str,
                 f: Callable,
                 stacks_used: Sequence[str],
                 code_blocks: int,
                 docstring="Write me!"):
        super().__init__(name, code_blocks, docstring)
        self.f = f
        self.stacks_used = set(stacks_used)

    def evaluate(self, push_state: PushState, push_config: PushConfig = None) -> PushState:
        """Evaluate the instruction on the given PushState. Return mutated State.

        A SimpleInstruction infers which values to pop and push from the stack
        based on its `input_stacks` and `output_stacks`.

        Parameters
        ----------
        push_state : PushState
            Push state to modify with the Instruction.
        push_config : PushConfig
            Configuration of the interpreter. Used to get various limits.

        Returns
        -------
        PushState
            Return the given state, possibly modified by the Instruction.

        """
        result = self.f(push_state)
        if result == Token.revert:
            return push_state
        else:
            return result

    def required_stacks(self) -> Set[str]:
        """Return a list of PushType names relevant to the instruction."""
        return self.stacks_used - RESERVED_PSEUDO_STACKS def _revert():
    return Token.revert def _wrap_tuple(*args):
    return tuple(args) def _dup(x):
    return x, x class Atom:
    """Base class of all Atoms. The fundamental element of Push programs."""

    @abstractmethod
    def pretty_str(self) -> str:
        """Generate a simple string representation of the Atom."""
        raise NotImplementedError() class InstructionMeta(Atom, PClass):
    """An identifier of a Push Instruction.

    The `InstructionMeta` is a placeholder atom. When a PushInterpreter evaluates
    an instruction ID, it searches for an instruction with the given
    name in its `InstructionSet`. If found, this instruction is evaluated by the `PushInterpreter`.

    Attributes
    ----------
    name: str
        The name of the Instruction being referenced.
    code_blocks: int
        The number of code blocks that are expected to follow the instruction.

    """

    name = field(type=str, mandatory=True)
    code_blocks = field(type=int, mandatory=True)

    def pretty_str(self) -> str:
        """Generate a simple string representation of the Instruction."""
        return self.name class CodeBlock(Atom, CheckedPVector):
    """An `Atom` which holds a sequence of other atoms in order to express a nested code block."""

    __type__ = Atom

    def size(self, depth: int = 1) -> int:
        """Return the size of the block and the size of all the nested blocks."""
        return sum([el.size(depth + 1) + 1 if isinstance(el, CodeBlock) else 1 for el in self])

    def depth(self) -> int:
        """Return the farthest depth of the CodeBlock."""
        i = iter(self)
        level = 0
        try:
            for level in count():
                i = chain([next(i)], i)
                i = chain.from_iterable(s for s in i if isinstance(s, Sequence) and not isinstance(s, str))
        except StopIteration:
            return level
        return 0

    def code_at_point(self, ndx: int) -> Optional[Atom]:
        """Return a nested element of the CodeBlock using depth first traversal."""
        if ndx == 0:
            return self
        i = ndx
        for el in self:
            i = i - 1
            if i == 0:
                return el
            if isinstance(el, CodeBlock):
                next_depth = el.code_at_point(i)
                if next_depth is not None:
                    return next_depth
                i = i - el.size()
        return None

    def with_code_inserted_at_point(self, code: Atom, index: int) -> CodeBlock:
        """Insert an element into the CodeBlock using depth first traversal."""
        if index > self.size():
            return self.append(code)
        _, new = self._attempt_code_insert(code, index)
        return new

    def _attempt_code_insert(self, code: Atom, index: int) -> Tuple[bool, CodeBlock]:
        i = index
        for idx, el in enumerate(self):
            if i == 0:
                new = CodeBlock(self[:idx] + [code] + self[idx:])
                return True, new
            if isinstance(el, CodeBlock):
                inserted, new = el._attempt_code_insert(code, i - 1)
                if inserted:
                    return True, self.set(idx, new)
                i = i - (el.size() + 1)
            i = i - 1
        if i == 0:
            return True, self.append(code)
        return False, self

    def pretty_str(self) -> str:
        """Generate a simple string representation of the CodeBlock."""
        return "(" + " ".join([el.pretty_str() for el in self]) + ")" class Literal(Atom, PClass):
    """An Atom which holds a constant value.

    Attributes
    ----------
    value : Any
        The value to be stored in Literal.
    push_type : PushType, optional
        The `PushType` of the `Literal`. Usually, the PushType's underlying native
        type is the same as the type of the Literal's value. The PushType is
        used to determine how to route the Literal onto the correct PushStack
        of the PushState during program execution.

    """

    __invariant__ = lambda l: (l.push_type.is_instance(l.value), "Value is not of PushType.")
    value = field(mandatory=True)
    push_type = field(type=PushType, mandatory=True)

    def pretty_str(self) -> str:
        """Generate a simple string representation of the Literal."""
        # @todo make smarter by using a method on the PushType.
        s = str(self.value)
        if isinstance(self.value, str):
            s = "\"" + s + "\""
        return s class PushState(dict):
    """A collection of PushStacks used during push program execution."""

    __slots__ = ["stdout", "inputs", "untyped", "type_library", "push_config"]

    def __init__(self, type_library: PushTypeLibrary, push_config: PushConfig):
        super().__init__()
        self.stdout = ""
        self.inputs = []
        self.untyped = deque([])
        self.type_library = type_library
        self.push_config = push_config

        for name, push_type in type_library.items():
            self[name] = PushStack(push_type, push_config)

    def __eq__(self, other) -> bool:
        if not isinstance(other, PushState):
            return False
        return super().__eq__(other) and self.inputs == other.inputs and self.stdout == other.stdout

    @classmethod
    def from_dict(cls, d, type_library: PushTypeLibrary, push_config: PushConfig):
        """Set the state to match the given dictionary.

        .. warning::
            This is written to be used in ``pyshgp`` tests, NOT as part of
            push program execution or evolution. There are no checks to confirm
            that the ``d`` can be converted to a valid Push state.

        Parameters
        ----------
        d : dict
            Dict that is converted into a Push state.
        type_library : PushTypeLibrary
            A Push type library.
        push_config : PushConfig
            The configuration of the current program being executed.

        """
        state = cls(type_library, push_config)
        inputs = []
        stdout = ""
        for k, v in d.items():
            if k == 'inputs':
                inputs = v
            elif k == 'stdout':
                stdout += v
            elif k == "untyped":
                for el in v:
                    state.untyped.append(el)
            else:
                for el in v:
                    state[k].push(el)
        state.load_inputs(inputs)
        state.stdout = stdout
        return state

    def load_code(self, program: CodeBlock):
        """Push the given CodeBlock to the execution stack."""
        self["exec"].push(program)

    def load_inputs(self, inputs):
        """Load a list of input values onto the PushState inputs.

        Parameters
        ----------
        inputs : list
            List of input values.

        """
        if not isinstance(inputs, (list, np.ndarray)):
            raise ValueError(
                "Push inputs must be a list, got {t}".format(t=type(inputs)))
        self.inputs = inputs

    def observe_stacks(self, types: Sequence[str]) -> list:
        """Return a list of output values based on the given types indicated.

        Items are take from the tops of each stack. If multiple occurrences of
        the same type are in ``output_types``, the returned values are taken
        from progressively deeper in that stack. Does not pop the values off
        the stacks.

        Parameters
        ----------
        types : list
            List of strings denoting the push types of the returned values.

        """
        values = []
        counts = {}
        for typ in types:
            if typ == "stdout":
                values.append(self.stdout)
            else:
                ndx = counts.get(typ, 0)
                values.append(self[typ].nth(ndx))
                counts[typ] = ndx + 1
        return values

    def pop_from_stacks(self, types: Sequence[str]) -> Union[Sequence, Token]:
        """Pop the top items for each value_type. Return a vector of the values popped from each stack."""
        values = []
        for typ in types:
            val = self[typ].top()
            if val is Token.no_stack_item:
                return Token.revert
            else:
                values.append(val)
                self[typ].pop()
        return values

    def push_to_stacks(self, values: list, types: Sequence[str]):
        """Check that all values can be coerced into their expected PushType. Push them onto the correct stack."""
        for ndx in range(len(values)):
            val = values[ndx]
            typ = types[ndx]
            if typ == "stdout":
                self.stdout += str(val)
            elif typ == "untyped":
                self.untyped.append(val)
            else:
                self[typ].push(val)

    def size(self):
        """Return the size of the PushState."""
        return sum([len(s) for s in self.values()]) + len(self.inputs)

    def pretty_print(self):
        """Print the state of all stacks in the PushState."""
        for k, v in self.items():
            print(" ".join([k, ":", str(v)]))
        print("untyped : " + str(self.untyped))
        print("inputs : " + str(self.inputs))
        print("stdout : " + str(self.stdout)) class Token(Enum):
    """Enum class of all Tokens."""

    no_stack_item = 1
    revert = 2
    whole_state = 3
from typing import Tuple, Union
from pyshgp.push.type_library import PushTypeLibrary
from pyshgp.push.types import PushInt
from pyshgp.push.instruction import (
    SimpleInstruction,
    StateToStateInstruction
)
from pyshgp.push.instructions.common import _revert, _wrap_tuple, _dup
from pyshgp.push.atoms import Atom, InstructionMeta, CodeBlock, Literal
from pyshgp.push.state import PushState
from pyshgp.utils import Token
    code = state["exec"].top()
    state["exec"].push(InstructionMeta(name="exec_while", code_blocks=1))
    state["exec"].push(code)
    return state


def _code_map(state: PushState) -> PushState:
    if state["exec"].is_empty() or state["code"].is_empty():
        return Token.revert
    e = state["exec"].pop()
    c = state["code"].pop()
    if not isinstance(c, CodeBlock):
        c = CodeBlock([c])
    l1 = [CodeBlock([InstructionMeta(name="code_from_exec", code_blocks=1), item, e]) for item in c]
    l2 = [InstructionMeta(name="code_combine", code_blocks=0) for _ in c[1:]]
    contents = l1 + [InstructionMeta(name="code_wrap", code_blocks=0)] + l2
    state["exec"].push(CodeBlock(contents))
    return state


def _if(b, _then, _else):
    return _then if b else _else,


def _code_when(state: PushState) -> PushState:
    if state["code"].is_empty() or state["bool"].is_empty():
        return Token.revert
    code = state["code"].pop()
    if state["bool"].pop():
        state["exec"].push(code)
    return state


def _exec_when(state: PushState) -> PushState:
    if state["exec"].is_empty() or state["bool"].is_empty():
        return Token.revert
    if not state["bool"].pop():
        state["exec"].pop()
    return state


def _code_member(code: Atom, item: Atom) -> Tuple[bool]:
    if not isinstance(code, CodeBlock):
        code = CodeBlock([code])
    return item in code,


def _code_nth(code: Atom, ndx: int) -> Tuple[Atom]:
    if not isinstance(code, CodeBlock):
        code = CodeBlock([code])
    if len(code) == 0:
        return Token.revert
    ndx = abs(ndx) % len(code)
    return code[ndx],


def _make_empty_code_block() -> Tuple[Atom]:
    return CodeBlock(),


def _is_empty_code_block(code: Atom) -> Tuple[bool]:
    return isinstance(code, CodeBlock) and len(code) == 0,


def _code_size(code: Atom) -> Tuple[int]:
    if not isinstance(code, CodeBlock):
        return 1,
    return code.size(),


def _code_extract(code: Atom, ndx: int) -> Union[Token, Tuple[Atom]]:
    if isinstance(code, CodeBlock) and code.size() == 0:
        return Token.revert
    if not isinstance(code, CodeBlock):
        return code,
    ndx = abs(ndx % code.size())
    return code.code_at_point(ndx),


def _code_insert(code1, code2, ndx) -> Union[Token, Tuple[Atom]]:
    if not isinstance(code1, CodeBlock):
        code1 = CodeBlock([code1])
    if code1.size() == 0:
        return code1.append(code2),
    ndx = abs(ndx) % code1.size()
    return code1.with_code_inserted_at_point(code2, ndx),


def _code_first_position(code1, code2) -> Union[Token, Tuple[int]]:
    if (not isinstance(code1, CodeBlock)) or (len(code1) == 0):
        if code1 == code2:
            return 0,
    else:
        for ndx, el in enumerate(code1):
            if el == code2:
                return ndx,
    return -1,


def _code_reverse(code):
    if not isinstance(code, CodeBlock):
        return code,
    return CodeBlock(code[::-1]),


def instructions(type_library: PushTypeLibrary):
    """Return all core code SimpleInstructions."""
    i = []

    i.append(SimpleInstruction(
        "noop",
        _revert,
        input_stacks=[],
        output_stacks=[],
        code_blocks=0,
        docstring="A noop SimpleInstruction which does nothing."
    ))

    i.append(SimpleInstruction(
        "noop_open",

        _revert,