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.

Files changed (25) hide show
  1. {syncraft-0.1.27 → syncraft-0.1.29}/PKG-INFO +3 -3
  2. {syncraft-0.1.27 → syncraft-0.1.29}/README.md +2 -2
  3. {syncraft-0.1.27 → syncraft-0.1.29}/pyproject.toml +1 -1
  4. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/algebra.py +42 -27
  5. syncraft-0.1.29/syncraft/ast.py +369 -0
  6. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/finder.py +13 -13
  7. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/generator.py +53 -30
  8. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/parser.py +4 -2
  9. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/syntax.py +82 -62
  10. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/PKG-INFO +3 -3
  11. {syncraft-0.1.27 → syncraft-0.1.29}/tests/test_bimap.py +5 -2
  12. syncraft-0.1.27/syncraft/ast.py +0 -187
  13. {syncraft-0.1.27 → syncraft-0.1.29}/LICENSE +0 -0
  14. {syncraft-0.1.27 → syncraft-0.1.29}/setup.cfg +0 -0
  15. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/__init__.py +0 -0
  16. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/constraint.py +0 -0
  17. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/diagnostic.py +0 -0
  18. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/py.typed +0 -0
  19. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft/sqlite3.py +0 -0
  20. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/SOURCES.txt +0 -0
  21. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/dependency_links.txt +0 -0
  22. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/requires.txt +0 -0
  23. {syncraft-0.1.27 → syncraft-0.1.29}/syncraft.egg-info/top_level.txt +0 -0
  24. {syncraft-0.1.27 → syncraft-0.1.29}/tests/test_parse.py +0 -0
  25. {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.27
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.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "syncraft"
3
- version = "0.1.27"
3
+ version = "0.1.29"
4
4
  description = "Parser combinator library"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE"]
@@ -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, S, Choice, Many, ChoiceKind
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[A, S]:
255
- return self.fmap(f).as_(Algebra[A, S]).imap(i)
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, left=value, right=None), state))
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, left=None, right=other_value), other_state))
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: 'Algebra[B, S]') -> 'Algebra[Then[A, B], S]':
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]) -> 'Algebra[Then[A, 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]) -> 'Algebra[Then[A, 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 T, ParseResult, Choice, Many, Then, Marked
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="anything", fixity='infix')
28
+ anything = Syntax(lambda cls: cls.factory('anything')).describe(name="Anything", fixity='infix')
29
29
 
30
- def matches(syntax: Syntax[Any, Any], data: ParseResult[T])-> bool:
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[T]) -> YieldGen[ParseResult[T], None, None]:
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(left=left, right=right):
52
- if left is not None:
53
- yield from find(syntax, left)
54
- if right is not None:
55
- yield from find(syntax, right)
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