syncraft 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl
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.
Potentially problematic release.
This version of syncraft might be problematic. Click here for more details.
- syncraft/__init__.py +30 -9
- syncraft/algebra.py +143 -214
- syncraft/ast.py +62 -7
- syncraft/cache.py +113 -0
- syncraft/constraint.py +184 -134
- syncraft/dev.py +9 -0
- syncraft/finder.py +17 -12
- syncraft/generator.py +80 -78
- syncraft/lexer.py +131 -0
- syncraft/parser.py +75 -224
- syncraft/syntax.py +187 -100
- syncraft/utils.py +214 -0
- syncraft/walker.py +147 -0
- syncraft-0.2.7.dist-info/METADATA +56 -0
- syncraft-0.2.7.dist-info/RECORD +20 -0
- syncraft/diagnostic.py +0 -70
- syncraft-0.2.5.dist-info/METADATA +0 -113
- syncraft-0.2.5.dist-info/RECORD +0 -16
- {syncraft-0.2.5.dist-info → syncraft-0.2.7.dist-info}/WHEEL +0 -0
- {syncraft-0.2.5.dist-info → syncraft-0.2.7.dist-info}/licenses/LICENSE +0 -0
- {syncraft-0.2.5.dist-info → syncraft-0.2.7.dist-info}/top_level.txt +0 -0
syncraft/__init__.py
CHANGED
|
@@ -1,32 +1,49 @@
|
|
|
1
1
|
from .syntax import (
|
|
2
|
+
Description,
|
|
2
3
|
Syntax,
|
|
4
|
+
regex,
|
|
5
|
+
literal,
|
|
3
6
|
choice,
|
|
4
7
|
lazy,
|
|
5
8
|
success,
|
|
6
9
|
fail,
|
|
10
|
+
when,
|
|
7
11
|
run,
|
|
8
12
|
)
|
|
13
|
+
from .walker import (
|
|
14
|
+
walk,
|
|
15
|
+
)
|
|
16
|
+
from .algebra import (
|
|
17
|
+
Algebra,
|
|
18
|
+
Error,
|
|
19
|
+
Left,
|
|
20
|
+
Right,
|
|
21
|
+
Either,
|
|
22
|
+
)
|
|
9
23
|
from .parser import (
|
|
24
|
+
Parser,
|
|
10
25
|
parse,
|
|
11
26
|
sqlglot,
|
|
12
27
|
token,
|
|
13
28
|
identifier,
|
|
14
29
|
variable,
|
|
15
|
-
literal,
|
|
16
30
|
number,
|
|
17
31
|
string,
|
|
18
|
-
regex,
|
|
19
|
-
until,
|
|
20
32
|
)
|
|
21
33
|
from .generator import (
|
|
34
|
+
Generator,
|
|
22
35
|
generate,
|
|
36
|
+
generate_with,
|
|
37
|
+
validate,
|
|
23
38
|
)
|
|
24
39
|
from .finder import (
|
|
40
|
+
Finder,
|
|
25
41
|
find,
|
|
26
42
|
matches,
|
|
27
43
|
anything,
|
|
28
44
|
)
|
|
29
45
|
from .constraint import (
|
|
46
|
+
FrozenDict,
|
|
30
47
|
Constraint,
|
|
31
48
|
Quantifier,
|
|
32
49
|
forall,
|
|
@@ -34,6 +51,8 @@ from .constraint import (
|
|
|
34
51
|
)
|
|
35
52
|
from .ast import (
|
|
36
53
|
AST,
|
|
54
|
+
Bimap,
|
|
55
|
+
Biarrow,
|
|
37
56
|
Token,
|
|
38
57
|
Then,
|
|
39
58
|
ThenKind,
|
|
@@ -45,15 +64,17 @@ from .ast import (
|
|
|
45
64
|
)
|
|
46
65
|
|
|
47
66
|
__all__ = [
|
|
67
|
+
# algebra
|
|
68
|
+
"Algebra", "Error", "Left", "Right", "Either",
|
|
48
69
|
# syntax & core
|
|
49
|
-
"Syntax", "choice", "lazy", "success", "fail", "run",
|
|
70
|
+
"Syntax", "choice", "lazy", "success", "fail", "run", "Description", "when",
|
|
50
71
|
# parsing/generation helpers
|
|
51
|
-
"parse", "sqlglot", "token", "identifier", "variable", "literal", "number", "string", "regex", "
|
|
52
|
-
"generate",
|
|
72
|
+
"parse", "sqlglot", "token", "identifier", "variable", "literal", "number", "string", "regex", "walk",
|
|
73
|
+
"generate", "generate_with", "validate", "Parser", "Generator",
|
|
53
74
|
# finder
|
|
54
|
-
"find", "matches", "anything",
|
|
75
|
+
"find", "matches", "anything", "Finder",
|
|
55
76
|
# constraints
|
|
56
|
-
"Constraint", "Quantifier", "forall", "exists",
|
|
77
|
+
"Constraint", "Quantifier", "forall", "exists", "FrozenDict",
|
|
57
78
|
# ast
|
|
58
|
-
"AST", "Token", "Then", "ThenKind", "Choice", "ChoiceKind", "Many", "Marked", "Collect",
|
|
79
|
+
"AST", "Token", "Then", "ThenKind", "Choice", "ChoiceKind", "Many", "Marked", "Collect", "Bimap", "Biarrow"
|
|
59
80
|
]
|
syncraft/algebra.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from typing import (
|
|
3
3
|
Optional, List, Any, TypeVar, Generic, Callable, Tuple, cast,
|
|
4
|
-
Dict, Type,
|
|
4
|
+
Dict, Type, Hashable, Generator
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
-
import traceback
|
|
8
7
|
from dataclasses import dataclass, replace
|
|
9
|
-
from
|
|
10
|
-
from syncraft.
|
|
8
|
+
from syncraft.ast import ThenKind, Then, Choice, Many, ChoiceKind, shallow_dict, SyncraftError
|
|
9
|
+
from syncraft.cache import Cache
|
|
11
10
|
from syncraft.constraint import Bindable
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
from rich import print
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
S = TypeVar('S', bound=Bindable)
|
|
@@ -19,7 +17,6 @@ A = TypeVar('A') # Result type
|
|
|
19
17
|
B = TypeVar('B') # Mapped result type
|
|
20
18
|
|
|
21
19
|
|
|
22
|
-
InProgress = object() # Marker for in-progress state, used to prevent re-entrance in recursive calls
|
|
23
20
|
L = TypeVar('L') # Left type for combined results
|
|
24
21
|
R = TypeVar('R') # Right type for combined results
|
|
25
22
|
|
|
@@ -38,7 +35,9 @@ class Right(Either[L, R]):
|
|
|
38
35
|
value: R
|
|
39
36
|
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class Incomplete(Generic[S]):
|
|
40
|
+
state: S
|
|
42
41
|
|
|
43
42
|
@dataclass(frozen=True)
|
|
44
43
|
class Error:
|
|
@@ -47,7 +46,7 @@ class Error:
|
|
|
47
46
|
error: Optional[Any] = None
|
|
48
47
|
state: Optional[Any] = None
|
|
49
48
|
committed: bool = False
|
|
50
|
-
|
|
49
|
+
fatal: bool = False
|
|
51
50
|
previous: Optional[Error] = None
|
|
52
51
|
|
|
53
52
|
def attach( self,
|
|
@@ -76,147 +75,71 @@ class Error:
|
|
|
76
75
|
@dataclass(frozen=True)
|
|
77
76
|
class Algebra(Generic[A, S]):
|
|
78
77
|
######################################################## shared among all subclasses ########################################################
|
|
79
|
-
run_f: Callable[[S, bool], Either[Any, Tuple[A, S]]]
|
|
78
|
+
run_f: Callable[[S, bool], Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]]
|
|
80
79
|
name: Hashable
|
|
81
|
-
|
|
80
|
+
cache: Cache[ S, Either[Any, Tuple[A, S]] ]
|
|
82
81
|
|
|
83
82
|
@classmethod
|
|
84
|
-
def state(cls,
|
|
83
|
+
def state(cls, **kwargs:Any)->Optional[S]:
|
|
85
84
|
return None
|
|
86
85
|
|
|
87
86
|
def named(self, name: Hashable) -> 'Algebra[A, S]':
|
|
88
87
|
return replace(self, name=name)
|
|
89
|
-
|
|
90
|
-
def __post_init__(self)-> None:
|
|
91
|
-
self._cache.setdefault(self.run_f, dict())
|
|
92
88
|
|
|
93
|
-
def __call__(self, input: S, use_cache: bool) -> Either[Any, Tuple[A, S]]:
|
|
89
|
+
def __call__(self, input: S, use_cache: bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]:
|
|
94
90
|
return self.run(input, use_cache=use_cache)
|
|
95
91
|
|
|
96
|
-
def run(self, input: S, use_cache: bool) -> Either[Any, Tuple[A, S]]:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
This is the core evaluation entry point used by all combinators. It
|
|
100
|
-
supports per-"parser" memoization and protects against infinite
|
|
101
|
-
recursion by detecting left-recursive re-entrance.
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
input: The initial state to run against. Must be hashable as it's
|
|
105
|
-
used as a cache key.
|
|
106
|
-
use_cache: When True, memoize results for ``(self.run_f, input)``
|
|
107
|
-
so repeated calls short-circuit. When False, the cache entry is
|
|
108
|
-
cleared after the run to effectively disable the cache.
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
Either[Error, Tuple[A, S]]: On success, ``Right((value, next_state))``.
|
|
112
|
-
On failure, ``Left(Error)``. Errors produced downstream are
|
|
113
|
-
automatically enriched with ``this=self`` and ``state=input`` to
|
|
114
|
-
preserve context. If an exception escapes the user code, it's
|
|
115
|
-
captured and returned as ``Left(Error)`` with a traceback in
|
|
116
|
-
``stack``.
|
|
117
|
-
|
|
118
|
-
Notes:
|
|
119
|
-
- Left recursion: if a re-entrant call is observed on the same
|
|
120
|
-
``input`` while an evaluation is in progress, a ``Left(Error)``
|
|
121
|
-
is returned indicating left-recursion was detected.
|
|
122
|
-
- Memoization scope: results are cached per ``run_f`` (the concrete
|
|
123
|
-
compiled function of this algebra) and keyed by the input state.
|
|
124
|
-
- Commitment: downstream combinators (e.g. ``cut``) may set the
|
|
125
|
-
``committed`` flag in ``Error``; ``run`` preserves that flag but
|
|
126
|
-
does not set it itself.
|
|
127
|
-
"""
|
|
128
|
-
cache = self._cache[self.run_f]
|
|
129
|
-
assert cache is not None, "Cache should be initialized in __post_init__"
|
|
130
|
-
if input in cache:
|
|
131
|
-
v = cache.get(input, None)
|
|
132
|
-
if v is InProgress:
|
|
133
|
-
return Left(
|
|
134
|
-
Error(
|
|
135
|
-
message="Left-recursion detected in parser",
|
|
136
|
-
this=self,
|
|
137
|
-
state=input
|
|
138
|
-
))
|
|
139
|
-
else:
|
|
140
|
-
return cast(Either[Error, Tuple[A, S]], v)
|
|
141
|
-
try:
|
|
142
|
-
cache[input] = InProgress
|
|
143
|
-
result = self.run_f(input, use_cache)
|
|
144
|
-
cache[input] = result
|
|
145
|
-
if not use_cache:
|
|
146
|
-
cache.pop(input, None) # Clear the cache entry if not using cache
|
|
147
|
-
if isinstance(result, Left):
|
|
148
|
-
if isinstance(result.value, Error):
|
|
149
|
-
result = Left(result.value.attach(this=self, state=input))
|
|
150
|
-
except Exception as e:
|
|
151
|
-
cache.pop(input, None) # Clear the cache entry on exception
|
|
152
|
-
# traceback.print_exc()
|
|
153
|
-
# print(f"Exception from self.run(S): {e}")
|
|
154
|
-
return Left(
|
|
155
|
-
Error(
|
|
156
|
-
message="Exception from self.run(S): {e}",
|
|
157
|
-
this=self,
|
|
158
|
-
state=input,
|
|
159
|
-
error=e,
|
|
160
|
-
stack=traceback.format_exc()
|
|
161
|
-
))
|
|
162
|
-
return result
|
|
92
|
+
def run(self, input: S, use_cache: bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]:
|
|
93
|
+
return (yield from self.cache.gen(self.run_f, input, use_cache))
|
|
94
|
+
|
|
163
95
|
|
|
164
96
|
def as_(self, typ: Type[B])->B:
|
|
165
97
|
return cast(typ, self) # type: ignore
|
|
166
98
|
|
|
167
99
|
@classmethod
|
|
168
|
-
def lazy(cls,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
thunk
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return thunk().run(input, use_cache)
|
|
182
|
-
return cls(lazy_run, name=cls.__name__ + '.lazy')
|
|
100
|
+
def lazy(cls,
|
|
101
|
+
thunk: Callable[[], Algebra[A, S]],
|
|
102
|
+
*,
|
|
103
|
+
cache: Cache) -> Algebra[A, S]:
|
|
104
|
+
def algebra_lazy_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]:
|
|
105
|
+
alg = thunk()
|
|
106
|
+
# print('--' * 20, "Algebra.lazy.algebra_lazy_run", '--' * 20)
|
|
107
|
+
# print('thunk', thunk, id(thunk))
|
|
108
|
+
# print('input', input, id(input))
|
|
109
|
+
# print('alg', alg, id(alg))
|
|
110
|
+
result = yield from alg.run(input, use_cache)
|
|
111
|
+
return result
|
|
112
|
+
return cls(algebra_lazy_run, name=cls.__name__ + '.lazy', cache=cache)
|
|
183
113
|
|
|
184
114
|
@classmethod
|
|
185
|
-
def fail(cls,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
An algebra producing ``Left(Error(...))`` without consuming input.
|
|
193
|
-
"""
|
|
194
|
-
def fail_run(input: S, use_cache:bool) -> Either[Any, Tuple[Any, S]]:
|
|
115
|
+
def fail(cls,
|
|
116
|
+
error: Any,
|
|
117
|
+
*,
|
|
118
|
+
cache: Cache) -> Algebra[Any, S]:
|
|
119
|
+
def fail_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]:
|
|
120
|
+
yield from ()
|
|
195
121
|
return Left(Error(
|
|
196
122
|
error=error,
|
|
197
123
|
this=cls,
|
|
198
124
|
state=input
|
|
199
125
|
))
|
|
200
|
-
return cls(fail_run, name=cls.__name__ + '.fail')
|
|
126
|
+
return cls(fail_run, name=cls.__name__ + '.fail', cache=cache)
|
|
201
127
|
|
|
202
128
|
@classmethod
|
|
203
|
-
def success(cls,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
value: The constant value to return.
|
|
210
|
-
|
|
211
|
-
Returns:
|
|
212
|
-
``Right((value, input))`` for any input state.
|
|
213
|
-
"""
|
|
214
|
-
def success_run(input: S, use_cache:bool) -> Either[Any, Tuple[Any, S]]:
|
|
129
|
+
def success(cls,
|
|
130
|
+
value: Any,
|
|
131
|
+
*,
|
|
132
|
+
cache: Cache) -> Algebra[Any, S]:
|
|
133
|
+
def success_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]:
|
|
134
|
+
yield from ()
|
|
215
135
|
return Right((value, input))
|
|
216
|
-
return cls(success_run, name=cls.__name__ + '.success')
|
|
136
|
+
return cls(success_run, name=cls.__name__ + '.success', cache=cache)
|
|
217
137
|
|
|
218
138
|
@classmethod
|
|
219
|
-
def factory(cls,
|
|
139
|
+
def factory(cls,
|
|
140
|
+
name: str,
|
|
141
|
+
*args: Any,
|
|
142
|
+
**kwargs: Any) -> Algebra[A, S]:
|
|
220
143
|
"""Call a named class method to construct an algebra.
|
|
221
144
|
|
|
222
145
|
Args:
|
|
@@ -232,10 +155,29 @@ class Algebra(Generic[A, S]):
|
|
|
232
155
|
"""
|
|
233
156
|
method = getattr(cls, name, None)
|
|
234
157
|
if method is None or not callable(method):
|
|
235
|
-
raise
|
|
158
|
+
raise SyncraftError(f"Method {name} is not defined in {cls.__name__}", offending=method, expect='callable')
|
|
236
159
|
return cast(Algebra[A, S], method(*args, **kwargs))
|
|
237
160
|
|
|
161
|
+
def fatal(self) -> Algebra[A, S]:
|
|
162
|
+
"""Commit this branch by marking failures as fatal.
|
|
238
163
|
|
|
164
|
+
Converts downstream errors into fatal errors (``fatal=True``),
|
|
165
|
+
which prevents alternatives from being tried in ``or_else``.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
An algebra that fails fatally on errors produced by this one.
|
|
169
|
+
"""
|
|
170
|
+
def fail_all_error(e: Any) -> Error:
|
|
171
|
+
match e:
|
|
172
|
+
case Error():
|
|
173
|
+
return replace(e, fatal=True)
|
|
174
|
+
case _:
|
|
175
|
+
return Error(
|
|
176
|
+
error=e,
|
|
177
|
+
this=self,
|
|
178
|
+
fatal=True
|
|
179
|
+
)
|
|
180
|
+
return self.map_error(fail_all_error)
|
|
239
181
|
|
|
240
182
|
def cut(self) -> Algebra[A, S]:
|
|
241
183
|
"""Commit this branch by marking failures as committed.
|
|
@@ -266,8 +208,9 @@ class Algebra(Generic[A, S]):
|
|
|
266
208
|
Left[Any, Tuple[A, S]],
|
|
267
209
|
Any
|
|
268
210
|
],
|
|
269
|
-
|
|
270
|
-
|
|
211
|
+
Either[Any, Tuple[B, S]]
|
|
212
|
+
],
|
|
213
|
+
ctx: Optional[Any] = None) -> Algebra[A | B, S]:
|
|
271
214
|
"""Run a handler only when this algebra fails.
|
|
272
215
|
|
|
273
216
|
Args:
|
|
@@ -278,12 +221,13 @@ class Algebra(Generic[A, S]):
|
|
|
278
221
|
An algebra that intercepts failures and can recover or transform them.
|
|
279
222
|
"""
|
|
280
223
|
assert callable(func), "func must be callable"
|
|
281
|
-
def fail_run(input: S, use_cache:bool) -> Either[Any, Tuple[A
|
|
282
|
-
result = self.run(input, use_cache)
|
|
224
|
+
def fail_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A|B, S]]]:
|
|
225
|
+
result = yield from self.run(input, use_cache)
|
|
283
226
|
if isinstance(result, Left):
|
|
284
227
|
return cast(Either[Any, Tuple[A | B, S]], func(self, input, result, ctx))
|
|
285
|
-
|
|
286
|
-
|
|
228
|
+
else:
|
|
229
|
+
return cast(Either[Any, Tuple[A | B, S]], result)
|
|
230
|
+
return self.__class__(fail_run, name=self.name, cache=self.cache) # type: ignore
|
|
287
231
|
|
|
288
232
|
def on_success(self,
|
|
289
233
|
func: Callable[
|
|
@@ -293,8 +237,9 @@ class Algebra(Generic[A, S]):
|
|
|
293
237
|
Right[Any, Tuple[A, S]],
|
|
294
238
|
Any
|
|
295
239
|
],
|
|
296
|
-
|
|
297
|
-
|
|
240
|
+
Either[Any, Tuple[B, S]]
|
|
241
|
+
],
|
|
242
|
+
ctx: Optional[Any] = None) -> Algebra[A | B, S]:
|
|
298
243
|
"""Run a handler only when this algebra succeeds.
|
|
299
244
|
|
|
300
245
|
Args:
|
|
@@ -305,41 +250,14 @@ class Algebra(Generic[A, S]):
|
|
|
305
250
|
An algebra that can transform or post-process successes.
|
|
306
251
|
"""
|
|
307
252
|
assert callable(func), "func must be callable"
|
|
308
|
-
def success_run(input: S, use_cache:bool) -> Either[Any, Tuple[A
|
|
309
|
-
result = self.run(input, use_cache)
|
|
253
|
+
def success_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A|B, S]]]:
|
|
254
|
+
result = yield from self.run(input, use_cache)
|
|
310
255
|
if isinstance(result, Right):
|
|
311
256
|
return cast(Either[Any, Tuple[A | B, S]], func(self, input, result, ctx))
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
label: str,
|
|
317
|
-
formatter: Optional[Callable[[
|
|
318
|
-
Algebra[Any, S],
|
|
319
|
-
S,
|
|
320
|
-
Either[Any, Tuple[Any, S]]], None]]=None) -> Algebra[A, S]:
|
|
321
|
-
def default_formatter(alg: Algebra[Any, S], input: S, result: Either[Any, Tuple[Any, S]]) -> None:
|
|
322
|
-
print(f"Debug: {'*' * 40} {alg.name} - State {'*' * 40}")
|
|
323
|
-
print(input)
|
|
324
|
-
print(f"Debug: {'~' * 40} (Result, State) {'~' * 40}")
|
|
325
|
-
print(result)
|
|
326
|
-
print()
|
|
327
|
-
print()
|
|
328
|
-
lazy_self: Algebra[A, S]
|
|
329
|
-
def debug_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
330
|
-
result = self.run(input, use_cache)
|
|
331
|
-
try:
|
|
332
|
-
if formatter is not None:
|
|
333
|
-
formatter(lazy_self, input, result)
|
|
334
|
-
else:
|
|
335
|
-
default_formatter(lazy_self, input, result)
|
|
336
|
-
except Exception as e:
|
|
337
|
-
traceback.print_exc()
|
|
338
|
-
print(f"Error occurred while formatting debug information: {e}")
|
|
339
|
-
finally:
|
|
340
|
-
return result
|
|
341
|
-
lazy_self = self.__class__(debug_run, name=label)
|
|
342
|
-
return lazy_self
|
|
257
|
+
else:
|
|
258
|
+
return cast(Either[Any, Tuple[A | B, S]], result)
|
|
259
|
+
return self.__class__(success_run, name=self.name, cache=self.cache) # type: ignore
|
|
260
|
+
|
|
343
261
|
|
|
344
262
|
######################################################## map on state ###########################################
|
|
345
263
|
def map_state(self, f: Callable[[S], S]) -> Algebra[A, S]:
|
|
@@ -351,30 +269,12 @@ class Algebra(Generic[A, S]):
|
|
|
351
269
|
Returns:
|
|
352
270
|
An algebra that runs with ``f(state)``.
|
|
353
271
|
"""
|
|
354
|
-
def map_state_run(state: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
355
|
-
|
|
356
|
-
|
|
272
|
+
def map_state_run(state: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]:
|
|
273
|
+
result = yield from self.run(f(state), use_cache)
|
|
274
|
+
return result
|
|
275
|
+
return self.__class__(map_state_run, name=self.name, cache=self.cache)
|
|
357
276
|
|
|
358
277
|
|
|
359
|
-
def map_all(self, f: Callable[[A, S], Tuple[B, S]]) -> Algebra[B, S]:
|
|
360
|
-
"""Map both the produced value and the resulting state on success.
|
|
361
|
-
|
|
362
|
-
Args:
|
|
363
|
-
f: Function mapping ``(value, state)`` to ``(new_value, new_state)``.
|
|
364
|
-
|
|
365
|
-
Returns:
|
|
366
|
-
An algebra producing the transformed value and state.
|
|
367
|
-
"""
|
|
368
|
-
def map_all_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
369
|
-
match self.run(input, use_cache):
|
|
370
|
-
case Right((value, state)):
|
|
371
|
-
new_value, new_state = f(value, state)
|
|
372
|
-
return Right((new_value, new_state))
|
|
373
|
-
case Left(err):
|
|
374
|
-
return Left(err)
|
|
375
|
-
case x:
|
|
376
|
-
raise ValueError(f"Unexpected result from self.run {x}")
|
|
377
|
-
return self.__class__(map_all_run, name=self.name) # type: ignore
|
|
378
278
|
######################################################## fundamental combinators ############################################
|
|
379
279
|
def map(self, f: Callable[[A], B]) -> Algebra[B, S]:
|
|
380
280
|
"""Transform the success value, leaving the state unchanged.
|
|
@@ -385,13 +285,13 @@ class Algebra(Generic[A, S]):
|
|
|
385
285
|
Returns:
|
|
386
286
|
An algebra that yields ``B`` with the same resulting state.
|
|
387
287
|
"""
|
|
388
|
-
def map_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
389
|
-
parsed = self.run(input, use_cache)
|
|
288
|
+
def map_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[B, S]]]:
|
|
289
|
+
parsed = yield from self.run(input, use_cache)
|
|
390
290
|
if isinstance(parsed, Right):
|
|
391
291
|
return Right((f(parsed.value[0]), parsed.value[1]))
|
|
392
292
|
else:
|
|
393
293
|
return cast(Either[Any, Tuple[B, S]], parsed)
|
|
394
|
-
return self.__class__(map_run, name=self.name) # type: ignore
|
|
294
|
+
return self.__class__(map_run, name=self.name, cache=self.cache) # type: ignore
|
|
395
295
|
|
|
396
296
|
|
|
397
297
|
def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Algebra[B, S]:
|
|
@@ -424,12 +324,13 @@ class Algebra(Generic[A, S]):
|
|
|
424
324
|
Returns:
|
|
425
325
|
An algebra that preserves successes and maps failures.
|
|
426
326
|
"""
|
|
427
|
-
def map_error_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
428
|
-
parsed = self.run(input, use_cache)
|
|
327
|
+
def map_error_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]:
|
|
328
|
+
parsed = yield from self.run(input, use_cache)
|
|
429
329
|
if isinstance(parsed, Left):
|
|
430
330
|
return Left(f(parsed.value))
|
|
431
|
-
|
|
432
|
-
|
|
331
|
+
else:
|
|
332
|
+
return parsed
|
|
333
|
+
return self.__class__(map_error_run, name=self.name, cache=self.cache)
|
|
433
334
|
|
|
434
335
|
def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Algebra[B, S]:
|
|
435
336
|
"""Chain computations where the next algebra depends on the value.
|
|
@@ -443,13 +344,31 @@ class Algebra(Generic[A, S]):
|
|
|
443
344
|
Returns:
|
|
444
345
|
An algebra yielding the result of the chained computation.
|
|
445
346
|
"""
|
|
446
|
-
def flat_map_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
447
|
-
parsed = self.run(input, use_cache)
|
|
347
|
+
def flat_map_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[B, S]]]:
|
|
348
|
+
parsed = yield from self.run(input, use_cache)
|
|
448
349
|
if isinstance(parsed, Right):
|
|
449
|
-
|
|
350
|
+
result = yield from f(parsed.value[0]).run(parsed.value[1], use_cache)
|
|
351
|
+
return result
|
|
450
352
|
else:
|
|
451
353
|
return cast(Either[Any, Tuple[B, S]], parsed)
|
|
452
|
-
return self.__class__(flat_map_run, name=self.name) # type: ignore
|
|
354
|
+
return self.__class__(flat_map_run, name=self.name, cache=self.cache) # type: ignore
|
|
355
|
+
|
|
356
|
+
def map_all(self, f: Callable[[A, S], Tuple[B, S]]) -> Algebra[B, S]:
|
|
357
|
+
"""Map both the produced value and the resulting state on success.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
f: Function mapping ``(value, state)`` to ``(new_value, new_state)``.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
An algebra producing the transformed value and state.
|
|
364
|
+
"""
|
|
365
|
+
def map_all_f(a : A) -> Algebra[B, S]:
|
|
366
|
+
def map_all_run_f(input:S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[B, S]]]:
|
|
367
|
+
yield from ()
|
|
368
|
+
return Right(f(a, input))
|
|
369
|
+
return self.__class__(map_all_run_f, name=self.name, cache=self.cache) # type: ignore
|
|
370
|
+
return self.flat_map(map_all_f)
|
|
371
|
+
|
|
453
372
|
|
|
454
373
|
|
|
455
374
|
def or_else(self: Algebra[A, S], other: Algebra[B, S]) -> Algebra[Choice[A, B], S]:
|
|
@@ -465,21 +384,26 @@ class Algebra(Generic[A, S]):
|
|
|
465
384
|
An algebra producing ``Choice.LEFT`` for this success or
|
|
466
385
|
``Choice.RIGHT`` for the other's success.
|
|
467
386
|
"""
|
|
468
|
-
def or_else_run(input: S, use_cache:bool) -> Either[Any, Tuple[Choice[A, B], S]]:
|
|
469
|
-
|
|
387
|
+
def or_else_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[Choice[A, B], S]]]:
|
|
388
|
+
self_result = yield from self.run(input, use_cache)
|
|
389
|
+
match self_result:
|
|
470
390
|
case Right((value, state)):
|
|
471
391
|
return Right((Choice(kind=ChoiceKind.LEFT, value=value), state))
|
|
472
392
|
case Left(err):
|
|
473
|
-
if isinstance(err, Error)
|
|
474
|
-
|
|
475
|
-
|
|
393
|
+
if isinstance(err, Error):
|
|
394
|
+
if err.fatal:
|
|
395
|
+
return Left(err)
|
|
396
|
+
elif err.committed:
|
|
397
|
+
return Left(replace(err, committed=False))
|
|
398
|
+
other_result = yield from other.run(input, use_cache)
|
|
399
|
+
match other_result:
|
|
476
400
|
case Right((other_value, other_state)):
|
|
477
401
|
return Right((Choice(kind=ChoiceKind.RIGHT, value=other_value), other_state))
|
|
478
402
|
case Left(other_err):
|
|
479
403
|
return Left(other_err)
|
|
480
|
-
raise
|
|
481
|
-
raise
|
|
482
|
-
return self.__class__(or_else_run, name=f'{self.name} | {other.name}') # type: ignore
|
|
404
|
+
raise SyncraftError(f"Unexpected result type from {other}", offending=other_result, expect=(Left, Right))
|
|
405
|
+
raise SyncraftError(f"Unexpected result type from {self}", offending=self_result, expect=(Left, Right))
|
|
406
|
+
return self.__class__(or_else_run, name=f'{self.name} | {other.name}', cache=self.cache | other.cache) # type: ignore
|
|
483
407
|
|
|
484
408
|
def then_both(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
|
|
485
409
|
"""Sequence two algebras and keep both values.
|
|
@@ -496,7 +420,8 @@ class Algebra(Generic[A, S]):
|
|
|
496
420
|
def combine(b: B) -> Then[A, B]:
|
|
497
421
|
return Then(left=a, right=b, kind=ThenKind.BOTH)
|
|
498
422
|
return other.map(combine)
|
|
499
|
-
|
|
423
|
+
ret = self.flat_map(then_both_f).named(f'{self.name} + {other.name}')
|
|
424
|
+
return replace(ret, cache=self.cache | other.cache)
|
|
500
425
|
|
|
501
426
|
def then_left(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
|
|
502
427
|
"""Sequence two algebras, keep the left value in the result.
|
|
@@ -513,7 +438,8 @@ class Algebra(Generic[A, S]):
|
|
|
513
438
|
def combine(b: B) -> Then[A, B]:
|
|
514
439
|
return Then(left=a, right=b, kind=ThenKind.LEFT)
|
|
515
440
|
return other.map(combine)
|
|
516
|
-
|
|
441
|
+
ret = self.flat_map(then_left_f).named(f'{self.name} // {other.name}')
|
|
442
|
+
return replace(ret, cache=self.cache | other.cache)
|
|
517
443
|
|
|
518
444
|
def then_right(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
|
|
519
445
|
"""Sequence two algebras, keep the right value in the result.
|
|
@@ -530,7 +456,8 @@ class Algebra(Generic[A, S]):
|
|
|
530
456
|
def combine(b: B) -> Then[A, B]:
|
|
531
457
|
return Then(left=a, right=b, kind=ThenKind.RIGHT)
|
|
532
458
|
return other.map(combine)
|
|
533
|
-
|
|
459
|
+
ret = self.flat_map(then_right_f).named(f'{self.name} >> {other.name}')
|
|
460
|
+
return replace(ret, cache=self.cache | other.cache)
|
|
534
461
|
|
|
535
462
|
def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[Many[A], S]:
|
|
536
463
|
"""Repeat this algebra and collect results into ``Many``.
|
|
@@ -552,18 +479,20 @@ class Algebra(Generic[A, S]):
|
|
|
552
479
|
``at_most<at_least``).
|
|
553
480
|
"""
|
|
554
481
|
if at_least <=0 or (at_most is not None and at_most < at_least):
|
|
555
|
-
raise
|
|
556
|
-
def many_run(input: S, use_cache:bool) -> Either[Any, Tuple[Many[A], S]]:
|
|
482
|
+
raise SyncraftError(f"Invalid arguments for many: at_least={at_least}, at_most={at_most}", offending=(at_least, at_most), expect="at_least>0 and (at_most is None or at_most>=at_least)")
|
|
483
|
+
def many_run(input: S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[Many[A], S]]]:
|
|
557
484
|
ret: List[A] = []
|
|
558
485
|
current_input = input
|
|
559
486
|
while True:
|
|
560
|
-
|
|
487
|
+
result = yield from self.run(current_input, use_cache)
|
|
488
|
+
match result:
|
|
561
489
|
case Left(_):
|
|
562
490
|
break
|
|
563
491
|
case Right((value, next_input)):
|
|
564
|
-
ret.append(value)
|
|
565
492
|
if next_input == current_input:
|
|
566
493
|
break # No progress, stop to avoid infinite loop
|
|
494
|
+
else:
|
|
495
|
+
ret.append(value)
|
|
567
496
|
current_input = next_input
|
|
568
497
|
if at_most is not None and len(ret) > at_most:
|
|
569
498
|
return Left(Error(
|
|
@@ -578,7 +507,7 @@ class Algebra(Generic[A, S]):
|
|
|
578
507
|
state=current_input
|
|
579
508
|
))
|
|
580
509
|
return Right((Many(value=tuple(ret)), current_input))
|
|
581
|
-
return self.__class__(many_run, name=f'*({self.name})') # type: ignore
|
|
510
|
+
return self.__class__(many_run, name=f'*({self.name})', cache=self.cache) # type: ignore
|
|
582
511
|
|
|
583
512
|
|
|
584
513
|
|