Skip to main content

A pure python small Mealy state machine library

Reason this release was yanked:

Deprecated

Project description

SNanoSM

A pure Python, minimalistic, and fully typed library for the implementation of Mealy Finite State Machines.

Part of Nobody Industry's MFFP (Made From First Principles) set of libraries.


Table of Contents

  1. Overview & Core Concept
  2. Installation
  3. Quick Start (Binary Inverter)
  4. Advanced Usage (Sequence Detector & Fallback Matching)
  5. API Reference
  6. Testing & Verification

Overview & Core Concept

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, 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 with the Hatchling build backend, you can install it locally in editable mode or build it using standard tools:

# 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.

from typing import Tuple, TypedDict
from snanosm.mealy import Machine

# Define a context to hold our state machine's output and metadata
class Context(TypedDict):
    result: str
    n_chars: int

def add_to_result(context: Context, c: str) -> None:
    context["result"] += c
    context["n_chars"] += 1

def inverter(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"
    m.add_transition("0", "S", "S", lambda ctx: add_to_result(ctx, "1"))
    m.add_transition("1", "S", "S", lambda ctx: add_to_result(ctx, "0"))
    
    # Process inputs sequentially
    for c in input_string:
        m.process_input(c)
        
    return context["result"], context["n_chars"]

if __name__ == '__main__':
    res, count = inverter("000011110010")
    print(f"Result: {res}, Characters processed: {count}")
    # Output: Result: 111100001101, Characters processed: 12

Advanced Usage (Sequence Detector & Fallback Matching)

The sequence detector in detector.py searches for the substring "AB" within a stream of characters. It illustrates the use of TransitionInputEnum to define catch-all transitions when no specific input matches.

from typing import TypedDict
from snanosm.mealy import Machine, TransitionInputEnum

class Context(TypedDict):
    count: int
    position: int
    matches: int

def dinc(context: Context):
    context["count"] += 1

def dset(context: Context):
    context["position"] = context["count"]
    context["count"] += 1

def dprn(context: Context):
    context["count"] += 1
    print(f"SUBSTRING FOUND AT POSITION: {context['position']}")
    context["matches"] += 1

def detector(input_string: str) -> int:
    initial_context: Context = {
        "count": 0,
        "position": 0,
        "matches": 0,
    }
    
    m = Machine(initial_context)
    m.add_state("Q0", is_start_state=True)
    m.add_state("Q1")
    
    # Q0 -> Q1 on 'A', saving the start position
    m.add_transition("A", "Q0", "Q1", lambda context: dset(context))
    # Catch-all transition: Q0 -> Q0 for any input other than 'A'
    m.add_transition(TransitionInputEnum.MATCH_REST, "Q0", "Q0", lambda context: dinc(context))
    
    # Q1 -> Q0 on 'B', printing match information
    m.add_transition("B", "Q1", "Q0", lambda context: dprn(context))
    # Catch-all transition: Q1 -> Q0 for any input other than 'B'
    m.add_transition(TransitionInputEnum.MATCH_REST, "Q1", "Q0", lambda context: dinc(context))
    
    for c in input_string:
        m.process_input(c)
        
    return initial_context["matches"]

API Reference

Core Abstractions

Class/Type Description
InputProtocol A typing protocol requiring __eq__ and __hash__. Any hashable, equatable Python object can serve as machine input.
TransitionInputEnum Enum containing special transition inputs (e.g., MATCH_REST).
State Represents a state node in the state machine.
Transition Represents an edge between states triggered by a transition input.
Machine The core finite state machine runner.

API Details

InputProtocol

class InputProtocol(Protocol):
    def __eq__(self, __o: Self) -> bool: ...
    def __hash__(self) -> int: ...

Any custom object used as an input to Machine.process_input must implement this protocol (or be natively hashable and equatable, e.g. strings, integers, frozen dataclasses).

TransitionInputEnum

  • MATCH_REST: Activates if no matching transition input is found for the current state. Useful for defining default fallback transitions.

State

  • get_name() -> str: Returns the state's name.
  • __str__() -> str: Returns [State <state_name>].

Transition

  • execute_output(context): Executes the output callback if it was supplied.
  • get_destination_name_hash() -> int: Returns the hash of the destination state name.
  • __str__() -> str: Returns [Transition (<origin>, <destination>, <input>)].

Machine

  • __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 or TransitionInputEnum.
    • 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 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. To run the test suite, navigate to the project directory and execute:

PYTHONPATH=src python -m unittest discover -s tests

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

snanosm-1.0.0.tar.gz (7.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

snanosm-1.0.0-py3-none-any.whl (6.7 kB view details)

Uploaded Python 3

File details

Details for the file snanosm-1.0.0.tar.gz.

File metadata

  • Download URL: snanosm-1.0.0.tar.gz
  • Upload date:
  • Size: 7.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.0

File hashes

Hashes for snanosm-1.0.0.tar.gz
Algorithm Hash digest
SHA256 8355f06b80db71a2d8d733e2694ede80c6913498be8f73d8582127e37e2096b1
MD5 f4c358f44a585acead7ab050b94146e9
BLAKE2b-256 e6138b1966ab6c3af5b9912ce7d6c3dd53b811d1785efb6b2e59dbddab9fb5ae

See more details on using hashes here.

File details

Details for the file snanosm-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: snanosm-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 6.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.0

File hashes

Hashes for snanosm-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ded75f6ea76d3b97257725b0ecf8f3b4413d5b08c9c3d6672bde89c2ea51f87d
MD5 70c56092b32be047f3674b67c28e2a3c
BLAKE2b-256 ccb38b1cdd79322b1967256c179d7b28faadff67e6a48cd80054e4cc543f7f3f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page