syncraft 0.1.27__tar.gz → 0.1.29__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.
Potentially problematic release.
This version of syncraft might be problematic. Click here for more details.
- {syncraft-0.1.27 → syncraft-0.1.29}/PKG-INFO +3 -3
- {syncraft-0.1.27 → syncraft-0.1.29}/README.md +2 -2
- {syncraft-0.1.27 → syncraft-0.1.29}/pyproject.toml +1 -1
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/algebra.py +42 -27
- syncraft-0.1.29/syncraft/ast.py +369 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/finder.py +13 -13
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/generator.py +53 -30
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/parser.py +4 -2
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/syntax.py +82 -62
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/PKG-INFO +3 -3
- {syncraft-0.1.27 → syncraft-0.1.29}/tests/test_bimap.py +5 -2
- syncraft-0.1.27/syncraft/ast.py +0 -187
- {syncraft-0.1.27 → syncraft-0.1.29}/LICENSE +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/setup.cfg +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/__init__.py +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/constraint.py +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/diagnostic.py +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/py.typed +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/sqlite3.py +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/SOURCES.txt +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/top_level.txt +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/tests/test_parse.py +0 -0
- {syncraft-0.1.27 → syncraft-0.1.29}/tests/test_until.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syncraft
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.29
|
|
4
4
|
Summary: Parser combinator library
|
|
5
5
|
Author-email: Michael Afmokt <michael@esacca.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,8 +19,6 @@ Syncraft is a parser/generator combinator library with full round-trip support:
|
|
|
19
19
|
|
|
20
20
|
- Parse source code into AST or dataclasses
|
|
21
21
|
- Generate source code from dataclasses
|
|
22
|
-
- Bidirectional transformations via lenses
|
|
23
|
-
- Convenience combinators: `all`, `first`, `last`, `named`
|
|
24
22
|
- SQLite syntax support included
|
|
25
23
|
|
|
26
24
|
## Installation
|
|
@@ -33,5 +31,7 @@ pip install syncraft
|
|
|
33
31
|
## TODO
|
|
34
32
|
- [ ] simplify the result of then_left and then_right by bimap the result in syntax.
|
|
35
33
|
- [ ] simplify the result of sep_by and between by bimap the result in syntax
|
|
34
|
+
- [ ] convert to dict/dataclass via bimap in syntax
|
|
35
|
+
- [ ] define DSL over Variable to construct predicates
|
|
36
36
|
- [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
|
|
37
37
|
- [ ] Make the library as fast as possible and feasible.
|
|
@@ -4,8 +4,6 @@ Syncraft is a parser/generator combinator library with full round-trip support:
|
|
|
4
4
|
|
|
5
5
|
- Parse source code into AST or dataclasses
|
|
6
6
|
- Generate source code from dataclasses
|
|
7
|
-
- Bidirectional transformations via lenses
|
|
8
|
-
- Convenience combinators: `all`, `first`, `last`, `named`
|
|
9
7
|
- SQLite syntax support included
|
|
10
8
|
|
|
11
9
|
## Installation
|
|
@@ -18,5 +16,7 @@ pip install syncraft
|
|
|
18
16
|
## TODO
|
|
19
17
|
- [ ] simplify the result of then_left and then_right by bimap the result in syntax.
|
|
20
18
|
- [ ] simplify the result of sep_by and between by bimap the result in syntax
|
|
19
|
+
- [ ] convert to dict/dataclass via bimap in syntax
|
|
20
|
+
- [ ] define DSL over Variable to construct predicates
|
|
21
21
|
- [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
|
|
22
22
|
- [ ] Make the library as fast as possible and feasible.
|
|
@@ -8,7 +8,10 @@ import traceback
|
|
|
8
8
|
from dataclasses import dataclass, replace, asdict
|
|
9
9
|
from weakref import WeakKeyDictionary
|
|
10
10
|
from abc import ABC
|
|
11
|
-
from syncraft.ast import ThenKind, Then,
|
|
11
|
+
from syncraft.ast import ThenKind, Then, Choice, Many, ChoiceKind
|
|
12
|
+
from syncraft.constraint import Bindable
|
|
13
|
+
|
|
14
|
+
S = TypeVar('S', bound=Bindable)
|
|
12
15
|
|
|
13
16
|
A = TypeVar('A') # Result type
|
|
14
17
|
B = TypeVar('B') # Mapped result type
|
|
@@ -128,10 +131,7 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
128
131
|
def lazy_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
129
132
|
return thunk().run(input, use_cache)
|
|
130
133
|
return cls(lazy_run, name=cls.__name__ + '.lazy')
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
@classmethod
|
|
136
136
|
def fail(cls, error: Any) -> Algebra[Any, S]:
|
|
137
137
|
def fail_run(input: S, use_cache:bool) -> Either[Any, Tuple[Any, S]]:
|
|
@@ -141,6 +141,7 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
141
141
|
state=input
|
|
142
142
|
))
|
|
143
143
|
return cls(fail_run, name=cls.__name__ + '.fail')
|
|
144
|
+
|
|
144
145
|
@classmethod
|
|
145
146
|
def success(cls, value: Any) -> Algebra[Any, S]:
|
|
146
147
|
def success_run(input: S, use_cache:bool) -> Either[Any, Tuple[Any, S]]:
|
|
@@ -234,7 +235,35 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
234
235
|
lazy_self = self.__class__(debug_run, name=label)
|
|
235
236
|
return lazy_self
|
|
236
237
|
|
|
238
|
+
######################################################## map on state ###########################################
|
|
239
|
+
def post_state(self, f: Callable[[S], S]) -> Algebra[A, S]:
|
|
240
|
+
def post_state_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
241
|
+
match self.run(input, use_cache):
|
|
242
|
+
case Right((value, state)):
|
|
243
|
+
return Right((value, f(state)))
|
|
244
|
+
case Left(err):
|
|
245
|
+
return Left(err)
|
|
246
|
+
case x:
|
|
247
|
+
raise ValueError(f"Unexpected result from self.run {x}")
|
|
248
|
+
return self.__class__(post_state_run, name=self.name)
|
|
249
|
+
|
|
250
|
+
def pre_state(self, f: Callable[[S], S]) -> Algebra[A, S]:
|
|
251
|
+
def pre_state_run(state: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
252
|
+
return self.run(f(state), use_cache)
|
|
253
|
+
return self.__class__(pre_state_run, name=self.name)
|
|
254
|
+
|
|
237
255
|
|
|
256
|
+
def map_all(self, f: Callable[[A, S], Tuple[B, S]]) -> Algebra[B, S]:
|
|
257
|
+
def map_all_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
258
|
+
match self.run(input, use_cache):
|
|
259
|
+
case Right((value, state)):
|
|
260
|
+
new_value, new_state = f(value, state)
|
|
261
|
+
return Right((new_value, new_state))
|
|
262
|
+
case Left(err):
|
|
263
|
+
return Left(err)
|
|
264
|
+
case x:
|
|
265
|
+
raise ValueError(f"Unexpected result from self.run {x}")
|
|
266
|
+
return self.__class__(map_all_run, name=self.name) # type: ignore
|
|
238
267
|
######################################################## fundamental combinators ############################################
|
|
239
268
|
def fmap(self, f: Callable[[A], B]) -> Algebra[B, S]:
|
|
240
269
|
def fmap_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
@@ -245,20 +274,12 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
245
274
|
return cast(Either[Any, Tuple[B, S]], parsed)
|
|
246
275
|
return self.__class__(fmap_run, name=self.name) # type: ignore
|
|
247
276
|
|
|
248
|
-
def imap(self, f: Callable[[B], A]) -> Algebra[A, S]:
|
|
249
|
-
return self.map_state(lambda s: s.map(f))
|
|
250
277
|
|
|
251
278
|
def map(self, f: Callable[[A], B]) -> Algebra[B, S]:
|
|
252
279
|
return self.fmap(f)
|
|
253
280
|
|
|
254
|
-
def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Algebra[
|
|
255
|
-
return self.fmap(f).
|
|
256
|
-
|
|
257
|
-
def map_all(self, f: Callable[[Either[Any, Tuple[A, S]]], Either[Any, Tuple[B, S]]])->Algebra[B, S]:
|
|
258
|
-
def map_all_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
259
|
-
parsed = self.run(input, use_cache)
|
|
260
|
-
return f(parsed)
|
|
261
|
-
return self.__class__(map_all_run, name=self.name) # type: ignore
|
|
281
|
+
def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Algebra[B, S]:
|
|
282
|
+
return self.fmap(f).pre_state(lambda s: s.map(i))
|
|
262
283
|
|
|
263
284
|
def map_error(self, f: Callable[[Optional[Any]], Any]) -> Algebra[A, S]:
|
|
264
285
|
def map_error_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
@@ -268,12 +289,6 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
268
289
|
return parsed
|
|
269
290
|
return self.__class__(map_error_run, name=self.name)
|
|
270
291
|
|
|
271
|
-
def map_state(self, f: Callable[[S], S]) -> Algebra[A, S]:
|
|
272
|
-
def map_state_run(state: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
273
|
-
return self.run(f(state), use_cache)
|
|
274
|
-
return self.__class__(map_state_run, name=self.name)
|
|
275
|
-
|
|
276
|
-
|
|
277
292
|
def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Algebra[B, S]:
|
|
278
293
|
def flat_map_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
279
294
|
parsed = self.run(input, use_cache)
|
|
@@ -288,34 +303,34 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
288
303
|
def or_else_run(input: S, use_cache:bool) -> Either[Any, Tuple[Choice[A, B], S]]:
|
|
289
304
|
match self.run(input, use_cache):
|
|
290
305
|
case Right((value, state)):
|
|
291
|
-
return Right((Choice(kind=ChoiceKind.LEFT,
|
|
306
|
+
return Right((Choice(kind=ChoiceKind.LEFT, value=value), state))
|
|
292
307
|
case Left(err):
|
|
293
308
|
if isinstance(err, Error) and err.committed:
|
|
294
|
-
return Left(err)
|
|
309
|
+
return Left(replace(err, committed=False))
|
|
295
310
|
match other.run(input, use_cache):
|
|
296
311
|
case Right((other_value, other_state)):
|
|
297
|
-
return Right((Choice(kind=ChoiceKind.RIGHT,
|
|
312
|
+
return Right((Choice(kind=ChoiceKind.RIGHT, value=other_value), other_state))
|
|
298
313
|
case Left(other_err):
|
|
299
314
|
return Left(other_err)
|
|
300
315
|
raise TypeError(f"Unexpected result type from {other}")
|
|
301
316
|
raise TypeError(f"Unexpected result type from {self}")
|
|
302
317
|
return self.__class__(or_else_run, name=f'{self.name} | {other.name}') # type: ignore
|
|
303
318
|
|
|
304
|
-
def then_both(self, other:
|
|
319
|
+
def then_both(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
|
|
305
320
|
def then_both_f(a: A) -> Algebra[Then[A, B], S]:
|
|
306
321
|
def combine(b: B) -> Then[A, B]:
|
|
307
322
|
return Then(left=a, right=b, kind=ThenKind.BOTH)
|
|
308
323
|
return other.fmap(combine)
|
|
309
324
|
return self.flat_map(then_both_f).named(f'{self.name} + {other.name}')
|
|
310
325
|
|
|
311
|
-
def then_left(self, other: Algebra[B, S]) ->
|
|
326
|
+
def then_left(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
|
|
312
327
|
def then_left_f(a: A) -> Algebra[Then[A, B], S]:
|
|
313
328
|
def combine(b: B) -> Then[A, B]:
|
|
314
329
|
return Then(left=a, right=b, kind=ThenKind.LEFT)
|
|
315
330
|
return other.fmap(combine)
|
|
316
331
|
return self.flat_map(then_left_f).named(f'{self.name} // {other.name}')
|
|
317
332
|
|
|
318
|
-
def then_right(self, other: Algebra[B, S]) ->
|
|
333
|
+
def then_right(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
|
|
319
334
|
def then_right_f(a: A) -> Algebra[Then[A, B], S]:
|
|
320
335
|
def combine(b: B) -> Then[A, B]:
|
|
321
336
|
return Then(left=a, right=b, kind=ThenKind.RIGHT)
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import re
|
|
5
|
+
from typing import (
|
|
6
|
+
Optional, Any, TypeVar, Tuple, runtime_checkable, cast,
|
|
7
|
+
Generic, Callable, Union, Protocol, Type, List, ClassVar
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, replace, is_dataclass, asdict, fields
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
A = TypeVar('A')
|
|
18
|
+
B = TypeVar('B')
|
|
19
|
+
C = TypeVar('C')
|
|
20
|
+
D = TypeVar('D')
|
|
21
|
+
S = TypeVar('S')
|
|
22
|
+
S1 = TypeVar('S1')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class Biarrow(Generic[A, B]):
|
|
27
|
+
forward: Callable[[A], B]
|
|
28
|
+
inverse: Callable[[B], A]
|
|
29
|
+
def __rshift__(self, other: Biarrow[B, C]) -> Biarrow[A, C]:
|
|
30
|
+
def fwd(a: A) -> C:
|
|
31
|
+
b = self.forward(a)
|
|
32
|
+
return other.forward(b)
|
|
33
|
+
def inv(c: C) -> A:
|
|
34
|
+
b = other.inverse(c)
|
|
35
|
+
return self.inverse(b)
|
|
36
|
+
return Biarrow(
|
|
37
|
+
forward=fwd,
|
|
38
|
+
inverse=inv
|
|
39
|
+
)
|
|
40
|
+
@staticmethod
|
|
41
|
+
def identity()->Biarrow[A, A]:
|
|
42
|
+
return Biarrow(
|
|
43
|
+
forward=lambda x: x,
|
|
44
|
+
inverse=lambda y: y
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def when(condition: Callable[..., bool],
|
|
49
|
+
then: Biarrow[A, B],
|
|
50
|
+
otherwise: Optional[Biarrow[A, B]] = None) -> Callable[..., Biarrow[A, B]]:
|
|
51
|
+
def _when(*args:Any, **kwargs:Any) -> Biarrow[A, B]:
|
|
52
|
+
return then if condition(*args, **kwargs) else (otherwise or Biarrow.identity())
|
|
53
|
+
return _when
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class Lens(Generic[C, A]):
|
|
58
|
+
get: Callable[[C], A]
|
|
59
|
+
set: Callable[[C, A], C]
|
|
60
|
+
|
|
61
|
+
def modify(self, source: C, f: Callable[[A], A]) -> C:
|
|
62
|
+
return self.set(source, f(self.get(source)))
|
|
63
|
+
|
|
64
|
+
def bimap(self, ff: Callable[[A], B], bf: Callable[[B], A]) -> Lens[C, B]:
|
|
65
|
+
def getf(data: C) -> B:
|
|
66
|
+
return ff(self.get(data))
|
|
67
|
+
|
|
68
|
+
def setf(data: C, value: B) -> C:
|
|
69
|
+
return self.set(data, bf(value))
|
|
70
|
+
|
|
71
|
+
return Lens(get=getf, set=setf)
|
|
72
|
+
|
|
73
|
+
def __truediv__(self, other: Lens[A, B]) -> Lens[C, B]:
|
|
74
|
+
def get_composed(obj: C) -> B:
|
|
75
|
+
return other.get(self.get(obj))
|
|
76
|
+
def set_composed(obj: C, value: B) -> C:
|
|
77
|
+
return self.set(obj, other.set(self.get(obj), value))
|
|
78
|
+
return Lens(get=get_composed, set=set_composed)
|
|
79
|
+
|
|
80
|
+
def __rtruediv__(self, other: Lens[B, C])->Lens[B, A]:
|
|
81
|
+
return other.__truediv__(self)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class Bimap(Generic[A, B]):
|
|
86
|
+
run_f: Callable[[A], Tuple[B, Callable[[B], A]]]
|
|
87
|
+
def __call__(self, a: A) -> Tuple[B, Callable[[B], A]]:
|
|
88
|
+
return self.run_f(a)
|
|
89
|
+
def __rshift__(self, other: Bimap[B, C] | Biarrow[B, C]) -> Bimap[A, C]:
|
|
90
|
+
if isinstance(other, Biarrow):
|
|
91
|
+
def biarrow_then_run(a: A) -> Tuple[C, Callable[[C], A]]:
|
|
92
|
+
b, inv1 = self(a)
|
|
93
|
+
c = other.forward(b)
|
|
94
|
+
def inv(c2: C) -> A:
|
|
95
|
+
b2 = other.inverse(c2)
|
|
96
|
+
return inv1(b2)
|
|
97
|
+
return c, inv
|
|
98
|
+
return Bimap(biarrow_then_run)
|
|
99
|
+
elif isinstance(other, Bimap):
|
|
100
|
+
def bimap_then_run(a: A) -> Tuple[C, Callable[[C], A]]:
|
|
101
|
+
b, inv1 = self(a)
|
|
102
|
+
c, inv2 = other(b)
|
|
103
|
+
def inv(c2: C) -> A:
|
|
104
|
+
return inv1(inv2(c2))
|
|
105
|
+
return c, inv
|
|
106
|
+
return Bimap(bimap_then_run)
|
|
107
|
+
else:
|
|
108
|
+
raise TypeError(f"Unsupported type for Bimap >>: {type(other)}")
|
|
109
|
+
def __rrshift__(self, other: Bimap[C, A] | Biarrow[C, A]) -> Bimap[C, B]:
|
|
110
|
+
if isinstance(other, Biarrow):
|
|
111
|
+
def biarrow_then_run(c: C) -> Tuple[B, Callable[[B], C]]:
|
|
112
|
+
a = other.forward(c)
|
|
113
|
+
b2, inv1 = self(a)
|
|
114
|
+
def inv(a2: B) -> C:
|
|
115
|
+
a3 = inv1(a2)
|
|
116
|
+
return other.inverse(a3)
|
|
117
|
+
return b2, inv
|
|
118
|
+
return Bimap(biarrow_then_run)
|
|
119
|
+
elif isinstance(other, Bimap):
|
|
120
|
+
def bimap_then_run(c: C)->Tuple[B, Callable[[B], C]]:
|
|
121
|
+
a, a2c = other(c)
|
|
122
|
+
b2, b2a = self(a)
|
|
123
|
+
def inv(b3: B) -> C:
|
|
124
|
+
a2 = b2a(b3)
|
|
125
|
+
return a2c(a2)
|
|
126
|
+
return b2, inv
|
|
127
|
+
return Bimap(bimap_then_run)
|
|
128
|
+
else:
|
|
129
|
+
raise TypeError(f"Unsupported type for Bimap <<: {type(other)}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def const(a: B)->Bimap[B, B]:
|
|
134
|
+
return Bimap(lambda _: (a, lambda b: b))
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def identity()->Bimap[A, A]:
|
|
138
|
+
return Bimap(lambda a: (a, lambda b: b))
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def when(cond: Callable[[A], bool],
|
|
142
|
+
then: Bimap[A, B],
|
|
143
|
+
otherwise: Optional[Bimap[A, C]] = None) -> Bimap[A, A | B | C]:
|
|
144
|
+
def when_run(a:A) -> Tuple[A | B | C, Callable[[A | B | C], A]]:
|
|
145
|
+
bimap = then if cond(a) else (otherwise if otherwise is not None else Bimap.identity())
|
|
146
|
+
abc, inv = bimap(a)
|
|
147
|
+
def inv_f(b: Any) -> A:
|
|
148
|
+
return inv(b)
|
|
149
|
+
return abc, inv_f
|
|
150
|
+
return Bimap(when_run)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass(frozen=True)
|
|
154
|
+
class Reducer(Generic[A, S]):
|
|
155
|
+
run_f: Callable[[A, S], S]
|
|
156
|
+
def __call__(self, a: A, s: S) -> S:
|
|
157
|
+
return self.run_f(a, s)
|
|
158
|
+
|
|
159
|
+
def map(self, f: Callable[[B], A]) -> Reducer[B, S]:
|
|
160
|
+
def map_run(b: B, s: S) -> S:
|
|
161
|
+
return self(f(b), s)
|
|
162
|
+
return Reducer(map_run)
|
|
163
|
+
|
|
164
|
+
def __rshift__(self, other: Reducer[A, S]) -> Reducer[A, S]:
|
|
165
|
+
return Reducer(lambda a, s: other(a, self(a, s)))
|
|
166
|
+
|
|
167
|
+
def zip(self, other: Reducer[A, S1])-> Reducer[A, Tuple[S, S1]]:
|
|
168
|
+
return Reducer(lambda a, s: (self(a, s[0]), other(a, s[1])))
|
|
169
|
+
|
|
170
|
+
def diff(self, other: Reducer[B, S]) -> Reducer[Tuple[A, B], S]:
|
|
171
|
+
return Reducer(lambda ab, s: other(ab[1], self(ab[0], s)))
|
|
172
|
+
|
|
173
|
+
def filter(self, f: Callable[[A, S], bool]) -> Reducer[A, S]:
|
|
174
|
+
return Reducer(lambda a, s: self(a, s) if f(a, s) else s)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@dataclass(frozen=True)
|
|
179
|
+
class AST:
|
|
180
|
+
def bimap(self, r: Bimap[Any, Any]=Bimap.identity()) -> Tuple[Any, Callable[[Any], Any]]:
|
|
181
|
+
return r(self)
|
|
182
|
+
|
|
183
|
+
@dataclass(frozen=True)
|
|
184
|
+
class Nothing(AST):
|
|
185
|
+
def __str__(self)->str:
|
|
186
|
+
return self.__class__.__name__
|
|
187
|
+
def __repr__(self)->str:
|
|
188
|
+
return self.__str__()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass(frozen=True)
|
|
192
|
+
class Marked(Generic[A], AST):
|
|
193
|
+
name: str
|
|
194
|
+
value: A
|
|
195
|
+
def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[Marked[B], Callable[[Marked[B]], Marked[A]]]:
|
|
196
|
+
v, inner_f = self.value.bimap(r) if isinstance(self.value, AST) else r(self.value)
|
|
197
|
+
return Marked(name=self.name, value=v), lambda b: Marked(name = b.name, value=inner_f(b.value))
|
|
198
|
+
|
|
199
|
+
class ChoiceKind(Enum):
|
|
200
|
+
LEFT = 'left'
|
|
201
|
+
RIGHT = 'right'
|
|
202
|
+
|
|
203
|
+
@dataclass(frozen=True)
|
|
204
|
+
class Choice(Generic[A, B], AST):
|
|
205
|
+
kind: Optional[ChoiceKind]
|
|
206
|
+
value: Optional[A | B] = None
|
|
207
|
+
def bimap(self, r: Bimap[A | B, C]=Bimap.identity()) -> Tuple[Optional[C], Callable[[Optional[C]], Choice[A, B]]]:
|
|
208
|
+
if self.value is None:
|
|
209
|
+
return None, lambda c: replace(self, value=None, kind=None)
|
|
210
|
+
else:
|
|
211
|
+
v, inv = self.value.bimap(r) if isinstance(self.value, AST) else r(self.value)
|
|
212
|
+
return v, lambda c: replace(self, value=inv(c) if c is not None else None, kind=None)
|
|
213
|
+
|
|
214
|
+
@dataclass(frozen=True)
|
|
215
|
+
class Many(Generic[A], AST):
|
|
216
|
+
value: Tuple[A, ...]
|
|
217
|
+
def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[List[B], Callable[[List[B]], Many[A]]]:
|
|
218
|
+
ret = [v.bimap(r) if isinstance(v, AST) else r(v) for v in self.value]
|
|
219
|
+
def inv(bs: List[B]) -> Many[A]:
|
|
220
|
+
if len(bs) <= len(ret):
|
|
221
|
+
return Many(value = tuple(ret[i][1](bs[i]) for i in range(len(bs))))
|
|
222
|
+
else:
|
|
223
|
+
half = [ret[i][1](bs[i]) for i in range(len(bs))]
|
|
224
|
+
tmp = [ret[-1][1](bs[i]) for i in range(len(ret)-1, len(bs))]
|
|
225
|
+
return Many(value = tuple(half + tmp))
|
|
226
|
+
return [v[0] for v in ret], inv
|
|
227
|
+
|
|
228
|
+
class ThenKind(Enum):
|
|
229
|
+
BOTH = '+'
|
|
230
|
+
LEFT = '//'
|
|
231
|
+
RIGHT = '>>'
|
|
232
|
+
|
|
233
|
+
@dataclass(eq=True, frozen=True)
|
|
234
|
+
class Then(Generic[A, B], AST):
|
|
235
|
+
kind: ThenKind
|
|
236
|
+
left: A
|
|
237
|
+
right: B
|
|
238
|
+
def arity(self)->int:
|
|
239
|
+
if self.kind == ThenKind.LEFT:
|
|
240
|
+
return self.left.arity() if isinstance(self.left, Then) else 1
|
|
241
|
+
elif self.kind == ThenKind.RIGHT:
|
|
242
|
+
return self.right.arity() if isinstance(self.right, Then) else 1
|
|
243
|
+
elif self.kind == ThenKind.BOTH:
|
|
244
|
+
left_arity = self.left.arity() if isinstance(self.left, Then) else 1
|
|
245
|
+
right_arity = self.right.arity() if isinstance(self.right, Then) else 1
|
|
246
|
+
return left_arity + right_arity
|
|
247
|
+
else:
|
|
248
|
+
return 1
|
|
249
|
+
|
|
250
|
+
def bimap(self, r: Bimap[A|B, Any]=Bimap.identity()) -> Tuple[Any | Tuple[Any, ...], Callable[[Any | Tuple[Any, ...]], Then[A, B]]]:
|
|
251
|
+
def need_wrap(x: Any) -> bool:
|
|
252
|
+
return not (isinstance(x, Then) and x.kind == ThenKind.BOTH)
|
|
253
|
+
match self.kind:
|
|
254
|
+
case ThenKind.LEFT:
|
|
255
|
+
lb, linv = self.left.bimap(r) if isinstance(self.left, AST) else r(self.left)
|
|
256
|
+
return lb, lambda c: replace(self, left=cast(A, linv(c)))
|
|
257
|
+
case ThenKind.RIGHT:
|
|
258
|
+
rb, rinv = self.right.bimap(r) if isinstance(self.right, AST) else r(self.right)
|
|
259
|
+
return rb, lambda c: replace(self, right=cast(B, rinv(c)))
|
|
260
|
+
case ThenKind.BOTH:
|
|
261
|
+
lb, linv = self.left.bimap(r) if isinstance(self.left, AST) else r(self.left)
|
|
262
|
+
rb, rinv = self.right.bimap(r) if isinstance(self.right, AST) else r(self.right)
|
|
263
|
+
left_v = (lb,) if need_wrap(self.left) else lb
|
|
264
|
+
right_v = (rb,) if need_wrap(self.right) else rb
|
|
265
|
+
def invf(b: Tuple[C, ...]) -> Then[A, B]:
|
|
266
|
+
left_size = self.left.arity() if isinstance(self.left, Then) else 1
|
|
267
|
+
right_size = self.right.arity() if isinstance(self.right, Then) else 1
|
|
268
|
+
lraw: Tuple[Any, ...] = b[:left_size]
|
|
269
|
+
rraw: Tuple[Any, ...] = b[left_size:left_size + right_size]
|
|
270
|
+
lraw = lraw[0] if left_size == 1 else lraw
|
|
271
|
+
rraw = rraw[0] if right_size == 1 else rraw
|
|
272
|
+
la = linv(lraw)
|
|
273
|
+
ra = rinv(rraw)
|
|
274
|
+
return replace(self, left=cast(A, la), right=cast(B, ra))
|
|
275
|
+
return left_v + right_v, invf
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class DataclassInstance(Protocol):
|
|
279
|
+
__dataclass_fields__: ClassVar[dict[str, Any]]
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
E = TypeVar("E", bound=DataclassInstance)
|
|
283
|
+
|
|
284
|
+
Collector = Type[E] | Callable[..., E]
|
|
285
|
+
@dataclass(frozen=True)
|
|
286
|
+
class Collect(Generic[A, E], AST):
|
|
287
|
+
collector: Collector
|
|
288
|
+
value: A
|
|
289
|
+
def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[B | E, Callable[[B | E], Collect[A, E]]]:
|
|
290
|
+
b, inner_f = r(self.value)
|
|
291
|
+
if isinstance(self.value, Then) and self.value.kind == ThenKind.BOTH:
|
|
292
|
+
assert isinstance(b, tuple), f"Expected tuple from Then.BOTH combinator, got {type(b)}"
|
|
293
|
+
index: List[str | int] = []
|
|
294
|
+
named_count = 0
|
|
295
|
+
for i, v in enumerate(b):
|
|
296
|
+
if isinstance(v, Marked):
|
|
297
|
+
index.append(v.name)
|
|
298
|
+
named_count += 1
|
|
299
|
+
else:
|
|
300
|
+
index.append(i - named_count)
|
|
301
|
+
named = {v.name: v.value for v in b if isinstance(v, Marked)}
|
|
302
|
+
unnamed = [v for v in b if not isinstance(v, Marked)]
|
|
303
|
+
ret: E = self.collector(*unnamed, **named)
|
|
304
|
+
def invf(e: E) -> Tuple[Any, ...]:
|
|
305
|
+
assert is_dataclass(e), f"Expected dataclass instance for collector inverse, got {type(e)}"
|
|
306
|
+
named_dict = asdict(e)
|
|
307
|
+
unnamed = []
|
|
308
|
+
for f in fields(e):
|
|
309
|
+
if f.name not in named:
|
|
310
|
+
unnamed.append(named_dict[f.name])
|
|
311
|
+
tmp = []
|
|
312
|
+
for x in index:
|
|
313
|
+
if isinstance(x, str):
|
|
314
|
+
tmp.append(Marked(name=x, value=named_dict[x]))
|
|
315
|
+
else:
|
|
316
|
+
tmp.append(unnamed[x])
|
|
317
|
+
return tuple(tmp)
|
|
318
|
+
return ret, lambda e: replace(self, value=inner_f(invf(e))) # type: ignore
|
|
319
|
+
else:
|
|
320
|
+
return b, lambda e: replace(self, value=inner_f(e)) # type: ignore
|
|
321
|
+
|
|
322
|
+
#########################################################################################################################
|
|
323
|
+
@dataclass(frozen=True)
|
|
324
|
+
class Token(AST):
|
|
325
|
+
token_type: Enum
|
|
326
|
+
text: str
|
|
327
|
+
def __str__(self) -> str:
|
|
328
|
+
return f"{self.token_type.name}({self.text})"
|
|
329
|
+
|
|
330
|
+
def __repr__(self) -> str:
|
|
331
|
+
return self.__str__()
|
|
332
|
+
|
|
333
|
+
@runtime_checkable
|
|
334
|
+
class TokenProtocol(Protocol):
|
|
335
|
+
@property
|
|
336
|
+
def token_type(self) -> Enum: ...
|
|
337
|
+
@property
|
|
338
|
+
def text(self) -> str: ...
|
|
339
|
+
|
|
340
|
+
T = TypeVar('T', bound=TokenProtocol)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@dataclass(frozen=True)
|
|
344
|
+
class TokenSpec:
|
|
345
|
+
token_type: Optional[Enum] = None
|
|
346
|
+
text: Optional[str] = None
|
|
347
|
+
case_sensitive: bool = False
|
|
348
|
+
regex: Optional[re.Pattern[str]] = None
|
|
349
|
+
|
|
350
|
+
def is_valid(self, token: TokenProtocol) -> bool:
|
|
351
|
+
type_match = self.token_type is None or token.token_type == self.token_type
|
|
352
|
+
value_match = self.text is None or (token.text.strip() == self.text.strip() if self.case_sensitive else
|
|
353
|
+
token.text.strip().upper() == self.text.strip().upper())
|
|
354
|
+
value_match = value_match or (self.regex is not None and self.regex.fullmatch(token.text) is not None)
|
|
355
|
+
return type_match and value_match
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
ParseResult = Union[
|
|
359
|
+
Then['ParseResult[T]', 'ParseResult[T]'],
|
|
360
|
+
Marked['ParseResult[T]'],
|
|
361
|
+
Choice['ParseResult[T]', 'ParseResult[T]'],
|
|
362
|
+
Many['ParseResult[T]'],
|
|
363
|
+
Collect['ParseResult[T]', Any],
|
|
364
|
+
Nothing,
|
|
365
|
+
T,
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
|
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
|
-
Any, Tuple, Generator as YieldGen
|
|
4
|
+
Any, Tuple, Generator as YieldGen, TypeVar
|
|
5
5
|
)
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from syncraft.algebra import (
|
|
8
8
|
Algebra, Either, Right,
|
|
9
9
|
)
|
|
10
|
-
from syncraft.ast import
|
|
10
|
+
from syncraft.ast import TokenProtocol, ParseResult, Choice, Many, Then, Marked, Collect
|
|
11
11
|
|
|
12
12
|
from syncraft.generator import GenState, Generator
|
|
13
13
|
|
|
14
14
|
from syncraft.syntax import Syntax
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
T=TypeVar('T', bound=TokenProtocol)
|
|
18
18
|
@dataclass(frozen=True)
|
|
19
19
|
class Finder(Generator[T]):
|
|
20
20
|
@classmethod
|
|
21
21
|
def anything(cls)->Algebra[Any, GenState[T]]:
|
|
22
22
|
def anything_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[Any, GenState[T]]]:
|
|
23
23
|
return Right((input.ast, input))
|
|
24
|
-
return cls(anything_run, name=cls.__name__ + '.anything
|
|
24
|
+
return cls(anything_run, name=cls.__name__ + '.anything')
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
anything = Syntax(lambda cls: cls.factory('anything')).describe(name="
|
|
28
|
+
anything = Syntax(lambda cls: cls.factory('anything')).describe(name="Anything", fixity='infix')
|
|
29
29
|
|
|
30
|
-
def matches(syntax: Syntax[Any, Any], data: ParseResult[
|
|
30
|
+
def matches(syntax: Syntax[Any, Any], data: ParseResult[Any])-> bool:
|
|
31
31
|
gen = syntax(Finder)
|
|
32
|
-
state = GenState.from_ast(ast = data, restore_pruned=True)
|
|
32
|
+
state = GenState[Any].from_ast(ast = data, restore_pruned=True)
|
|
33
33
|
result = gen.run(state, use_cache=True)
|
|
34
34
|
return isinstance(result, Right)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def find(syntax: Syntax[Any, Any], data: ParseResult[
|
|
37
|
+
def find(syntax: Syntax[Any, Any], data: ParseResult[Any]) -> YieldGen[ParseResult[Any], None, None]:
|
|
38
38
|
if matches(syntax, data):
|
|
39
39
|
yield data
|
|
40
40
|
match data:
|
|
@@ -48,10 +48,10 @@ def find(syntax: Syntax[Any, Any], data: ParseResult[T]) -> YieldGen[ParseResult
|
|
|
48
48
|
yield from find(syntax, e)
|
|
49
49
|
case Marked(value=value):
|
|
50
50
|
yield from find(syntax, value)
|
|
51
|
-
case Choice(
|
|
52
|
-
if
|
|
53
|
-
yield from find(syntax,
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
case Choice(value=value):
|
|
52
|
+
if value is not None:
|
|
53
|
+
yield from find(syntax, value)
|
|
54
|
+
case Collect(value=value):
|
|
55
|
+
yield from find(syntax, value)
|
|
56
56
|
case _:
|
|
57
57
|
pass
|