syncraft 0.1.26__tar.gz → 0.1.28__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.26 → syncraft-0.1.28}/PKG-INFO +3 -3
- {syncraft-0.1.26 → syncraft-0.1.28}/README.md +2 -2
- {syncraft-0.1.26 → syncraft-0.1.28}/pyproject.toml +1 -1
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/algebra.py +38 -26
- syncraft-0.1.28/syncraft/ast.py +378 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/finder.py +4 -4
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/generator.py +28 -16
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/syntax.py +83 -63
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft.egg-info/PKG-INFO +3 -3
- {syncraft-0.1.26 → syncraft-0.1.28}/tests/test_bimap.py +5 -2
- syncraft-0.1.26/syncraft/ast.py +0 -187
- {syncraft-0.1.26 → syncraft-0.1.28}/LICENSE +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/setup.cfg +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/__init__.py +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/constraint.py +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/diagnostic.py +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/parser.py +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/py.typed +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft/sqlite3.py +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft.egg-info/SOURCES.txt +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/syncraft.egg-info/top_level.txt +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/tests/test_parse.py +0 -0
- {syncraft-0.1.26 → syncraft-0.1.28}/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.28
|
|
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.
|
|
@@ -128,10 +128,7 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
128
128
|
def lazy_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
129
129
|
return thunk().run(input, use_cache)
|
|
130
130
|
return cls(lazy_run, name=cls.__name__ + '.lazy')
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
|
|
135
132
|
@classmethod
|
|
136
133
|
def fail(cls, error: Any) -> Algebra[Any, S]:
|
|
137
134
|
def fail_run(input: S, use_cache:bool) -> Either[Any, Tuple[Any, S]]:
|
|
@@ -141,6 +138,7 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
141
138
|
state=input
|
|
142
139
|
))
|
|
143
140
|
return cls(fail_run, name=cls.__name__ + '.fail')
|
|
141
|
+
|
|
144
142
|
@classmethod
|
|
145
143
|
def success(cls, value: Any) -> Algebra[Any, S]:
|
|
146
144
|
def success_run(input: S, use_cache:bool) -> Either[Any, Tuple[Any, S]]:
|
|
@@ -234,7 +232,35 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
234
232
|
lazy_self = self.__class__(debug_run, name=label)
|
|
235
233
|
return lazy_self
|
|
236
234
|
|
|
235
|
+
######################################################## map on state ###########################################
|
|
236
|
+
def post_state(self, f: Callable[[S], S]) -> Algebra[A, S]:
|
|
237
|
+
def post_state_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
238
|
+
match self.run(input, use_cache):
|
|
239
|
+
case Right((value, state)):
|
|
240
|
+
return Right((value, f(state)))
|
|
241
|
+
case Left(err):
|
|
242
|
+
return Left(err)
|
|
243
|
+
case x:
|
|
244
|
+
raise ValueError(f"Unexpected result from self.run {x}")
|
|
245
|
+
return self.__class__(post_state_run, name=self.name)
|
|
246
|
+
|
|
247
|
+
def pre_state(self, f: Callable[[S], S]) -> Algebra[A, S]:
|
|
248
|
+
def pre_state_run(state: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
249
|
+
return self.run(f(state), use_cache)
|
|
250
|
+
return self.__class__(pre_state_run, name=self.name)
|
|
251
|
+
|
|
237
252
|
|
|
253
|
+
def map_all(self, f: Callable[[A, S], Tuple[B, S]]) -> Algebra[B, S]:
|
|
254
|
+
def map_all_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
255
|
+
match self.run(input, use_cache):
|
|
256
|
+
case Right((value, state)):
|
|
257
|
+
new_value, new_state = f(value, state)
|
|
258
|
+
return Right((new_value, new_state))
|
|
259
|
+
case Left(err):
|
|
260
|
+
return Left(err)
|
|
261
|
+
case x:
|
|
262
|
+
raise ValueError(f"Unexpected result from self.run {x}")
|
|
263
|
+
return self.__class__(map_all_run, name=self.name) # type: ignore
|
|
238
264
|
######################################################## fundamental combinators ############################################
|
|
239
265
|
def fmap(self, f: Callable[[A], B]) -> Algebra[B, S]:
|
|
240
266
|
def fmap_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
@@ -245,20 +271,12 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
245
271
|
return cast(Either[Any, Tuple[B, S]], parsed)
|
|
246
272
|
return self.__class__(fmap_run, name=self.name) # type: ignore
|
|
247
273
|
|
|
248
|
-
def imap(self, f: Callable[[B], A]) -> Algebra[A, S]:
|
|
249
|
-
return self.map_state(lambda s: s.map(f))
|
|
250
274
|
|
|
251
275
|
def map(self, f: Callable[[A], B]) -> Algebra[B, S]:
|
|
252
276
|
return self.fmap(f)
|
|
253
277
|
|
|
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
|
|
278
|
+
def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Algebra[B, S]:
|
|
279
|
+
return self.fmap(f).pre_state(lambda s: s.map(i))
|
|
262
280
|
|
|
263
281
|
def map_error(self, f: Callable[[Optional[Any]], Any]) -> Algebra[A, S]:
|
|
264
282
|
def map_error_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
|
|
@@ -268,12 +286,6 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
268
286
|
return parsed
|
|
269
287
|
return self.__class__(map_error_run, name=self.name)
|
|
270
288
|
|
|
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
289
|
def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Algebra[B, S]:
|
|
278
290
|
def flat_map_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
|
|
279
291
|
parsed = self.run(input, use_cache)
|
|
@@ -288,34 +300,34 @@ class Algebra(ABC, Generic[A, S]):
|
|
|
288
300
|
def or_else_run(input: S, use_cache:bool) -> Either[Any, Tuple[Choice[A, B], S]]:
|
|
289
301
|
match self.run(input, use_cache):
|
|
290
302
|
case Right((value, state)):
|
|
291
|
-
return Right((Choice(kind=ChoiceKind.LEFT,
|
|
303
|
+
return Right((Choice(kind=ChoiceKind.LEFT, value=value), state))
|
|
292
304
|
case Left(err):
|
|
293
305
|
if isinstance(err, Error) and err.committed:
|
|
294
|
-
return Left(err)
|
|
306
|
+
return Left(replace(err, committed=False))
|
|
295
307
|
match other.run(input, use_cache):
|
|
296
308
|
case Right((other_value, other_state)):
|
|
297
|
-
return Right((Choice(kind=ChoiceKind.RIGHT,
|
|
309
|
+
return Right((Choice(kind=ChoiceKind.RIGHT, value=other_value), other_state))
|
|
298
310
|
case Left(other_err):
|
|
299
311
|
return Left(other_err)
|
|
300
312
|
raise TypeError(f"Unexpected result type from {other}")
|
|
301
313
|
raise TypeError(f"Unexpected result type from {self}")
|
|
302
314
|
return self.__class__(or_else_run, name=f'{self.name} | {other.name}') # type: ignore
|
|
303
315
|
|
|
304
|
-
def then_both(self, other:
|
|
316
|
+
def then_both(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
|
|
305
317
|
def then_both_f(a: A) -> Algebra[Then[A, B], S]:
|
|
306
318
|
def combine(b: B) -> Then[A, B]:
|
|
307
319
|
return Then(left=a, right=b, kind=ThenKind.BOTH)
|
|
308
320
|
return other.fmap(combine)
|
|
309
321
|
return self.flat_map(then_both_f).named(f'{self.name} + {other.name}')
|
|
310
322
|
|
|
311
|
-
def then_left(self, other: Algebra[B, S]) ->
|
|
323
|
+
def then_left(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
|
|
312
324
|
def then_left_f(a: A) -> Algebra[Then[A, B], S]:
|
|
313
325
|
def combine(b: B) -> Then[A, B]:
|
|
314
326
|
return Then(left=a, right=b, kind=ThenKind.LEFT)
|
|
315
327
|
return other.fmap(combine)
|
|
316
328
|
return self.flat_map(then_left_f).named(f'{self.name} // {other.name}')
|
|
317
329
|
|
|
318
|
-
def then_right(self, other: Algebra[B, S]) ->
|
|
330
|
+
def then_right(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
|
|
319
331
|
def then_right_f(a: A) -> Algebra[Then[A, B], S]:
|
|
320
332
|
def combine(b: B) -> Then[A, B]:
|
|
321
333
|
return Then(left=a, right=b, kind=ThenKind.RIGHT)
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import re
|
|
5
|
+
from typing import (
|
|
6
|
+
Optional, Any, TypeVar, Tuple, runtime_checkable, Self,
|
|
7
|
+
Dict, Generic, Callable, Union, Protocol, Type
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from syncraft.constraint import Bindable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
A = TypeVar('A')
|
|
18
|
+
B = TypeVar('B')
|
|
19
|
+
C = TypeVar('C')
|
|
20
|
+
D = TypeVar('D')
|
|
21
|
+
S = TypeVar('S', bound=Bindable)
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class Biarrow(Generic[A, B]):
|
|
25
|
+
forward: Callable[[A], B]
|
|
26
|
+
inverse: Callable[[B], A]
|
|
27
|
+
def __rshift__(self, other: Biarrow[B, C]) -> Biarrow[A, C]:
|
|
28
|
+
def fwd(a: A) -> C:
|
|
29
|
+
b = self.forward(a)
|
|
30
|
+
return other.forward(b)
|
|
31
|
+
def inv(c: C) -> A:
|
|
32
|
+
b = other.inverse(c)
|
|
33
|
+
return self.inverse(b)
|
|
34
|
+
return Biarrow(
|
|
35
|
+
forward=fwd,
|
|
36
|
+
inverse=inv
|
|
37
|
+
)
|
|
38
|
+
@staticmethod
|
|
39
|
+
def identity()->Biarrow[A, A]:
|
|
40
|
+
return Biarrow(
|
|
41
|
+
forward=lambda x: x,
|
|
42
|
+
inverse=lambda y: y
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def when(condition: Callable[..., bool],
|
|
47
|
+
then: Biarrow[A, B],
|
|
48
|
+
otherwise: Optional[Biarrow[A, B]] = None) -> Callable[..., Biarrow[A, B]]:
|
|
49
|
+
def _when(*args:Any, **kwargs:Any) -> Biarrow[A, B]:
|
|
50
|
+
return then if condition(*args, **kwargs) else (otherwise or Biarrow.identity())
|
|
51
|
+
return _when
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class Lens(Generic[C, A]):
|
|
56
|
+
get: Callable[[C], A]
|
|
57
|
+
set: Callable[[C, A], C]
|
|
58
|
+
|
|
59
|
+
def modify(self, source: C, f: Callable[[A], A]) -> C:
|
|
60
|
+
return self.set(source, f(self.get(source)))
|
|
61
|
+
|
|
62
|
+
def bimap(self, ff: Callable[[A], B], bf: Callable[[B], A]) -> Lens[C, B]:
|
|
63
|
+
def getf(data: C) -> B:
|
|
64
|
+
return ff(self.get(data))
|
|
65
|
+
|
|
66
|
+
def setf(data: C, value: B) -> C:
|
|
67
|
+
return self.set(data, bf(value))
|
|
68
|
+
|
|
69
|
+
return Lens(get=getf, set=setf)
|
|
70
|
+
|
|
71
|
+
def __truediv__(self, other: Lens[A, B]) -> Lens[C, B]:
|
|
72
|
+
def get_composed(obj: C) -> B:
|
|
73
|
+
return other.get(self.get(obj))
|
|
74
|
+
def set_composed(obj: C, value: B) -> C:
|
|
75
|
+
return self.set(obj, other.set(self.get(obj), value))
|
|
76
|
+
return Lens(get=get_composed, set=set_composed)
|
|
77
|
+
|
|
78
|
+
def __rtruediv__(self, other: Lens[B, C])->Lens[B, A]:
|
|
79
|
+
return other.__truediv__(self)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class Reducer(Generic[A, S]):
|
|
84
|
+
run_f: Callable[[A, S], S]
|
|
85
|
+
def __call__(self, a: A, s: S) -> S:
|
|
86
|
+
return self.run_f(a, s)
|
|
87
|
+
|
|
88
|
+
def map(self, f: Callable[[B], A]) -> Reducer[B, S]:
|
|
89
|
+
def map_run(b: B, s: S) -> S:
|
|
90
|
+
return self(f(b), s)
|
|
91
|
+
return Reducer(map_run)
|
|
92
|
+
|
|
93
|
+
def __rshift__(self, other: Reducer[A, S]) -> Reducer[A, S]:
|
|
94
|
+
return Reducer(lambda a, s: other(a, self(a, s)))
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class Bimap(Generic[A, B]):
|
|
98
|
+
run_f: Callable[[A], Tuple[B, Callable[[B], A]]]
|
|
99
|
+
def __call__(self, a: A) -> Tuple[B, Callable[[B], A]]:
|
|
100
|
+
return self.run_f(a)
|
|
101
|
+
def __rshift__(self, other: Bimap[B, C] | Biarrow[B, C]) -> Bimap[A, C]:
|
|
102
|
+
if isinstance(other, Biarrow):
|
|
103
|
+
def biarrow_then_run(a: A) -> Tuple[C, Callable[[C], A]]:
|
|
104
|
+
b, inv1 = self(a)
|
|
105
|
+
c = other.forward(b)
|
|
106
|
+
def inv(c2: C) -> A:
|
|
107
|
+
b2 = other.inverse(c2)
|
|
108
|
+
return inv1(b2)
|
|
109
|
+
return c, inv
|
|
110
|
+
return Bimap(biarrow_then_run)
|
|
111
|
+
elif isinstance(other, Bimap):
|
|
112
|
+
def bimap_then_run(a: A) -> Tuple[C, Callable[[C], A]]:
|
|
113
|
+
b, inv1 = self(a)
|
|
114
|
+
c, inv2 = other(b)
|
|
115
|
+
def inv(c2: C) -> A:
|
|
116
|
+
return inv1(inv2(c2))
|
|
117
|
+
return c, inv
|
|
118
|
+
return Bimap(bimap_then_run)
|
|
119
|
+
else:
|
|
120
|
+
raise TypeError(f"Unsupported type for Bimap >>: {type(other)}")
|
|
121
|
+
def __rrshift__(self, other: Bimap[C, A] | Biarrow[C, A]) -> Bimap[C, B]:
|
|
122
|
+
if isinstance(other, Biarrow):
|
|
123
|
+
def biarrow_then_run(c: C) -> Tuple[B, Callable[[B], C]]:
|
|
124
|
+
a = other.forward(c)
|
|
125
|
+
b2, inv1 = self(a)
|
|
126
|
+
def inv(a2: B) -> C:
|
|
127
|
+
a3 = inv1(a2)
|
|
128
|
+
return other.inverse(a3)
|
|
129
|
+
return b2, inv
|
|
130
|
+
return Bimap(biarrow_then_run)
|
|
131
|
+
elif isinstance(other, Bimap):
|
|
132
|
+
def bimap_then_run(c: C)->Tuple[B, Callable[[B], C]]:
|
|
133
|
+
a, a2c = other(c)
|
|
134
|
+
b2, b2a = self(a)
|
|
135
|
+
def inv(b3: B) -> C:
|
|
136
|
+
a2 = b2a(b3)
|
|
137
|
+
return a2c(a2)
|
|
138
|
+
return b2, inv
|
|
139
|
+
return Bimap(bimap_then_run)
|
|
140
|
+
else:
|
|
141
|
+
raise TypeError(f"Unsupported type for Bimap <<: {type(other)}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def const(a: B)->Bimap[B, B]:
|
|
146
|
+
return Bimap(lambda _: (a, lambda b: b))
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def identity()->Bimap[A, A]:
|
|
150
|
+
return Bimap(lambda a: (a, lambda b: b))
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def when(cond: Callable[[A], bool],
|
|
154
|
+
then: Bimap[A, B],
|
|
155
|
+
otherwise: Optional[Bimap[A, C]] = None) -> Bimap[A, A | B | C]:
|
|
156
|
+
def when_run(a:A) -> Tuple[A | B | C, Callable[[A | B | C], A]]:
|
|
157
|
+
bimap = then if cond(a) else (otherwise if otherwise is not None else Bimap.identity())
|
|
158
|
+
abc, inv = bimap(a)
|
|
159
|
+
def inv_f(b: Any) -> A:
|
|
160
|
+
return inv(b)
|
|
161
|
+
return abc, inv_f
|
|
162
|
+
return Bimap(when_run)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dataclass(frozen=True)
|
|
169
|
+
class AST:
|
|
170
|
+
def walk(self, r: Reducer[Any, S], s: S) -> S:
|
|
171
|
+
return s
|
|
172
|
+
|
|
173
|
+
@dataclass(frozen=True)
|
|
174
|
+
class Nothing(AST):
|
|
175
|
+
def __str__(self)->str:
|
|
176
|
+
return self.__class__.__name__
|
|
177
|
+
def __repr__(self)->str:
|
|
178
|
+
return self.__str__()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dataclass(frozen=True)
|
|
182
|
+
class Marked(Generic[A], AST):
|
|
183
|
+
name: str
|
|
184
|
+
value: A
|
|
185
|
+
def walk(self, r: Reducer[A, S], s: S) -> S:
|
|
186
|
+
return self.value.walk(r, s) if isinstance(self.value, AST) else r(self.value, s)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class DataclassProtocol(Protocol):
|
|
190
|
+
__dataclass_fields__: dict
|
|
191
|
+
|
|
192
|
+
E = TypeVar("E")
|
|
193
|
+
@dataclass(frozen=True)
|
|
194
|
+
class Collect(Generic[A, E], AST):
|
|
195
|
+
collector: Type[E]
|
|
196
|
+
value: A
|
|
197
|
+
|
|
198
|
+
class ChoiceKind(Enum):
|
|
199
|
+
LEFT = 'left'
|
|
200
|
+
RIGHT = 'right'
|
|
201
|
+
|
|
202
|
+
@dataclass(frozen=True)
|
|
203
|
+
class Choice(Generic[A, B], AST):
|
|
204
|
+
kind: Optional[ChoiceKind]
|
|
205
|
+
value: Optional[A | B] = None
|
|
206
|
+
def walk(self, r: Reducer[A | B, S], s: S) -> S:
|
|
207
|
+
if self.value is not None:
|
|
208
|
+
if isinstance(self.value, AST):
|
|
209
|
+
return self.value.walk(r, s)
|
|
210
|
+
else:
|
|
211
|
+
return r(self.value, s)
|
|
212
|
+
return s
|
|
213
|
+
|
|
214
|
+
@dataclass(frozen=True)
|
|
215
|
+
class Many(Generic[A], AST):
|
|
216
|
+
value: Tuple[A, ...]
|
|
217
|
+
def walk(self, r: Reducer[A, S], s: S) -> S:
|
|
218
|
+
for item in self.value:
|
|
219
|
+
if isinstance(item, AST):
|
|
220
|
+
s = item.walk(r, s)
|
|
221
|
+
else:
|
|
222
|
+
s = r(item, s)
|
|
223
|
+
return s
|
|
224
|
+
|
|
225
|
+
class ThenKind(Enum):
|
|
226
|
+
BOTH = '+'
|
|
227
|
+
LEFT = '//'
|
|
228
|
+
RIGHT = '>>'
|
|
229
|
+
|
|
230
|
+
FlatThen = Tuple[Any, ...]
|
|
231
|
+
MarkedThen = Tuple[Dict[str, Any] | Any, FlatThen]
|
|
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 walk(self, r: Reducer[A | B, S], s: S) -> S:
|
|
239
|
+
if isinstance(self.left, AST):
|
|
240
|
+
s = self.left.walk(r, s)
|
|
241
|
+
else:
|
|
242
|
+
s = r(self.left, s)
|
|
243
|
+
if isinstance(self.right, AST):
|
|
244
|
+
s = self.right.walk(r, s)
|
|
245
|
+
else:
|
|
246
|
+
s = r(self.right, s)
|
|
247
|
+
return s
|
|
248
|
+
|
|
249
|
+
@dataclass(frozen=True)
|
|
250
|
+
class Token(AST):
|
|
251
|
+
token_type: Enum
|
|
252
|
+
text: str
|
|
253
|
+
def __str__(self) -> str:
|
|
254
|
+
return f"{self.token_type.name}({self.text})"
|
|
255
|
+
|
|
256
|
+
def __repr__(self) -> str:
|
|
257
|
+
return self.__str__()
|
|
258
|
+
|
|
259
|
+
def walk(self, r: Reducer['Token', S], s: S) -> S:
|
|
260
|
+
return r(self, s)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@runtime_checkable
|
|
264
|
+
class TokenProtocol(Protocol):
|
|
265
|
+
@property
|
|
266
|
+
def token_type(self) -> Enum: ...
|
|
267
|
+
@property
|
|
268
|
+
def text(self) -> str: ...
|
|
269
|
+
|
|
270
|
+
T = TypeVar('T', bound=TokenProtocol)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@dataclass(frozen=True)
|
|
274
|
+
class TokenSpec:
|
|
275
|
+
token_type: Optional[Enum] = None
|
|
276
|
+
text: Optional[str] = None
|
|
277
|
+
case_sensitive: bool = False
|
|
278
|
+
regex: Optional[re.Pattern[str]] = None
|
|
279
|
+
|
|
280
|
+
def is_valid(self, token: TokenProtocol) -> bool:
|
|
281
|
+
type_match = self.token_type is None or token.token_type == self.token_type
|
|
282
|
+
value_match = self.text is None or (token.text.strip() == self.text.strip() if self.case_sensitive else
|
|
283
|
+
token.text.strip().upper() == self.text.strip().upper())
|
|
284
|
+
value_match = value_match or (self.regex is not None and self.regex.fullmatch(token.text) is not None)
|
|
285
|
+
return type_match and value_match
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
ParseResult = Union[
|
|
289
|
+
Then['ParseResult[T]', 'ParseResult[T]'],
|
|
290
|
+
Marked['ParseResult[T]'],
|
|
291
|
+
Choice['ParseResult[T]', 'ParseResult[T]'],
|
|
292
|
+
Many['ParseResult[T]'],
|
|
293
|
+
Nothing,
|
|
294
|
+
T,
|
|
295
|
+
]
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
"""
|
|
300
|
+
@staticmethod
|
|
301
|
+
def collect_marked(a: FlatThen, f: Optional[Callable[..., Any]] = None)->Tuple[MarkedThen, Callable[[MarkedThen], FlatThen]]:
|
|
302
|
+
index: List[str | int] = []
|
|
303
|
+
named_count = 0
|
|
304
|
+
for i, v in enumerate(a):
|
|
305
|
+
if isinstance(v, Marked):
|
|
306
|
+
index.append(v.name)
|
|
307
|
+
named_count += 1
|
|
308
|
+
else:
|
|
309
|
+
index.append(i - named_count)
|
|
310
|
+
named = {v.name: v.value for v in a if isinstance(v, Marked)}
|
|
311
|
+
unnamed = [v for v in a if not isinstance(v, Marked)]
|
|
312
|
+
if f is None:
|
|
313
|
+
ret = (named, tuple(unnamed))
|
|
314
|
+
else:
|
|
315
|
+
ret = (f(**named), tuple(unnamed))
|
|
316
|
+
def invf(b: MarkedThen) -> Tuple[Any, ...]:
|
|
317
|
+
named_value, unnamed_value = b
|
|
318
|
+
assert isinstance(named_value, dict) or is_dataclass(named_value), f"Expected dict or dataclass for named values, got {type(named_value)}"
|
|
319
|
+
if is_dataclass(named_value):
|
|
320
|
+
named_dict = named | asdict(cast(Any, named_value))
|
|
321
|
+
else:
|
|
322
|
+
named_dict = named | named_value
|
|
323
|
+
ret = []
|
|
324
|
+
for x in index:
|
|
325
|
+
if isinstance(x, str):
|
|
326
|
+
assert x in named_dict, f"Missing named value: {x}"
|
|
327
|
+
ret.append(named_dict[x])
|
|
328
|
+
else:
|
|
329
|
+
assert 0 <= x < len(unnamed_value), f"Missing unnamed value at index: {x}"
|
|
330
|
+
ret.append(unnamed_value[x])
|
|
331
|
+
return tuple(ret)
|
|
332
|
+
return ret, invf
|
|
333
|
+
|
|
334
|
+
def bimap(self, f: Bimap[Any, Any]=Bimap.identity()) -> Tuple[FlatThen, Callable[[FlatThen], Then[A, B]]]:
|
|
335
|
+
match self.kind:
|
|
336
|
+
case ThenKind.LEFT:
|
|
337
|
+
lb, linv = self.left.bimap(f) if isinstance(self.left, AST) else f(self.left)
|
|
338
|
+
return lb, lambda b: replace(self, left=linv(b))
|
|
339
|
+
case ThenKind.RIGHT:
|
|
340
|
+
rb, rinv = self.right.bimap(f) if isinstance(self.right, AST) else f(self.right)
|
|
341
|
+
return rb, lambda b: replace(self, right=rinv(b))
|
|
342
|
+
case ThenKind.BOTH:
|
|
343
|
+
lb, linv = self.left.bimap(f) if isinstance(self.left, AST) else f(self.left)
|
|
344
|
+
rb, rinv = self.right.bimap(f) if isinstance(self.right, AST) else f(self.right)
|
|
345
|
+
left_v = (lb,) if not isinstance(self.left, Then) else lb
|
|
346
|
+
right_v = (rb,) if not isinstance(self.right, Then) else rb
|
|
347
|
+
def invf(b: Tuple[Any, ...]) -> Then[A, B]:
|
|
348
|
+
left_size = self.left.arity() if isinstance(self.left, Then) else 1
|
|
349
|
+
right_size = self.right.arity() if isinstance(self.right, Then) else 1
|
|
350
|
+
lraw = b[:left_size]
|
|
351
|
+
rraw = b[left_size:left_size + right_size]
|
|
352
|
+
lraw = lraw[0] if left_size == 1 else lraw
|
|
353
|
+
rraw = rraw[0] if right_size == 1 else rraw
|
|
354
|
+
la = linv(lraw)
|
|
355
|
+
ra = rinv(rraw)
|
|
356
|
+
return replace(self, left=la, right=ra)
|
|
357
|
+
return left_v + right_v, invf
|
|
358
|
+
|
|
359
|
+
def bimap_collected(self, f: Bimap[Any, Any]=Bimap.identity()) -> Tuple[MarkedThen, Callable[[MarkedThen], Then[A, B]]]:
|
|
360
|
+
data, invf = self.bimap(f)
|
|
361
|
+
data, func = Then.collect_marked(data)
|
|
362
|
+
return data, lambda d: invf(func(d))
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def arity(self)->int:
|
|
366
|
+
if self.kind == ThenKind.LEFT:
|
|
367
|
+
return self.left.arity() if isinstance(self.left, Then) else 1
|
|
368
|
+
elif self.kind == ThenKind.RIGHT:
|
|
369
|
+
return self.right.arity() if isinstance(self.right, Then) else 1
|
|
370
|
+
elif self.kind == ThenKind.BOTH:
|
|
371
|
+
left_arity = self.left.arity() if isinstance(self.left, Then) else 1
|
|
372
|
+
right_arity = self.right.arity() if isinstance(self.right, Then) else 1
|
|
373
|
+
return left_arity + right_arity
|
|
374
|
+
else:
|
|
375
|
+
return 1
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
"""
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
|
-
Any, Tuple,
|
|
4
|
+
Any, Tuple, Generator as YieldGen
|
|
5
5
|
)
|
|
6
|
-
from dataclasses import dataclass
|
|
6
|
+
from dataclasses import dataclass
|
|
7
7
|
from syncraft.algebra import (
|
|
8
8
|
Algebra, Either, Right,
|
|
9
9
|
)
|
|
10
10
|
from syncraft.ast import T, ParseResult, Choice, Many, Then, Marked
|
|
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
|
|
|
18
18
|
@dataclass(frozen=True)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
|
-
Any, TypeVar, Tuple, Optional, Callable, Generic,
|
|
4
|
+
Any, TypeVar, Tuple, Optional, Callable, Generic,
|
|
5
5
|
List,
|
|
6
6
|
)
|
|
7
7
|
from functools import cached_property
|
|
@@ -11,8 +11,8 @@ from syncraft.algebra import (
|
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
from syncraft.ast import (
|
|
14
|
-
T, ParseResult, AST, Token, TokenSpec,
|
|
15
|
-
Bindable,
|
|
14
|
+
T, ParseResult, AST, Token, TokenSpec,
|
|
15
|
+
Bindable, Nothing,
|
|
16
16
|
Choice, Many, ChoiceKind,
|
|
17
17
|
Then, ThenKind, Marked
|
|
18
18
|
)
|
|
@@ -23,7 +23,7 @@ import re
|
|
|
23
23
|
import rstr
|
|
24
24
|
from functools import lru_cache
|
|
25
25
|
import random
|
|
26
|
-
|
|
26
|
+
from rich import print
|
|
27
27
|
B = TypeVar('B')
|
|
28
28
|
|
|
29
29
|
|
|
@@ -136,7 +136,7 @@ class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
|
136
136
|
def flat_map(self, f: Callable[[ParseResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
|
|
137
137
|
def flat_map_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
|
|
138
138
|
try:
|
|
139
|
-
if not isinstance(input.ast, Then):
|
|
139
|
+
if not isinstance(input.ast, Then) or isinstance(input.ast, Nothing):
|
|
140
140
|
return Left(Error(this=self,
|
|
141
141
|
message=f"Expect Then got {input.ast}",
|
|
142
142
|
state=input))
|
|
@@ -179,7 +179,7 @@ class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
|
179
179
|
pass
|
|
180
180
|
return Right((Many(value=tuple(ret)), input))
|
|
181
181
|
else:
|
|
182
|
-
if not isinstance(input.ast, Many):
|
|
182
|
+
if not isinstance(input.ast, Many) or isinstance(input.ast, Nothing):
|
|
183
183
|
return Left(Error(this=self,
|
|
184
184
|
message=f"Expect Many got {input.ast}",
|
|
185
185
|
state=input))
|
|
@@ -194,7 +194,7 @@ class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
|
194
194
|
this=self,
|
|
195
195
|
state=input.inject(x)
|
|
196
196
|
))
|
|
197
|
-
case Left(
|
|
197
|
+
case Left(e):
|
|
198
198
|
pass
|
|
199
199
|
if len(ret) < at_least:
|
|
200
200
|
return Left(Error(
|
|
@@ -210,22 +210,34 @@ class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
|
210
210
|
other: Algebra[ParseResult[T], GenState[T]]
|
|
211
211
|
) -> Algebra[Choice[ParseResult[T], ParseResult[T]], GenState[T]]:
|
|
212
212
|
def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[Choice[ParseResult[T], ParseResult[T]], GenState[T]]]:
|
|
213
|
-
def exec(kind: ChoiceKind,
|
|
213
|
+
def exec(kind: ChoiceKind | None,
|
|
214
214
|
left: GenState[T],
|
|
215
215
|
right: GenState[T])->Either[Any, Tuple[Choice[ParseResult[T], ParseResult[T]], GenState[T]]]:
|
|
216
216
|
match kind:
|
|
217
217
|
case ChoiceKind.LEFT:
|
|
218
218
|
match self.run(left, use_cache):
|
|
219
219
|
case Right((value, next_input)):
|
|
220
|
-
return Right((Choice(kind=ChoiceKind.LEFT,
|
|
220
|
+
return Right((Choice(kind=ChoiceKind.LEFT, value=value), next_input))
|
|
221
221
|
case Left(error):
|
|
222
222
|
return Left(error)
|
|
223
223
|
case ChoiceKind.RIGHT:
|
|
224
224
|
match other.run(right, use_cache):
|
|
225
225
|
case Right((value, next_input)):
|
|
226
|
-
return Right((Choice(kind=ChoiceKind.RIGHT,
|
|
226
|
+
return Right((Choice(kind=ChoiceKind.RIGHT, value=value), next_input))
|
|
227
227
|
case Left(error):
|
|
228
228
|
return Left(error)
|
|
229
|
+
case None:
|
|
230
|
+
match self.run(left, use_cache):
|
|
231
|
+
case Right((value, next_input)):
|
|
232
|
+
return Right((Choice(kind=ChoiceKind.LEFT, value=value), next_input))
|
|
233
|
+
case Left(error):
|
|
234
|
+
if isinstance(error, Error) and error.committed:
|
|
235
|
+
return Left(replace(error, committed=False))
|
|
236
|
+
match other.run(left, use_cache):
|
|
237
|
+
case Right((value, next_input)):
|
|
238
|
+
return Right((Choice(kind=ChoiceKind.RIGHT, value=value), next_input))
|
|
239
|
+
case Left(error):
|
|
240
|
+
return Left(error)
|
|
229
241
|
raise ValueError(f"Invalid ChoiceKind: {kind}")
|
|
230
242
|
|
|
231
243
|
if input.pruned:
|
|
@@ -233,14 +245,14 @@ class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
|
233
245
|
which = forked_input.rng("or_else").choice((ChoiceKind.LEFT, ChoiceKind.RIGHT))
|
|
234
246
|
return exec(which, forked_input, forked_input)
|
|
235
247
|
else:
|
|
236
|
-
if isinstance(input.ast, Choice):
|
|
237
|
-
return exec(input.ast.kind,
|
|
238
|
-
input.inject(input.ast.left),
|
|
239
|
-
input.inject(input.ast.right))
|
|
240
|
-
else:
|
|
248
|
+
if not isinstance(input.ast, Choice) or isinstance(input.ast, Nothing):
|
|
241
249
|
return Left(Error(this=self,
|
|
242
250
|
message=f"Expect Choice got {input.ast}",
|
|
243
251
|
state=input))
|
|
252
|
+
else:
|
|
253
|
+
return exec(input.ast.kind,
|
|
254
|
+
input.inject(input.ast.value),
|
|
255
|
+
input.inject(input.ast.value))
|
|
244
256
|
return self.__class__(or_else_run, name=f"or_else({self.name} | {other.name})") # type: ignore
|
|
245
257
|
|
|
246
258
|
@classmethod
|
|
@@ -259,7 +271,7 @@ class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
|
259
271
|
current = input.ast
|
|
260
272
|
if not isinstance(current, Token) or not gen.is_valid(current):
|
|
261
273
|
return Left(Error(None,
|
|
262
|
-
message=f"Expected a Token, but got {
|
|
274
|
+
message=f"Expected a Token({gen.text}), but got {current}.",
|
|
263
275
|
state=input))
|
|
264
276
|
return Right((current, input))
|
|
265
277
|
lazy_self = cls(token_run, name=cls.__name__ + f'.token({token_type or text or regex})') # type: ignore
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
|
-
Optional,
|
|
4
|
+
Optional, Any, TypeVar, Generic, Callable, Tuple, cast,
|
|
5
5
|
Type, Literal
|
|
6
6
|
)
|
|
7
7
|
from dataclasses import dataclass, field, replace
|
|
8
8
|
from functools import reduce
|
|
9
9
|
from syncraft.algebra import Algebra, Error, Either, Right
|
|
10
10
|
from syncraft.constraint import Variable, Bindable
|
|
11
|
-
from syncraft.ast import Then, ThenKind, Marked
|
|
11
|
+
from syncraft.ast import Then, ThenKind, Marked, Choice, Many, ChoiceKind, Nothing, Collect, E
|
|
12
12
|
from types import MethodType, FunctionType
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
from rich import print
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
A = TypeVar('A') # Result type
|
|
18
18
|
B = TypeVar('B') # Result type for mapping
|
|
19
19
|
C = TypeVar('C') # Result type for else branch
|
|
20
|
+
D = TypeVar('D') # Result type for else branch
|
|
20
21
|
S = TypeVar('S', bound=Bindable) # State type
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
|
|
25
27
|
@dataclass(frozen=True)
|
|
26
28
|
class Description:
|
|
27
29
|
name: Optional[str] = None
|
|
@@ -134,49 +136,50 @@ class Syntax(Generic[A, S]):
|
|
|
134
136
|
######################################################## value transformation ########################################################
|
|
135
137
|
def map(self, f: Callable[[A], B]) -> Syntax[B, S]:
|
|
136
138
|
return self.__class__(lambda cls: self.alg(cls).map(f), meta = self.meta) # type: ignore
|
|
137
|
-
|
|
138
|
-
def
|
|
139
|
-
return self.__class__(lambda cls: self.alg(cls).
|
|
140
|
-
|
|
141
|
-
def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Syntax[A, S]:
|
|
142
|
-
return self.__class__(lambda cls: self.alg(cls).bimap(f, i), meta=self.meta)
|
|
139
|
+
|
|
140
|
+
def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Syntax[B, S]:
|
|
141
|
+
return self.__class__(lambda cls: self.alg(cls).bimap(f, i), meta=self.meta) # type: ignore
|
|
143
142
|
|
|
144
|
-
def map_all(self, f: Callable[[
|
|
143
|
+
def map_all(self, f: Callable[[A, S], Tuple[B, S]]) -> Syntax[B, S]:
|
|
145
144
|
return self.__class__(lambda cls: self.alg(cls).map_all(f), meta=self.meta) # type: ignore
|
|
146
|
-
|
|
145
|
+
|
|
147
146
|
def map_error(self, f: Callable[[Optional[Any]], Any]) -> Syntax[A, S]:
|
|
148
147
|
return self.__class__(lambda cls: self.alg(cls).map_error(f), meta=self.meta)
|
|
149
148
|
|
|
150
|
-
def
|
|
151
|
-
return self.__class__(lambda cls: self.alg(cls).
|
|
149
|
+
def pre_state(self, f: Callable[[S], S]) -> Syntax[A, S]:
|
|
150
|
+
return self.__class__(lambda cls: self.alg(cls).pre_state(f), meta=self.meta)
|
|
152
151
|
|
|
152
|
+
def post_state(self, f: Callable[[S], S]) -> Syntax[A, S]:
|
|
153
|
+
return self.__class__(lambda cls: self.alg(cls).post_state(f), meta=self.meta)
|
|
153
154
|
|
|
154
155
|
def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
|
|
155
156
|
return self.__class__(lambda cls: self.alg(cls).flat_map(f)) # type: ignore
|
|
156
157
|
|
|
157
|
-
def many(self, *, at_least: int = 1, at_most: Optional[int] = None) -> Syntax[
|
|
158
|
+
def many(self, *, at_least: int = 1, at_most: Optional[int] = None) -> Syntax[Many[A], S]:
|
|
158
159
|
return self.__class__(lambda cls:self.alg(cls).many(at_least=at_least, at_most=at_most)).describe(name='*', # type: ignore
|
|
159
160
|
fixity='prefix',
|
|
160
161
|
parameter=(self,))
|
|
161
162
|
|
|
162
163
|
################################################ facility combinators ############################################################
|
|
163
164
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def between(self, left: Syntax[Any, S], right: Syntax[Any, S]) -> Syntax[Then[Any, Then[A, Any]], S]:
|
|
165
|
+
def between(self, left: Syntax[B, S], right: Syntax[C, S]) -> Syntax[Then[B, Then[A, C]], S]:
|
|
167
166
|
return left >> self // right
|
|
168
167
|
|
|
169
|
-
def sep_by(self, sep: Syntax[
|
|
170
|
-
|
|
168
|
+
def sep_by(self, sep: Syntax[B, S]) -> Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S]:
|
|
169
|
+
ret: Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S] = (self + (sep >> self).many().optional())
|
|
170
|
+
return ret.describe(
|
|
171
171
|
name='sep_by',
|
|
172
172
|
fixity='prefix',
|
|
173
173
|
parameter=(self, sep))
|
|
174
|
-
|
|
175
|
-
def parens(self,
|
|
174
|
+
|
|
175
|
+
def parens(self,
|
|
176
|
+
sep: Syntax[C, S],
|
|
177
|
+
open: Syntax[B, S],
|
|
178
|
+
close: Syntax[D, S]) -> Syntax[Then[B, Then[Then[A, Choice[Many[Then[C, A]], Optional[Nothing]]], D]], S]:
|
|
176
179
|
return self.sep_by(sep=sep).between(left=open, right=close)
|
|
177
180
|
|
|
178
|
-
def optional(self
|
|
179
|
-
return (self | success(
|
|
181
|
+
def optional(self) -> Syntax[Choice[A, Optional[Nothing]], S]:
|
|
182
|
+
return (self | success(Nothing())).describe(name='~', fixity='prefix', parameter=(self,))
|
|
180
183
|
|
|
181
184
|
|
|
182
185
|
def cut(self) -> Syntax[A, S]:
|
|
@@ -184,63 +187,80 @@ class Syntax(Generic[A, S]):
|
|
|
184
187
|
|
|
185
188
|
|
|
186
189
|
####################################################### operator overloading #############################################
|
|
187
|
-
def __floordiv__(self, other: Syntax[B, S]) -> Syntax[Then[A,
|
|
190
|
+
def __floordiv__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
188
191
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
ret: Syntax[Then[A, B], S] = self.__class__(lambda cls: self.alg(cls).then_left(other.alg(cls))) # type: ignore
|
|
193
|
+
return ret.describe(name=ThenKind.LEFT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
194
|
+
|
|
195
|
+
def __lshift__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
196
|
+
return self.__floordiv__(other)
|
|
192
197
|
|
|
193
|
-
def __rfloordiv__(self, other: Syntax[B, S]) -> Syntax[Then[B,
|
|
198
|
+
def __rfloordiv__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
194
199
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
195
200
|
return other.__floordiv__(self)
|
|
201
|
+
|
|
202
|
+
def __rlshift__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
203
|
+
return self.__rfloordiv__(other)
|
|
196
204
|
|
|
197
|
-
def
|
|
198
|
-
|
|
205
|
+
def __add__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
206
|
+
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
207
|
+
ret: Syntax[Then[A, B], S] = self.__class__(lambda cls: self.alg(cls).then_both(other.alg(cls))) # type: ignore
|
|
208
|
+
return ret.describe(name=ThenKind.BOTH.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
199
209
|
|
|
200
210
|
def __radd__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
201
211
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
202
212
|
return other.__add__(self)
|
|
203
213
|
|
|
204
|
-
def
|
|
205
|
-
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
206
|
-
return self.__class__(
|
|
207
|
-
lambda cls: self.alg(cls).then_both(other.alg(cls)) # type: ignore
|
|
208
|
-
).describe(name=ThenKind.BOTH.value, fixity='infix', parameter=(self, other))
|
|
209
|
-
|
|
210
|
-
def __rshift__(self, other: Syntax[B, S]) -> Syntax[Then[None, B], S]:
|
|
214
|
+
def __rshift__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
211
215
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
).describe(name=ThenKind.RIGHT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[None, B], S])
|
|
215
|
-
|
|
216
|
+
ret: Syntax[Then[A, B], S] = self.__class__(lambda cls: self.alg(cls).then_right(other.alg(cls))) # type: ignore
|
|
217
|
+
return ret.describe(name=ThenKind.RIGHT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
216
218
|
|
|
217
|
-
def __rrshift__(self, other: Syntax[B, S]) -> Syntax[Then[
|
|
219
|
+
def __rrshift__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
218
220
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
219
221
|
return other.__rshift__(self)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
def __or__(self, other: Syntax[B, S]) -> Syntax[A | B, S]:
|
|
222
|
+
|
|
223
|
+
def __or__(self, other: Syntax[B, S]) -> Syntax[Choice[A, B], S]:
|
|
223
224
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
ret: Syntax[Choice[A, B], S] = self.__class__(lambda cls: self.alg(cls).or_else(other.alg(cls))) # type: ignore
|
|
226
|
+
return ret.describe(name='|', fixity='infix', parameter=(self, other))
|
|
226
227
|
|
|
227
|
-
def __ror__(self, other: Syntax[B, S]) -> Syntax[
|
|
228
|
+
def __ror__(self, other: Syntax[B, S]) -> Syntax[Choice[B, A], S]:
|
|
228
229
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
229
|
-
return other.__or__(self)
|
|
230
|
+
return other.__or__(self)
|
|
231
|
+
|
|
232
|
+
def __invert__(self) -> Syntax[Choice[A, Optional[Nothing]], S]:
|
|
233
|
+
return self.optional()
|
|
230
234
|
|
|
231
235
|
|
|
232
236
|
######################################################################## data processing combinators #########################################################
|
|
233
|
-
def bind(self,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
237
|
+
def bind(self,
|
|
238
|
+
var: Variable,
|
|
239
|
+
collector: Optional[Type[E]]=None) -> Syntax[A |
|
|
240
|
+
Marked[A] |
|
|
241
|
+
Marked[Collect[A, E]] |
|
|
242
|
+
Collect[A, E], S]:
|
|
243
|
+
def bind_v(v: A | Marked[A] | Marked[Collect[A, E]] | Collect[A, E],
|
|
244
|
+
s: S)->Tuple[A | Marked[A] | Marked[Collect[A, E]] | Collect[A, E], S]:
|
|
245
|
+
return v, s.bind(var, v)
|
|
246
|
+
if callable(collector):
|
|
247
|
+
ret = self.to(collector).mark(var.name).map_all(bind_v) if var.name else self.to(collector).map_all(bind_v)
|
|
248
|
+
else:
|
|
249
|
+
ret = self.mark(var.name).map_all(bind_v) if var.name else self.map_all(bind_v)
|
|
241
250
|
return ret.describe(name=f'bind({var.name})', fixity='postfix', parameter=(self,))
|
|
242
251
|
|
|
243
|
-
def
|
|
252
|
+
def to(self, f: Type[E])-> Syntax[Collect[A, E], S]:
|
|
253
|
+
def to_f(v: A) -> Collect[A, E]:
|
|
254
|
+
if isinstance(v, Collect):
|
|
255
|
+
return replace(v, collector=f)
|
|
256
|
+
else:
|
|
257
|
+
return Collect(collector=f, value=v)
|
|
258
|
+
def ito_f(c: Collect[A, E]) -> A:
|
|
259
|
+
return c.value if isinstance(c, Collect) else c
|
|
260
|
+
return self.bimap(to_f, ito_f).describe(name=f'to({f})', fixity='postfix', parameter=(self,))
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def mark(self, var: str) -> Syntax[Marked[A], S]:
|
|
244
264
|
def bind_s(value: A) -> Marked[A]:
|
|
245
265
|
if isinstance(value, Marked):
|
|
246
266
|
return replace(value, name=var)
|
|
@@ -278,17 +298,17 @@ def success(value: Any) -> Syntax[Any, Any]:
|
|
|
278
298
|
return Syntax(lambda alg: alg.success(value)).describe(name=f'success({value})', fixity='prefix')
|
|
279
299
|
|
|
280
300
|
def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
281
|
-
return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(
|
|
301
|
+
return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(Nothing())
|
|
282
302
|
|
|
283
303
|
|
|
284
304
|
def all(*parsers: Syntax[Any, S]) -> Syntax[Then[Any, Any], S]:
|
|
285
|
-
return reduce(lambda a, b: a + b, parsers) if len(parsers) > 0 else success(
|
|
305
|
+
return reduce(lambda a, b: a + b, parsers) if len(parsers) > 0 else success(Nothing())
|
|
286
306
|
|
|
287
307
|
def first(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
288
|
-
return reduce(lambda a, b: a // b, parsers) if len(parsers) > 0 else success(
|
|
308
|
+
return reduce(lambda a, b: a // b, parsers) if len(parsers) > 0 else success(Nothing())
|
|
289
309
|
|
|
290
310
|
def last(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
291
|
-
return reduce(lambda a, b: a >> b, parsers) if len(parsers) > 0 else success(
|
|
311
|
+
return reduce(lambda a, b: a >> b, parsers) if len(parsers) > 0 else success(Nothing())
|
|
292
312
|
|
|
293
313
|
def bound(* parsers: Syntax[Any, S] | Tuple[str|Variable, Syntax[Any, S]]) -> Syntax[Any, S]:
|
|
294
314
|
def is_named_parser(x: Any) -> bool:
|
|
@@ -325,5 +345,5 @@ def bound(* parsers: Syntax[Any, S] | Tuple[str|Variable, Syntax[Any, S]]) -> Sy
|
|
|
325
345
|
else:
|
|
326
346
|
ret = ret >> just_parser
|
|
327
347
|
|
|
328
|
-
return ret if ret is not None else success(
|
|
348
|
+
return ret if ret is not None else success(Nothing())
|
|
329
349
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syncraft
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.28
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from syncraft.ast import Then, ThenKind, Many
|
|
3
|
+
from syncraft.ast import Then, ThenKind, Many, Choice, ChoiceKind, Token
|
|
4
4
|
from syncraft.algebra import Error
|
|
5
5
|
from syncraft.parser import literal, parse
|
|
6
6
|
import syncraft.generator as gen
|
|
@@ -248,7 +248,7 @@ def test_ambiguous() -> None:
|
|
|
248
248
|
sql = "a"
|
|
249
249
|
ast = parse(syntax, sql, dialect="sqlite")
|
|
250
250
|
# Does it prefer A (shorter) or B (fails)? Depends on design.
|
|
251
|
-
|
|
251
|
+
assert ast == Choice[Token, Token](value=TokenGen.from_string("a"), kind=ChoiceKind.LEFT)
|
|
252
252
|
|
|
253
253
|
|
|
254
254
|
def test_combo() -> None:
|
|
@@ -258,8 +258,11 @@ def test_combo() -> None:
|
|
|
258
258
|
syntax = ((A + B).many() | C) + B
|
|
259
259
|
sql = "a b a b c b"
|
|
260
260
|
# Should fail, as we discussed earlier
|
|
261
|
+
# the working syntax should be ((A + B) | C).many() + B
|
|
261
262
|
ast = parse(syntax, sql, dialect="sqlite")
|
|
262
263
|
assert isinstance(ast, Error)
|
|
264
|
+
ast = parse(((A + B) | C).many() + B, sql, dialect="sqlite")
|
|
265
|
+
assert not isinstance(ast, Error)
|
|
263
266
|
|
|
264
267
|
|
|
265
268
|
def test_optional():
|
syncraft-0.1.26/syncraft/ast.py
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
import re
|
|
5
|
-
from typing import (
|
|
6
|
-
Optional, Any, TypeVar, Tuple, runtime_checkable,
|
|
7
|
-
Dict, Generic, Callable, Union, cast, List, Protocol, Type
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from dataclasses import dataclass, replace, is_dataclass, asdict
|
|
12
|
-
from enum import Enum
|
|
13
|
-
|
|
14
|
-
from syncraft.constraint import Binding, Variable, Bindable
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
A = TypeVar('A')
|
|
19
|
-
B = TypeVar('B')
|
|
20
|
-
C = TypeVar('C')
|
|
21
|
-
S = TypeVar('S', bound=Bindable)
|
|
22
|
-
|
|
23
|
-
@dataclass(frozen=True)
|
|
24
|
-
class Reducer(Generic[A, S]):
|
|
25
|
-
run_f: Callable[[A, S], S]
|
|
26
|
-
def __call__(self, a: A, s: S) -> S:
|
|
27
|
-
return self.run_f(a, s)
|
|
28
|
-
|
|
29
|
-
def map(self, f: Callable[[B], A]) -> Reducer[B, S]:
|
|
30
|
-
def map_run(b: B, s: S) -> S:
|
|
31
|
-
return self(f(b), s)
|
|
32
|
-
return Reducer(map_run)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@dataclass(frozen=True)
|
|
37
|
-
class Bimap(Generic[A, B]):
|
|
38
|
-
run_f: Callable[[A], Tuple[B, Callable[[B], A]]]
|
|
39
|
-
def __call__(self, a: A) -> Tuple[B, Callable[[B], A]]:
|
|
40
|
-
return self.run_f(a)
|
|
41
|
-
def __rshift__(self, other: Bimap[B, C]) -> Bimap[A, C]:
|
|
42
|
-
def then_run(a: A) -> Tuple[C, Callable[[C], A]]:
|
|
43
|
-
b, inv1 = self(a)
|
|
44
|
-
c, inv2 = other(b)
|
|
45
|
-
def inv(c2: C) -> A:
|
|
46
|
-
return inv1(inv2(c2))
|
|
47
|
-
return c, inv
|
|
48
|
-
return Bimap(then_run)
|
|
49
|
-
@staticmethod
|
|
50
|
-
def const(a: B)->Bimap[B, B]:
|
|
51
|
-
return Bimap(lambda _: (a, lambda b: b))
|
|
52
|
-
|
|
53
|
-
@staticmethod
|
|
54
|
-
def identity()->Bimap[Any, Any]:
|
|
55
|
-
return Bimap(lambda a: (a, lambda b: b))
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def when(cond: Callable[[A], bool],
|
|
59
|
-
then: Bimap[A, B],
|
|
60
|
-
otherwise: Optional[Bimap[A, C]] = None) -> Bimap[A, A | B | C]:
|
|
61
|
-
def when_run(a:A) -> Tuple[A | B | C, Callable[[A | B | C], A]]:
|
|
62
|
-
bimap = then if cond(a) else (otherwise if otherwise is not None else Bimap.identity())
|
|
63
|
-
abc, inv = bimap(a)
|
|
64
|
-
def inv_f(b: Any) -> A:
|
|
65
|
-
return inv(b)
|
|
66
|
-
return abc, inv_f
|
|
67
|
-
return Bimap(when_run)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@dataclass(frozen=True)
|
|
72
|
-
class Biarrow(Generic[S, A, B]):
|
|
73
|
-
forward: Callable[[S, A], Tuple[S, B]]
|
|
74
|
-
inverse: Callable[[S, B], Tuple[S, A]]
|
|
75
|
-
def __rshift__(self, other: Biarrow[S, B, C]) -> Biarrow[S, A, C]:
|
|
76
|
-
def fwd(s: S, a: A) -> Tuple[S, C]:
|
|
77
|
-
s1, b = self.forward(s, a)
|
|
78
|
-
return other.forward(s1, b)
|
|
79
|
-
def inv(s: S, c: C) -> Tuple[S, A]:
|
|
80
|
-
s1, b = other.inverse(s, c)
|
|
81
|
-
return self.inverse(s1, b)
|
|
82
|
-
return Biarrow(
|
|
83
|
-
forward=fwd,
|
|
84
|
-
inverse=inv
|
|
85
|
-
)
|
|
86
|
-
@staticmethod
|
|
87
|
-
def identity()->Biarrow[S, A, A]:
|
|
88
|
-
return Biarrow(
|
|
89
|
-
forward=lambda s, x: (s, x),
|
|
90
|
-
inverse=lambda s, y: (s, y)
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
@staticmethod
|
|
94
|
-
def when(condition: Callable[..., bool],
|
|
95
|
-
then: Biarrow[S, A, B],
|
|
96
|
-
otherwise: Optional[Biarrow[S, A, B]] = None) -> Callable[..., Biarrow[S, A, B]]:
|
|
97
|
-
def _when(*args:Any, **kwargs:Any) -> Biarrow[S, A, B]:
|
|
98
|
-
return then if condition(*args, **kwargs) else (otherwise or Biarrow.identity())
|
|
99
|
-
return _when
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@dataclass(frozen=True)
|
|
103
|
-
class AST:
|
|
104
|
-
pass
|
|
105
|
-
|
|
106
|
-
class ChoiceKind(Enum):
|
|
107
|
-
LEFT = 'left'
|
|
108
|
-
RIGHT = 'right'
|
|
109
|
-
@dataclass(frozen=True)
|
|
110
|
-
class Choice(Generic[A, B], AST):
|
|
111
|
-
kind: ChoiceKind
|
|
112
|
-
left: Optional[A]
|
|
113
|
-
right: Optional[B]
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
@dataclass(frozen=True)
|
|
117
|
-
class Many(Generic[A], AST):
|
|
118
|
-
value: Tuple[A, ...]
|
|
119
|
-
|
|
120
|
-
@dataclass(frozen=True)
|
|
121
|
-
class Marked(Generic[A], AST):
|
|
122
|
-
name: str
|
|
123
|
-
value: A
|
|
124
|
-
class ThenKind(Enum):
|
|
125
|
-
BOTH = '+'
|
|
126
|
-
LEFT = '//'
|
|
127
|
-
RIGHT = '>>'
|
|
128
|
-
|
|
129
|
-
FlatThen = Tuple[Any, ...]
|
|
130
|
-
MarkedThen = Tuple[Dict[str, Any] | Any, FlatThen]
|
|
131
|
-
|
|
132
|
-
@dataclass(eq=True, frozen=True)
|
|
133
|
-
class Then(Generic[A, B], AST):
|
|
134
|
-
kind: ThenKind
|
|
135
|
-
left: Optional[A]
|
|
136
|
-
right: Optional[B]
|
|
137
|
-
|
|
138
|
-
@runtime_checkable
|
|
139
|
-
class TokenProtocol(Protocol):
|
|
140
|
-
@property
|
|
141
|
-
def token_type(self) -> Enum: ...
|
|
142
|
-
@property
|
|
143
|
-
def text(self) -> str: ...
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
@dataclass(frozen=True)
|
|
147
|
-
class Token:
|
|
148
|
-
token_type: Enum
|
|
149
|
-
text: str
|
|
150
|
-
def __str__(self) -> str:
|
|
151
|
-
return f"{self.token_type.name}({self.text})"
|
|
152
|
-
|
|
153
|
-
def __repr__(self) -> str:
|
|
154
|
-
return self.__str__()
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
@dataclass(frozen=True)
|
|
159
|
-
class TokenSpec:
|
|
160
|
-
token_type: Optional[Enum] = None
|
|
161
|
-
text: Optional[str] = None
|
|
162
|
-
case_sensitive: bool = False
|
|
163
|
-
regex: Optional[re.Pattern[str]] = None
|
|
164
|
-
|
|
165
|
-
def is_valid(self, token: TokenProtocol) -> bool:
|
|
166
|
-
type_match = self.token_type is None or token.token_type == self.token_type
|
|
167
|
-
value_match = self.text is None or (token.text.strip() == self.text.strip() if self.case_sensitive else
|
|
168
|
-
token.text.strip().upper() == self.text.strip().upper())
|
|
169
|
-
value_match = value_match or (self.regex is not None and self.regex.fullmatch(token.text) is not None)
|
|
170
|
-
return type_match and value_match
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
T = TypeVar('T', bound=TokenProtocol)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
ParseResult = Union[
|
|
179
|
-
Then['ParseResult[T]', 'ParseResult[T]'],
|
|
180
|
-
Marked['ParseResult[T]'],
|
|
181
|
-
Choice['ParseResult[T]', 'ParseResult[T]'],
|
|
182
|
-
Many['ParseResult[T]'],
|
|
183
|
-
T,
|
|
184
|
-
]
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|