tinyfsm 0.0.1__tar.gz → 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tinyfsm-0.0.1 → tinyfsm-0.0.2}/PKG-INFO +34 -35
- tinyfsm-0.0.2/README.md +80 -0
- {tinyfsm-0.0.1 → tinyfsm-0.0.2}/pyproject.toml +1 -1
- tinyfsm-0.0.2/tinyfsm/__init__.py +1 -0
- {tinyfsm-0.0.1 → tinyfsm-0.0.2}/tinyfsm/exc.py +16 -16
- {tinyfsm-0.0.1 → tinyfsm-0.0.2}/tinyfsm/interface.py +19 -22
- {tinyfsm-0.0.1 → tinyfsm-0.0.2}/tinyfsm/runner.py +23 -25
- tinyfsm-0.0.1/README.md +0 -83
- tinyfsm-0.0.1/tinyfsm/__init__.py +0 -1
- {tinyfsm-0.0.1 → tinyfsm-0.0.2}/LICENSE.txt +0 -0
- {tinyfsm-0.0.1 → tinyfsm-0.0.2}/tinyfsm/_export_list.py +0 -0
- {tinyfsm-0.0.1 → tinyfsm-0.0.2}/tinyfsm/api.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tinyfsm
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: A tiny and minimal finite state machine for Python.
|
|
5
5
|
License: MIT
|
|
6
|
+
License-File: LICENSE.txt
|
|
6
7
|
Author: Maciej Wiatrzyk
|
|
7
8
|
Author-email: maciej.wiatrzyk@gmail.com
|
|
8
9
|
Requires-Python: >=3.9,<4
|
|
@@ -13,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
18
|
Description-Content-Type: text/markdown
|
|
17
19
|
|
|
18
20
|
# TinyFSM
|
|
@@ -38,57 +40,42 @@ $ pip install tinyfsm
|
|
|
38
40
|
|
|
39
41
|
## Quickstart
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
Here's a simple text tokenizer written using **TinyFSM** library that splits
|
|
44
|
+
text composed of alphanumeric characters into groups of words and numbers and
|
|
45
|
+
returns list of ``(token_name, token_data)`` tuples:
|
|
44
46
|
|
|
45
47
|
```python
|
|
46
|
-
from tinyfsm.api import Traversal, StateMachineRunner,
|
|
48
|
+
from tinyfsm.api import Traversal, StateMachineRunner, InputRejectedError
|
|
47
49
|
|
|
48
|
-
# Declaration of the state machine as list of Traversal objects.
|
|
49
|
-
# Each object contains source state, destination state, and a function that
|
|
50
|
-
# checks if current input (or event) should trigger state traversal.
|
|
51
50
|
definition = [
|
|
52
|
-
Traversal[str]("initial", "word",
|
|
53
|
-
Traversal[str]("initial", "
|
|
54
|
-
Traversal[str]("
|
|
55
|
-
Traversal[str]("word", "
|
|
56
|
-
Traversal[str]("word", "
|
|
57
|
-
Traversal[str]("
|
|
58
|
-
Traversal[str]("
|
|
59
|
-
Traversal[str]("
|
|
51
|
+
Traversal[str]("initial", "word", str.isalpha),
|
|
52
|
+
Traversal[str]("initial", "number", str.isdigit),
|
|
53
|
+
Traversal[str]("initial", "final", lambda input: input == ""),
|
|
54
|
+
Traversal[str]("word", "word", str.isalpha),
|
|
55
|
+
Traversal[str]("word", "number", str.isdigit),
|
|
56
|
+
Traversal[str]("word", "final", lambda input: input == ""),
|
|
57
|
+
Traversal[str]("number", "number", str.isdigit),
|
|
58
|
+
Traversal[str]("number", "word", str.isalpha),
|
|
59
|
+
Traversal[str]("number", "final", lambda input: input == ""),
|
|
60
60
|
]
|
|
61
61
|
|
|
62
62
|
class Listener:
|
|
63
|
-
"""Event listener for the state machine.
|
|
64
|
-
|
|
65
|
-
It listens for events like state change, or input value dispatching end.
|
|
66
|
-
Implementations (like this one) can use these methods to buffer inputs and
|
|
67
|
-
emit tokens.
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
63
|
def __init__(self, output: list[tuple[str, str]]):
|
|
71
64
|
self._output = output
|
|
72
65
|
self._buffer = ""
|
|
73
66
|
|
|
74
|
-
def on_state_change(self,
|
|
67
|
+
def on_state_change(self, input: str, prev_state: str, current_state: str):
|
|
75
68
|
if prev_state != current_state:
|
|
76
69
|
if prev_state == "word":
|
|
77
70
|
self._output.append(("WORD", self._buffer))
|
|
78
|
-
if prev_state == "
|
|
79
|
-
self._output.append(("
|
|
71
|
+
if prev_state == "number":
|
|
72
|
+
self._output.append(("NUMBER", self._buffer))
|
|
80
73
|
self._buffer = ""
|
|
81
74
|
|
|
82
|
-
def on_dispatch_done(self,
|
|
83
|
-
self._buffer +=
|
|
75
|
+
def on_dispatch_done(self, input: str, current_state: str):
|
|
76
|
+
self._buffer += input
|
|
84
77
|
|
|
85
78
|
def tokenize(text: str) -> list[tuple[str, str]]:
|
|
86
|
-
"""Tokenization function.
|
|
87
|
-
|
|
88
|
-
This is just an example, but in general a some sort of function gluing all
|
|
89
|
-
parts together is recommended. Here the function parses given text and
|
|
90
|
-
outputs list of tokens parsed from it.
|
|
91
|
-
"""
|
|
92
79
|
out = []
|
|
93
80
|
listener = Listener(out)
|
|
94
81
|
runner = StateMachineRunner(definition, listener)
|
|
@@ -99,3 +86,15 @@ def tokenize(text: str) -> list[tuple[str, str]]:
|
|
|
99
86
|
return out
|
|
100
87
|
```
|
|
101
88
|
|
|
89
|
+
And the tokenizer from above will split any text composed of just words and
|
|
90
|
+
numbers into something like this:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
out = tokenize("foo123bar456")
|
|
94
|
+
print(out) # Would print: [("WORD", "foo"), ("NUMBER", "123"), ("WORD", "bar"), ("NUMBER", "456")]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
# License
|
|
98
|
+
|
|
99
|
+
This project is released under the terms of the MIT license.
|
|
100
|
+
|
tinyfsm-0.0.2/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# TinyFSM
|
|
2
|
+
|
|
3
|
+
A tiny and minimal finite state machine for Python.
|
|
4
|
+
|
|
5
|
+
## About
|
|
6
|
+
|
|
7
|
+
**TinyFSM** is a fast and minimal finite state machine engine for Python. It
|
|
8
|
+
runs the finite state defined as list of traversals, where each traversal is
|
|
9
|
+
given by current and next state name and a traversal function that is evaluated
|
|
10
|
+
on currently processed event. Once traversal function returns true, the current
|
|
11
|
+
state is changed to the next state according to matched traversal.
|
|
12
|
+
|
|
13
|
+
This library can be used as a backbone for creating larger scale FSM-based
|
|
14
|
+
tokenizers and parsers.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```shell
|
|
19
|
+
$ pip install tinyfsm
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
Here's a simple text tokenizer written using **TinyFSM** library that splits
|
|
25
|
+
text composed of alphanumeric characters into groups of words and numbers and
|
|
26
|
+
returns list of ``(token_name, token_data)`` tuples:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from tinyfsm.api import Traversal, StateMachineRunner, InputRejectedError
|
|
30
|
+
|
|
31
|
+
definition = [
|
|
32
|
+
Traversal[str]("initial", "word", str.isalpha),
|
|
33
|
+
Traversal[str]("initial", "number", str.isdigit),
|
|
34
|
+
Traversal[str]("initial", "final", lambda input: input == ""),
|
|
35
|
+
Traversal[str]("word", "word", str.isalpha),
|
|
36
|
+
Traversal[str]("word", "number", str.isdigit),
|
|
37
|
+
Traversal[str]("word", "final", lambda input: input == ""),
|
|
38
|
+
Traversal[str]("number", "number", str.isdigit),
|
|
39
|
+
Traversal[str]("number", "word", str.isalpha),
|
|
40
|
+
Traversal[str]("number", "final", lambda input: input == ""),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
class Listener:
|
|
44
|
+
def __init__(self, output: list[tuple[str, str]]):
|
|
45
|
+
self._output = output
|
|
46
|
+
self._buffer = ""
|
|
47
|
+
|
|
48
|
+
def on_state_change(self, input: str, prev_state: str, current_state: str):
|
|
49
|
+
if prev_state != current_state:
|
|
50
|
+
if prev_state == "word":
|
|
51
|
+
self._output.append(("WORD", self._buffer))
|
|
52
|
+
if prev_state == "number":
|
|
53
|
+
self._output.append(("NUMBER", self._buffer))
|
|
54
|
+
self._buffer = ""
|
|
55
|
+
|
|
56
|
+
def on_dispatch_done(self, input: str, current_state: str):
|
|
57
|
+
self._buffer += input
|
|
58
|
+
|
|
59
|
+
def tokenize(text: str) -> list[tuple[str, str]]:
|
|
60
|
+
out = []
|
|
61
|
+
listener = Listener(out)
|
|
62
|
+
runner = StateMachineRunner(definition, listener)
|
|
63
|
+
with runner:
|
|
64
|
+
for char in text:
|
|
65
|
+
runner.dispatch(char)
|
|
66
|
+
runner.dispatch("")
|
|
67
|
+
return out
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
And the tokenizer from above will split any text composed of just words and
|
|
71
|
+
numbers into something like this:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
out = tokenize("foo123bar456")
|
|
75
|
+
print(out) # Would print: [("WORD", "foo"), ("NUMBER", "123"), ("WORD", "bar"), ("NUMBER", "456")]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
# License
|
|
79
|
+
|
|
80
|
+
This project is released under the terms of the MIT license.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.2"
|
|
@@ -11,38 +11,38 @@ class TinyFSMError(Exception):
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@export
|
|
14
|
-
class
|
|
15
|
-
"""Raised when
|
|
14
|
+
class InputRejectedError(TinyFSMError):
|
|
15
|
+
"""Raised when input was rejected by the state machine.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Input is rejected only if there was no matching traversal found or if all
|
|
18
|
+
traversal functions returned ``False`` for that input.
|
|
18
19
|
"""
|
|
19
20
|
|
|
20
|
-
#: The rejected
|
|
21
|
-
|
|
21
|
+
#: The rejected input object.
|
|
22
|
+
input: Any
|
|
22
23
|
|
|
23
24
|
#: The current state name.
|
|
24
25
|
current_state: str
|
|
25
26
|
|
|
26
|
-
def __init__(self,
|
|
27
|
+
def __init__(self, input: Any, current_state: str):
|
|
27
28
|
super().__init__()
|
|
28
|
-
self.
|
|
29
|
+
self.input = input
|
|
29
30
|
self.current_state = current_state
|
|
30
31
|
|
|
31
32
|
def __str__(self) -> str:
|
|
32
|
-
return f"
|
|
33
|
+
return f"input {self.input!r} was rejected; no traversal found for current state {self.current_state!r}"
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
@export
|
|
36
37
|
class FinalStateNotReached(TinyFSMError):
|
|
37
38
|
"""Raised when closing state machine if final state was not reached.
|
|
38
39
|
|
|
39
|
-
This means that either the input sequence
|
|
40
|
-
|
|
41
|
-
unreachable.
|
|
40
|
+
This means that either the input sequence is incomplete, or that the state
|
|
41
|
+
machine definition is malformed and final state is unreachable.
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
|
-
#: The last dispatched
|
|
45
|
-
|
|
44
|
+
#: The last dispatched input.
|
|
45
|
+
last_input: Any
|
|
46
46
|
|
|
47
47
|
#: The name of the final state.
|
|
48
48
|
final_state: str
|
|
@@ -50,11 +50,11 @@ class FinalStateNotReached(TinyFSMError):
|
|
|
50
50
|
#: The name of the current state.
|
|
51
51
|
current_state: str
|
|
52
52
|
|
|
53
|
-
def __init__(self,
|
|
53
|
+
def __init__(self, last_input: Any, final_state: str, current_state: str):
|
|
54
54
|
super().__init__()
|
|
55
|
-
self.
|
|
55
|
+
self.last_input = last_input
|
|
56
56
|
self.final_state = final_state
|
|
57
57
|
self.current_state = current_state
|
|
58
58
|
|
|
59
59
|
def __str__(self) -> str:
|
|
60
|
-
return f"final state {self.final_state!r} was not reached; current state is {self.current_state!r}, last
|
|
60
|
+
return f"final state {self.final_state!r} was not reached; current state is {self.current_state!r}, last input was {self.last_input!r}"
|
|
@@ -21,19 +21,16 @@ class Traversal(Generic[T]):
|
|
|
21
21
|
state.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
-
#: Source
|
|
25
|
-
|
|
24
|
+
#: Source state name.
|
|
25
|
+
source_state: str
|
|
26
26
|
|
|
27
|
-
#:
|
|
28
|
-
|
|
27
|
+
#: Target state name.
|
|
28
|
+
target_state: str
|
|
29
29
|
|
|
30
|
-
#:
|
|
31
|
-
#:
|
|
32
|
-
#:
|
|
33
|
-
|
|
34
|
-
#: performed. If the current state is not the source state, then the
|
|
35
|
-
#: function will not be called.
|
|
36
|
-
cond: Callable[[T], bool]
|
|
30
|
+
#: The function to be called for each input when current state is
|
|
31
|
+
#: :attr:`source_state`; if the function returns ``True``, current state is
|
|
32
|
+
#: changed to :attr:`target_state`.
|
|
33
|
+
traverse_func: Callable[[T], bool]
|
|
37
34
|
|
|
38
35
|
|
|
39
36
|
@export
|
|
@@ -45,15 +42,15 @@ class StateMachineListener(Protocol, Generic[Tc]):
|
|
|
45
42
|
use this to add events to internal buffers, queues etc.
|
|
46
43
|
"""
|
|
47
44
|
|
|
48
|
-
def on_state_change(self,
|
|
49
|
-
"""Triggered when *
|
|
45
|
+
def on_state_change(self, input: Tc, prev_state: str, current_state: str):
|
|
46
|
+
"""Triggered when *input* caused state traversal from *prev_state* to
|
|
50
47
|
*current_state*.
|
|
51
48
|
|
|
52
|
-
This will only be called if one of
|
|
53
|
-
returned ``True``.
|
|
49
|
+
This will only be called if one of traverse functions (see
|
|
50
|
+
:attr:`Traversal.traverse_func`) returned ``True``.
|
|
54
51
|
|
|
55
|
-
:param
|
|
56
|
-
The
|
|
52
|
+
:param input:
|
|
53
|
+
The input that caused traversal.
|
|
57
54
|
|
|
58
55
|
:param prev_state:
|
|
59
56
|
The name of a previous state.
|
|
@@ -63,14 +60,14 @@ class StateMachineListener(Protocol, Generic[Tc]):
|
|
|
63
60
|
"""
|
|
64
61
|
...
|
|
65
62
|
|
|
66
|
-
def on_dispatch_done(self,
|
|
67
|
-
"""Triggered when
|
|
63
|
+
def on_dispatch_done(self, input: Tc, current_state: str):
|
|
64
|
+
"""Triggered when input dispatching ends.
|
|
68
65
|
|
|
69
|
-
This method will be called for every successfully accepted
|
|
66
|
+
This method will be called for every successfully accepted input even
|
|
70
67
|
if there was no state change.
|
|
71
68
|
|
|
72
|
-
:param
|
|
73
|
-
The
|
|
69
|
+
:param input:
|
|
70
|
+
The input object.
|
|
74
71
|
|
|
75
72
|
:param current_state:
|
|
76
73
|
The name of the state the state machine was left in once *event*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Generic, Optional, Sequence, TypeVar
|
|
2
2
|
|
|
3
3
|
from . import _export_list
|
|
4
|
-
from .exc import
|
|
4
|
+
from .exc import InputRejectedError, FinalStateNotReached
|
|
5
5
|
from .interface import StateMachineListener, Traversal
|
|
6
6
|
|
|
7
7
|
__all__ = export = _export_list.ExportList() # type: ignore
|
|
@@ -66,11 +66,11 @@ class StateMachineRunner(Generic[T]):
|
|
|
66
66
|
self.__traversal_map: dict[str, list[Traversal]] = {}
|
|
67
67
|
has_initial = has_final = False
|
|
68
68
|
for traversal in definition:
|
|
69
|
-
if traversal.
|
|
69
|
+
if traversal.source_state == initial_state:
|
|
70
70
|
has_initial = True
|
|
71
|
-
if traversal.
|
|
71
|
+
if traversal.target_state == final_state:
|
|
72
72
|
has_final = True
|
|
73
|
-
self.__traversal_map.setdefault(traversal.
|
|
73
|
+
self.__traversal_map.setdefault(traversal.source_state, []).append(traversal)
|
|
74
74
|
if not has_initial:
|
|
75
75
|
raise TypeError(f"no initial state found: {initial_state}")
|
|
76
76
|
if not has_final:
|
|
@@ -79,7 +79,7 @@ class StateMachineRunner(Generic[T]):
|
|
|
79
79
|
self.__initial_state = initial_state
|
|
80
80
|
self.__final_state = final_state
|
|
81
81
|
self.__current_state = self.__initial_state
|
|
82
|
-
self.
|
|
82
|
+
self.__last_input: Optional[T] = None
|
|
83
83
|
|
|
84
84
|
def __enter__(self) -> "StateMachineRunner":
|
|
85
85
|
return self
|
|
@@ -89,42 +89,40 @@ class StateMachineRunner(Generic[T]):
|
|
|
89
89
|
return None
|
|
90
90
|
return self.close()
|
|
91
91
|
|
|
92
|
-
def dispatch(self,
|
|
93
|
-
"""Dispatch
|
|
92
|
+
def dispatch(self, input: T):
|
|
93
|
+
"""Dispatch input to the state machine.
|
|
94
94
|
|
|
95
|
-
This should be called for each
|
|
96
|
-
accept the
|
|
97
|
-
|
|
98
|
-
exception.
|
|
95
|
+
This should be called for each input, and state machine can either
|
|
96
|
+
accept the input, and maybe traverse to a different state, or reject it
|
|
97
|
+
by raising :exc:`InputRejectedError` exception.
|
|
99
98
|
|
|
100
|
-
:param
|
|
101
|
-
The
|
|
99
|
+
:param input:
|
|
100
|
+
The current input to dispatch.
|
|
102
101
|
"""
|
|
103
|
-
self.
|
|
102
|
+
self.__last_input = input
|
|
104
103
|
current_state_traversals = self.__traversal_map.get(self.__current_state)
|
|
105
104
|
if current_state_traversals is None:
|
|
106
|
-
raise
|
|
107
|
-
print(current_state_traversals)
|
|
105
|
+
raise InputRejectedError(input, self.__current_state)
|
|
108
106
|
for traversal in current_state_traversals:
|
|
109
|
-
if traversal.
|
|
110
|
-
next_state = traversal.
|
|
111
|
-
self.__listener.on_state_change(
|
|
107
|
+
if traversal.traverse_func(input):
|
|
108
|
+
next_state = traversal.target_state
|
|
109
|
+
self.__listener.on_state_change(input, self.__current_state, next_state)
|
|
112
110
|
self.__current_state = next_state
|
|
113
111
|
break
|
|
114
112
|
else:
|
|
115
|
-
raise
|
|
116
|
-
self.__listener.on_dispatch_done(
|
|
113
|
+
raise InputRejectedError(input, self.__current_state)
|
|
114
|
+
self.__listener.on_dispatch_done(input, self.__current_state)
|
|
117
115
|
|
|
118
116
|
def close(self):
|
|
119
117
|
"""Close this state machine.
|
|
120
118
|
|
|
121
|
-
This method should be called after
|
|
122
|
-
to
|
|
123
|
-
reached, the method silently returns. Otherwise it raises
|
|
119
|
+
This method should be called shortly after all inputs are dispatched.
|
|
120
|
+
Its role is to ensure that the final state was reached; if the final
|
|
121
|
+
state was reached, the method silently returns. Otherwise it raises
|
|
124
122
|
:exc:`tinyfsm.exc.FinalStateNotReached` error.
|
|
125
123
|
"""
|
|
126
124
|
if not self.is_final():
|
|
127
|
-
raise FinalStateNotReached(self.
|
|
125
|
+
raise FinalStateNotReached(self.__last_input, self.__final_state, self.__current_state)
|
|
128
126
|
|
|
129
127
|
def is_final(self) -> bool:
|
|
130
128
|
"""Check if the final state is reached."""
|
tinyfsm-0.0.1/README.md
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# TinyFSM
|
|
2
|
-
|
|
3
|
-
A tiny and minimal finite state machine for Python.
|
|
4
|
-
|
|
5
|
-
## About
|
|
6
|
-
|
|
7
|
-
**TinyFSM** is a fast and minimal finite state machine engine for Python. It
|
|
8
|
-
runs the finite state defined as list of traversals, where each traversal is
|
|
9
|
-
given by current and next state name and a traversal function that is evaluated
|
|
10
|
-
on currently processed event. Once traversal function returns true, the current
|
|
11
|
-
state is changed to the next state according to matched traversal.
|
|
12
|
-
|
|
13
|
-
This library can be used as a backbone for creating larger scale FSM-based
|
|
14
|
-
tokenizers and parsers.
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
17
|
-
|
|
18
|
-
```shell
|
|
19
|
-
$ pip install tinyfsm
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Quickstart
|
|
23
|
-
|
|
24
|
-
Below you'll find a simple text parser that split text into WORD and SPACE
|
|
25
|
-
tokens, where WORD is a consecutive collection of alpha characters, and SPACE
|
|
26
|
-
is consecutive collection of space characters:
|
|
27
|
-
|
|
28
|
-
```python
|
|
29
|
-
from tinyfsm.api import Traversal, StateMachineRunner, EventRejectedError
|
|
30
|
-
|
|
31
|
-
# Declaration of the state machine as list of Traversal objects.
|
|
32
|
-
# Each object contains source state, destination state, and a function that
|
|
33
|
-
# checks if current input (or event) should trigger state traversal.
|
|
34
|
-
definition = [
|
|
35
|
-
Traversal[str]("initial", "word", lambda event: event.isalpha()),
|
|
36
|
-
Traversal[str]("initial", "space", lambda event: event == " "),
|
|
37
|
-
Traversal[str]("word", "word", lambda event: event.isalpha()),
|
|
38
|
-
Traversal[str]("word", "space", lambda event: event == " "),
|
|
39
|
-
Traversal[str]("word", "final", lambda event: event == ""),
|
|
40
|
-
Traversal[str]("space", "space", lambda event: event == " "),
|
|
41
|
-
Traversal[str]("space", "word", lambda event: event.isalpha()),
|
|
42
|
-
Traversal[str]("space", "final", lambda event: event == ""),
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
class Listener:
|
|
46
|
-
"""Event listener for the state machine.
|
|
47
|
-
|
|
48
|
-
It listens for events like state change, or input value dispatching end.
|
|
49
|
-
Implementations (like this one) can use these methods to buffer inputs and
|
|
50
|
-
emit tokens.
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
def __init__(self, output: list[tuple[str, str]]):
|
|
54
|
-
self._output = output
|
|
55
|
-
self._buffer = ""
|
|
56
|
-
|
|
57
|
-
def on_state_change(self, event: str, prev_state: str, current_state: str):
|
|
58
|
-
if prev_state != current_state:
|
|
59
|
-
if prev_state == "word":
|
|
60
|
-
self._output.append(("WORD", self._buffer))
|
|
61
|
-
if prev_state == "space":
|
|
62
|
-
self._output.append(("SPACE", self._buffer))
|
|
63
|
-
self._buffer = ""
|
|
64
|
-
|
|
65
|
-
def on_dispatch_done(self, event: str, current_state: str):
|
|
66
|
-
self._buffer += event
|
|
67
|
-
|
|
68
|
-
def tokenize(text: str) -> list[tuple[str, str]]:
|
|
69
|
-
"""Tokenization function.
|
|
70
|
-
|
|
71
|
-
This is just an example, but in general a some sort of function gluing all
|
|
72
|
-
parts together is recommended. Here the function parses given text and
|
|
73
|
-
outputs list of tokens parsed from it.
|
|
74
|
-
"""
|
|
75
|
-
out = []
|
|
76
|
-
listener = Listener(out)
|
|
77
|
-
runner = StateMachineRunner(definition, listener)
|
|
78
|
-
with runner:
|
|
79
|
-
for char in text:
|
|
80
|
-
runner.dispatch(char)
|
|
81
|
-
runner.dispatch("")
|
|
82
|
-
return out
|
|
83
|
-
```
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|