diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-09-02 00:01:09 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-09-02 00:09:17 +0300 |
commit | b5c4ec42ac2cc59dc3b104277ce2e85f5f77c88e (patch) | |
tree | 9b37605b78a3d2398da4addca3ee37899621c38e /contrib/python/Automat/py3/automat/_core.py | |
parent | 88fb7b5c334c3afdffacd104ee20efda4400d1bc (diff) | |
download | ydb-b5c4ec42ac2cc59dc3b104277ce2e85f5f77c88e.tar.gz |
Intermediate changes
Diffstat (limited to 'contrib/python/Automat/py3/automat/_core.py')
-rw-r--r-- | contrib/python/Automat/py3/automat/_core.py | 146 |
1 files changed, 92 insertions, 54 deletions
diff --git a/contrib/python/Automat/py3/automat/_core.py b/contrib/python/Automat/py3/automat/_core.py index 4118a4b070a..fc637b3a0f4 100644 --- a/contrib/python/Automat/py3/automat/_core.py +++ b/contrib/python/Automat/py3/automat/_core.py @@ -5,23 +5,41 @@ A core state-machine abstraction. Perhaps something that could be replaced with or integrated into machinist. """ +from __future__ import annotations +import sys from itertools import chain +from typing import Callable, Generic, Optional, Sequence, TypeVar, Hashable + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias _NO_STATE = "<no state>" +State = TypeVar("State", bound=Hashable) +Input = TypeVar("Input", bound=Hashable) +Output = TypeVar("Output", bound=Hashable) -class NoTransition(Exception): +class NoTransition(Exception, Generic[State, Input]): """ A finite state machine in C{state} has no transition for C{symbol}. - @param state: the finite state machine's state at the time of the - illegal transition. + @ivar state: See C{state} init parameter. - @param symbol: the input symbol for which no transition exists. + @ivar symbol: See C{symbol} init parameter. """ - def __init__(self, state, symbol): + def __init__(self, state: State, symbol: Input): + """ + Construct a L{NoTransition}. + + @param state: the finite state machine's state at the time of the + illegal transition. + + @param symbol: the input symbol for which no transition exists. + """ self.state = state self.symbol = symbol super(Exception, self).__init__( @@ -29,31 +47,33 @@ class NoTransition(Exception): ) -class Automaton(object): +class Automaton(Generic[State, Input, Output]): """ A declaration of a finite state machine. Note that this is not the machine itself; it is immutable. """ - def __init__(self): + def __init__(self, initial: State | None = None) -> None: """ Initialize the set of transitions and the initial state. """ - self._initialState = _NO_STATE - self._transitions = set() - + if initial is None: + initial = _NO_STATE # type:ignore[assignment] + assert initial is not None + self._initialState: State = initial + self._transitions: set[tuple[State, Input, State, Sequence[Output]]] = set() + self._unhandledTransition: Optional[tuple[State, Sequence[Output]]] = None @property - def initialState(self): + def initialState(self) -> State: """ Return this automaton's initial state. """ return self._initialState - @initialState.setter - def initialState(self, state): + def initialState(self, state: State) -> None: """ Set this automaton's initial state. Raises a ValueError if this automaton already has an initial state. @@ -61,12 +81,18 @@ class Automaton(object): if self._initialState is not _NO_STATE: raise ValueError( - "initial state already set to {}".format(self._initialState)) + "initial state already set to {}".format(self._initialState) + ) self._initialState = state - - def addTransition(self, inState, inputSymbol, outState, outputSymbols): + def addTransition( + self, + inState: State, + inputSymbol: Input, + outState: State, + outputSymbols: tuple[Output, ...], + ): """ Add the given transition to the outputSymbol. Raise ValueError if there is already a transition with the same inState and inputSymbol. @@ -74,44 +100,51 @@ class Automaton(object): # keeping self._transitions in a flat list makes addTransition # O(n^2), but state machines don't tend to have hundreds of # transitions. - for (anInState, anInputSymbol, anOutState, _) in self._transitions: - if (anInState == inState and anInputSymbol == inputSymbol): + for anInState, anInputSymbol, anOutState, _ in self._transitions: + if anInState == inState and anInputSymbol == inputSymbol: raise ValueError( - "already have transition from {} via {}".format(inState, inputSymbol)) - self._transitions.add( - (inState, inputSymbol, outState, tuple(outputSymbols)) - ) + "already have transition from {} to {} via {}".format( + inState, anOutState, inputSymbol + ) + ) + self._transitions.add((inState, inputSymbol, outState, tuple(outputSymbols))) + def unhandledTransition( + self, outState: State, outputSymbols: Sequence[Output] + ) -> None: + """ + All unhandled transitions will be handled by transitioning to the given + error state and error-handling output symbols. + """ + self._unhandledTransition = (outState, tuple(outputSymbols)) - def allTransitions(self): + def allTransitions(self) -> frozenset[tuple[State, Input, State, Sequence[Output]]]: """ All transitions. """ return frozenset(self._transitions) - - def inputAlphabet(self): + def inputAlphabet(self) -> set[Input]: """ The full set of symbols acceptable to this automaton. """ - return {inputSymbol for (inState, inputSymbol, outState, - outputSymbol) in self._transitions} + return { + inputSymbol + for (inState, inputSymbol, outState, outputSymbol) in self._transitions + } - - def outputAlphabet(self): + def outputAlphabet(self) -> set[Output]: """ The full set of symbols which can be produced by this automaton. """ return set( chain.from_iterable( - outputSymbols for - (inState, inputSymbol, outState, outputSymbols) - in self._transitions + outputSymbols + for (inState, inputSymbol, outState, outputSymbols) in self._transitions ) ) - - def states(self): + def states(self) -> frozenset[State]: """ All valid states; "Q" in the mathematical description of a state machine. @@ -119,47 +152,52 @@ class Automaton(object): return frozenset( chain.from_iterable( (inState, outState) - for - (inState, inputSymbol, outState, outputSymbol) - in self._transitions + for (inState, inputSymbol, outState, outputSymbol) in self._transitions ) ) - - def outputForInput(self, inState, inputSymbol): + def outputForInput( + self, inState: State, inputSymbol: Input + ) -> tuple[State, Sequence[Output]]: """ A 2-tuple of (outState, outputSymbols) for inputSymbol. """ - for (anInState, anInputSymbol, - outState, outputSymbols) in self._transitions: + for anInState, anInputSymbol, outState, outputSymbols in self._transitions: if (inState, inputSymbol) == (anInState, anInputSymbol): return (outState, list(outputSymbols)) - raise NoTransition(state=inState, symbol=inputSymbol) + if self._unhandledTransition is None: + raise NoTransition(state=inState, symbol=inputSymbol) + return self._unhandledTransition + +OutputTracer = Callable[[Output], None] +Tracer: TypeAlias = "Callable[[State, Input, State], OutputTracer[Output] | None]" -class Transitioner(object): + +class Transitioner(Generic[State, Input, Output]): """ The combination of a current state and an L{Automaton}. """ - def __init__(self, automaton, initialState): - self._automaton = automaton - self._state = initialState - self._tracer = None + def __init__(self, automaton: Automaton[State, Input, Output], initialState: State): + self._automaton: Automaton[State, Input, Output] = automaton + self._state: State = initialState + self._tracer: Tracer[State, Input, Output] | None = None - def setTrace(self, tracer): + def setTrace(self, tracer: Tracer[State, Input, Output] | None) -> None: self._tracer = tracer - def transition(self, inputSymbol): + def transition( + self, inputSymbol: Input + ) -> tuple[Sequence[Output], OutputTracer[Output] | None]: """ Transition between states, returning any outputs. """ - outState, outputSymbols = self._automaton.outputForInput(self._state, - inputSymbol) + outState, outputSymbols = self._automaton.outputForInput( + self._state, inputSymbol + ) outTracer = None if self._tracer: - outTracer = self._tracer(self._state._name(), - inputSymbol._name(), - outState._name()) + outTracer = self._tracer(self._state, inputSymbol, outState) self._state = outState return (outputSymbols, outTracer) |