A **Mealy State Machine** is a finite-state machine whose output values are determined both by its current state and its current inputs.
In [mealy.py](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py), this is modeled by:
- **States**: Unique nodes in the machine.
- **Transitions**: Directed connections between states triggered by specific inputs.
- **Action Functions (Outputs)**: Arbitrary callbacks associated with transitions that receive a mutable user-defined context object.
> [!NOTE]
> By passing a mutable context down to transition actions, you can build rich state-dependent behavior while keeping the machine's state logic simple and decoupled.
---
## Installation
Since the library uses [pyproject.toml](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/pyproject.toml) with the Hatchling build backend, you can install it locally in editable mode or build it using standard tools:
```bash
# Install in editable mode
pip install -e .
# Or build the package
python -m build
```
---
## Quick Start (Binary Inverter)
Here is a simple example demonstrating how to invert a binary string (`"0"` becomes `"1"`, `"1"` becomes `"0"`) using [inverter.py](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/examples/inverter.py).
```python
fromtypingimportTuple,TypedDict
fromsnanosm.mealyimportMachine
# Define a context to hold our state machine's output and metadata
classContext(TypedDict):
result:str
n_chars:int
defadd_to_result(context:Context,c:str)->None:
context["result"]+=c
context["n_chars"]+=1
definverter(input_string:str)->Tuple[str,int]:
# Initialize the mutable context
context:Context={
"result":"",
"n_chars":0
}
# Create the machine with the context
m=Machine(context)
# Add a start state "S"
m.add_state("S",is_start_state=True)
# Define transitions: when in state "S" and input is "0", execute action and stay in "S"
The sequence detector in [detector.py](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/examples/detector.py) searches for the substring `"AB"` within a stream of characters. It illustrates the use of [TransitionInputEnum](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py#L14) to define catch-all transitions when no specific input matches.
| [InputProtocol](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py#L6) | A typing protocol requiring `__eq__` and `__hash__`. Any hashable, equatable Python object can serve as machine input. |
| [State](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py#L22) | Represents a state node in the state machine. |
| [Transition](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py#L33) | Represents an edge between states triggered by a transition input. |
| [Machine](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py#L53) | The core finite state machine runner. |
Any custom object used as an input to [Machine.process_input](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py#L103) must implement this protocol (or be natively hashable and equatable, e.g. strings, integers, frozen dataclasses).
-`__init__(initial_context: object = None)`: Initializes the machine. Sets up the configuration using an optional initial context. If not provided, an empty dict `{}` is instantiated.
-`add_state(state_name: str, is_start_state: bool = False, is_end_state: bool = False) -> None`: Registers a new state node in the machine.
> [!WARNING]
> State names must not start with `#` (reserved for internal configurations). A machine cannot have multiple start states.
-`add_transition(transition_input: TransitionInput, origin_name: str, destination_name: str, output_function: Optional[Callable[[Optional[object]], None]]) -> None`: Registers a transition edge between two existing states.
-`transition_input`: An input conforming to [InputProtocol](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py#L6) or [TransitionInputEnum](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py#L14).
-`output_function`: A callable accepting context, run upon transitioning.
-`process_input(i: Input) -> None`: Processes a single input token. It evaluates transitions registered under the current state.
- If a transition matching `i` is registered, it will be executed.
- If no matching transition is found but a `MATCH_REST` transition is registered, that fallback is executed.
- If no valid transition is found, it raises a `ValueError`.
-`reset() -> None`: Resets the state machine's active state back to the start state, and re-assigns the context back to `initial_context`.
-`get_current_state() -> Optional[State]`: Returns the current [State](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/src/snanosm/mealy.py#L22) object, or `None` if the machine has not started or processed any inputs.
-`is_in_final_state() -> bool`: Returns `True` if the machine's current state is registered as an end state.
-`__str__() -> str`: Returns a structured string layout of the machine structure, lists of states, and transitions.
---
## Testing & Verification
Unit tests are located in [test_mealy.py](file:///Users/davidgildegomezperez/PycharmProjects/snanosm/tests/test_mealy.py). To run the test suite, navigate to the project directory and execute: