syncraft 0.1.28__tar.gz → 0.1.30__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.
Files changed (24) hide show
  1. {syncraft-0.1.28 → syncraft-0.1.30}/PKG-INFO +1 -3
  2. {syncraft-0.1.28 → syncraft-0.1.30}/README.md +0 -2
  3. {syncraft-0.1.28 → syncraft-0.1.30}/pyproject.toml +1 -1
  4. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/algebra.py +4 -1
  5. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/ast.py +135 -144
  6. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/finder.py +13 -13
  7. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/generator.py +29 -18
  8. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/parser.py +4 -2
  9. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/syntax.py +2 -2
  10. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/PKG-INFO +1 -3
  11. {syncraft-0.1.28 → syncraft-0.1.30}/tests/test_bimap.py +34 -48
  12. {syncraft-0.1.28 → syncraft-0.1.30}/tests/test_parse.py +6 -0
  13. {syncraft-0.1.28 → syncraft-0.1.30}/LICENSE +0 -0
  14. {syncraft-0.1.28 → syncraft-0.1.30}/setup.cfg +0 -0
  15. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/__init__.py +0 -0
  16. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/constraint.py +0 -0
  17. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/diagnostic.py +0 -0
  18. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/py.typed +0 -0
  19. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft/sqlite3.py +0 -0
  20. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/SOURCES.txt +0 -0
  21. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/dependency_links.txt +0 -0
  22. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/requires.txt +0 -0
  23. {syncraft-0.1.28 → syncraft-0.1.30}/syncraft.egg-info/top_level.txt +0 -0
  24. {syncraft-0.1.28 → syncraft-0.1.30}/tests/test_until.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.28
3
+ Version: 0.1.30
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -29,8 +29,6 @@ pip install syncraft
29
29
 
30
30
 
31
31
  ## TODO
32
- - [ ] simplify the result of then_left and then_right by bimap the result in syntax.
33
- - [ ] simplify the result of sep_by and between by bimap the result in syntax
34
32
  - [ ] convert to dict/dataclass via bimap in syntax
35
33
  - [ ] define DSL over Variable to construct predicates
36
34
  - [ ] 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.
@@ -14,8 +14,6 @@ pip install syncraft
14
14
 
15
15
 
16
16
  ## TODO
17
- - [ ] simplify the result of then_left and then_right by bimap the result in syntax.
18
- - [ ] simplify the result of sep_by and between by bimap the result in syntax
19
17
  - [ ] convert to dict/dataclass via bimap in syntax
20
18
  - [ ] define DSL over Variable to construct predicates
21
19
  - [ ] 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.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "syncraft"
3
- version = "0.1.28"
3
+ version = "0.1.30"
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
@@ -3,14 +3,14 @@
3
3
  from __future__ import annotations
4
4
  import re
5
5
  from typing import (
6
- Optional, Any, TypeVar, Tuple, runtime_checkable, Self,
7
- Dict, Generic, Callable, Union, Protocol, Type
6
+ Optional, Any, TypeVar, Tuple, runtime_checkable, cast,
7
+ Generic, Callable, Union, Protocol, Type, List, ClassVar
8
8
  )
9
9
 
10
10
 
11
- from dataclasses import dataclass
11
+ from dataclasses import dataclass, replace, is_dataclass, asdict, fields
12
12
  from enum import Enum
13
- from syncraft.constraint import Bindable
13
+
14
14
 
15
15
 
16
16
 
@@ -18,7 +18,9 @@ A = TypeVar('A')
18
18
  B = TypeVar('B')
19
19
  C = TypeVar('C')
20
20
  D = TypeVar('D')
21
- S = TypeVar('S', bound=Bindable)
21
+ S = TypeVar('S')
22
+ S1 = TypeVar('S1')
23
+
22
24
 
23
25
  @dataclass(frozen=True)
24
26
  class Biarrow(Generic[A, B]):
@@ -79,20 +81,6 @@ class Lens(Generic[C, A]):
79
81
  return other.__truediv__(self)
80
82
 
81
83
 
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
84
  @dataclass(frozen=True)
97
85
  class Bimap(Generic[A, B]):
98
86
  run_f: Callable[[A], Tuple[B, Callable[[B], A]]]
@@ -161,14 +149,36 @@ class Bimap(Generic[A, B]):
161
149
  return abc, inv_f
162
150
  return Bimap(when_run)
163
151
 
164
-
165
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)
166
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
+
167
177
 
168
178
  @dataclass(frozen=True)
169
179
  class AST:
170
- def walk(self, r: Reducer[Any, S], s: S) -> S:
171
- return s
180
+ def bimap(self, r: Bimap[Any, Any]=Bimap.identity()) -> Tuple[Any, Callable[[Any], Any]]:
181
+ return r(self)
172
182
 
173
183
  @dataclass(frozen=True)
174
184
  class Nothing(AST):
@@ -182,19 +192,10 @@ class Nothing(AST):
182
192
  class Marked(Generic[A], AST):
183
193
  name: str
184
194
  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
-
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
+
198
199
  class ChoiceKind(Enum):
199
200
  LEFT = 'left'
200
201
  RIGHT = 'right'
@@ -203,49 +204,122 @@ class ChoiceKind(Enum):
203
204
  class Choice(Generic[A, B], AST):
204
205
  kind: Optional[ChoiceKind]
205
206
  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
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
213
 
214
214
  @dataclass(frozen=True)
215
215
  class Many(Generic[A], AST):
216
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)
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))))
221
222
  else:
222
- s = r(item, s)
223
- return s
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
224
227
 
225
228
  class ThenKind(Enum):
226
229
  BOTH = '+'
227
230
  LEFT = '//'
228
231
  RIGHT = '>>'
229
232
 
230
- FlatThen = Tuple[Any, ...]
231
- MarkedThen = Tuple[Dict[str, Any] | Any, FlatThen]
232
-
233
233
  @dataclass(eq=True, frozen=True)
234
234
  class Then(Generic[A, B], AST):
235
235
  kind: ThenKind
236
236
  left: A
237
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)
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
241
247
  else:
242
- s = r(self.left, s)
243
- if isinstance(self.right, AST):
244
- s = self.right.walk(r, s)
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
245
319
  else:
246
- s = r(self.right, s)
247
- return s
320
+ return b, lambda e: replace(self, value=inner_f(e)) # type: ignore
248
321
 
322
+ #########################################################################################################################
249
323
  @dataclass(frozen=True)
250
324
  class Token(AST):
251
325
  token_type: Enum
@@ -255,11 +329,7 @@ class Token(AST):
255
329
 
256
330
  def __repr__(self) -> str:
257
331
  return self.__str__()
258
-
259
- def walk(self, r: Reducer['Token', S], s: S) -> S:
260
- return r(self, s)
261
-
262
-
332
+
263
333
  @runtime_checkable
264
334
  class TokenProtocol(Protocol):
265
335
  @property
@@ -290,89 +360,10 @@ ParseResult = Union[
290
360
  Marked['ParseResult[T]'],
291
361
  Choice['ParseResult[T]', 'ParseResult[T]'],
292
362
  Many['ParseResult[T]'],
363
+ Collect['ParseResult[T]', Any],
293
364
  Nothing,
294
365
  T,
295
366
  ]
296
367
 
297
368
 
298
369
 
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,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
@@ -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, Nothing,
14
+ ParseResult, AST, Token, TokenSpec,
15
+ Nothing, TokenProtocol,
16
16
  Choice, Many, ChoiceKind,
17
17
  Then, ThenKind, Marked
18
18
  )
@@ -24,6 +24,14 @@ import rstr
24
24
  from functools import lru_cache
25
25
  import random
26
26
  from rich import print
27
+ from syncraft.constraint import Bindable
28
+
29
+ T = TypeVar('T', bound=TokenProtocol)
30
+
31
+ S = TypeVar('S', bound=Bindable)
32
+
33
+
34
+
27
35
  B = TypeVar('B')
28
36
 
29
37
 
@@ -63,9 +71,7 @@ class GenState(Bindable, Generic[T]):
63
71
  if isinstance(self.ast, Then) and (self.ast.kind != ThenKind.LEFT or self.restore_pruned):
64
72
  return replace(self, ast=self.ast.right)
65
73
  return replace(self, ast=None)
66
-
67
74
 
68
-
69
75
  def down(self, index: int) -> GenState[T]:
70
76
  if self.ast is None:
71
77
  return self
@@ -76,19 +82,27 @@ class GenState(Bindable, Generic[T]):
76
82
  raise TypeError(f"Invalid AST type({self.ast}) for down traversal")
77
83
 
78
84
  @classmethod
79
- def from_ast(cls, *, ast: Optional[ParseResult[T]], seed: int = 0, restore_pruned:bool=False) -> GenState[T]:
85
+ def from_ast(cls,
86
+ *,
87
+ ast: Optional[ParseResult[T]],
88
+ seed: int = 0,
89
+ restore_pruned:bool=False) -> GenState[T]:
80
90
  return cls(ast=ast, seed=seed, restore_pruned=restore_pruned)
81
91
 
82
-
83
-
84
-
85
-
86
92
  @lru_cache(maxsize=None)
87
- def token_type_from_string(token_type: Optional[TokenType], text: str, case_sensitive:bool)-> TokenType:
93
+ def token_type_from_string(token_type: Optional[TokenType],
94
+ text: str,
95
+ case_sensitive:bool = False)-> TokenType:
88
96
  if not isinstance(token_type, TokenType) or token_type == TokenType.VAR:
89
- for t in TokenType:
90
- if t.value == text or str(t.value).lower() == text.lower():
91
- return t
97
+ if case_sensitive:
98
+ for t in TokenType:
99
+ if t.value == text:
100
+ return t
101
+ else:
102
+ text = text.lower()
103
+ for t in TokenType:
104
+ if t.value == text or str(t.value).lower() == text:
105
+ return t
92
106
  return TokenType.VAR
93
107
  return token_type
94
108
 
@@ -121,13 +135,10 @@ class TokenGen(TokenSpec):
121
135
  else:
122
136
  text = "VALUE"
123
137
 
124
- return Token(token_type= token_type_from_string(self.token_type,
125
- text,
126
- self.case_sensitive),
127
- text=text)
138
+ return Token(token_type=token_type_from_string(self.token_type, text, case_sensitive=False), text=text)
128
139
 
129
140
  @staticmethod
130
- def from_string(string: str)->Token:
141
+ def from_string(string: str) -> Token:
131
142
  return Token(token_type=token_type_from_string(None, string, case_sensitive=False), text=string)
132
143
 
133
144
 
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
  import re
3
3
  from sqlglot import tokenize, TokenType, Parser as GlotParser, exp
4
4
  from typing import (
5
- Optional, List, Any, Tuple,
5
+ Optional, List, Any, Tuple, TypeVar,
6
6
  Generic
7
7
  )
8
8
  from syncraft.algebra import (
@@ -13,9 +13,11 @@ from enum import Enum
13
13
  from functools import reduce
14
14
  from syncraft.syntax import Syntax
15
15
 
16
- from syncraft.ast import Token, TokenSpec, AST, T, Bindable
16
+ from syncraft.ast import Token, TokenSpec, AST, TokenProtocol
17
+ from syncraft.constraint import Bindable
17
18
 
18
19
 
20
+ T = TypeVar('T', bound=TokenProtocol)
19
21
  @dataclass(frozen=True)
20
22
  class ParserState(Bindable, Generic[T]):
21
23
  input: Tuple[T, ...] = field(default_factory=tuple)
@@ -8,7 +8,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, Choice, Many, ChoiceKind, Nothing, Collect, E
11
+ from syncraft.ast import Then, ThenKind, Marked, Choice, Many, ChoiceKind, Nothing, Collect, E, Collector
12
12
  from types import MethodType, FunctionType
13
13
 
14
14
  from rich import print
@@ -249,7 +249,7 @@ class Syntax(Generic[A, S]):
249
249
  ret = self.mark(var.name).map_all(bind_v) if var.name else self.map_all(bind_v)
250
250
  return ret.describe(name=f'bind({var.name})', fixity='postfix', parameter=(self,))
251
251
 
252
- def to(self, f: Type[E])-> Syntax[Collect[A, E], S]:
252
+ def to(self, f: Collector[E])-> Syntax[Collect[A, E], S]:
253
253
  def to_f(v: A) -> Collect[A, E]:
254
254
  if isinstance(v, Collect):
255
255
  return replace(v, collector=f)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.28
3
+ Version: 0.1.30
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -29,8 +29,6 @@ pip install syncraft
29
29
 
30
30
 
31
31
  ## TODO
32
- - [ ] simplify the result of then_left and then_right by bimap the result in syntax.
33
- - [ ] simplify the result of sep_by and between by bimap the result in syntax
34
32
  - [ ] convert to dict/dataclass via bimap in syntax
35
33
  - [ ] define DSL over Variable to construct predicates
36
34
  - [ ] 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.
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from syncraft.ast import Then, ThenKind, Many, Choice, ChoiceKind, Token
3
+ from syncraft.ast import Then, ThenKind, Many, Choice, ChoiceKind, Token, Marked, Nothing
4
4
  from syncraft.algebra import Error
5
5
  from syncraft.parser import literal, parse
6
6
  import syncraft.generator as gen
@@ -19,9 +19,9 @@ def test1_simple_then() -> None:
19
19
  print("---" * 40)
20
20
  print(generated)
21
21
  assert ast == generated
22
- # value, bmap = generated.bimap()
23
- # print(value)
24
- # assert bmap(value) == generated
22
+ value, bmap = generated.bimap()
23
+ print(value)
24
+ assert gen.generate(syntax, bmap(value)) == generated
25
25
 
26
26
 
27
27
  def test2_named_results() -> None:
@@ -35,10 +35,9 @@ def test2_named_results() -> None:
35
35
  print("---" * 40)
36
36
  print(generated)
37
37
  assert ast == generated
38
- # value, bmap = generated.bimap()
39
- # print(value)
40
- # print(bmap(value))
41
- # assert bmap(value) == generated
38
+ value, bmap = generated.bimap()
39
+ assert gen.generate(syntax, bmap(value)) == generated
40
+
42
41
 
43
42
 
44
43
  def test3_many_literals() -> None:
@@ -52,9 +51,8 @@ def test3_many_literals() -> None:
52
51
  print("---" * 40)
53
52
  print(generated)
54
53
  assert ast == generated
55
- # value, bmap = generated.bimap()
56
- # print(value)
57
- # assert bmap(value) == generated
54
+ value, bmap = generated.bimap()
55
+ assert gen.generate(syntax, bmap(value)) == generated
58
56
 
59
57
 
60
58
  def test4_mixed_many_named() -> None:
@@ -69,9 +67,8 @@ def test4_mixed_many_named() -> None:
69
67
  print("---" * 40)
70
68
  print(generated)
71
69
  assert ast == generated
72
- # value, bmap = generated.bimap()
73
- # print(value)
74
- # assert bmap(value) == generated
70
+ value, bmap = generated.bimap()
71
+ assert gen.generate(syntax, bmap(value)) == generated
75
72
 
76
73
 
77
74
  def test5_nested_then_many() -> None:
@@ -85,9 +82,8 @@ def test5_nested_then_many() -> None:
85
82
  print("---" * 40)
86
83
  print(generated)
87
84
  assert ast == generated
88
- # value, bmap = generated.bimap()
89
- # print(value)
90
- # assert bmap(value) == generated
85
+ value, bmap = generated.bimap()
86
+ assert gen.generate(syntax, bmap(value), restore_pruned=True) == generated
91
87
 
92
88
 
93
89
 
@@ -99,8 +95,8 @@ def test_then_flatten():
99
95
  print(ast)
100
96
  generated = gen.generate(syntax, ast)
101
97
  assert ast == generated
102
- # value, bmap = ast.bimap()
103
- # assert bmap(value) == ast
98
+ value, bmap = generated.bimap()
99
+ assert gen.generate(syntax, bmap(value)) == generated
104
100
 
105
101
 
106
102
 
@@ -114,11 +110,8 @@ def test_named_in_then():
114
110
  print(ast)
115
111
  generated = gen.generate(syntax, ast)
116
112
  assert ast == generated
117
- # value, bmap = ast.bimap()
118
- # assert isinstance(value, tuple)
119
- # print(value)
120
- # assert set(x.name for x in value if isinstance(x, Marked)) == {"first", "second", "third"}
121
- # assert bmap(value) == ast
113
+ value, bmap = generated.bimap()
114
+ assert gen.generate(syntax, bmap(value)) == generated
122
115
 
123
116
 
124
117
  def test_named_in_many():
@@ -129,10 +122,8 @@ def test_named_in_many():
129
122
  print(ast)
130
123
  generated = gen.generate(syntax, ast)
131
124
  assert ast == generated
132
- # value, bmap = ast.bimap()
133
- # assert isinstance(value, list)
134
- # assert all(isinstance(v, Marked) for v in value if isinstance(v, Marked))
135
- # assert bmap(value) == ast
125
+ value, bmap = generated.bimap()
126
+ assert gen.generate(syntax, bmap(value)) == generated
136
127
 
137
128
 
138
129
  def test_named_in_or():
@@ -144,10 +135,8 @@ def test_named_in_or():
144
135
  print(ast)
145
136
  generated = gen.generate(syntax, ast)
146
137
  assert ast == generated
147
- # value, bmap = ast.bimap()
148
- # assert isinstance(value, Marked)
149
- # assert value.name == "b"
150
- # assert bmap(value) == ast
138
+ value, bmap = generated.bimap()
139
+ assert gen.generate(syntax, bmap(value)) == generated
151
140
 
152
141
 
153
142
 
@@ -165,8 +154,8 @@ def test_deep_mix():
165
154
  print('---' * 40)
166
155
  print(generated)
167
156
  assert ast == generated
168
- # value, bmap = ast.bimap()
169
- # assert bmap(value) == ast
157
+ value, bmap = generated.bimap()
158
+ assert gen.generate(syntax, bmap(value)) == generated
170
159
 
171
160
 
172
161
  def test_empty_many() -> None:
@@ -183,8 +172,8 @@ def test_backtracking_many() -> None:
183
172
  syntax = (A.many() + B) # must not eat the final "a" needed for B
184
173
  sql = "a a a a b"
185
174
  ast = parse(syntax, sql, dialect="sqlite")
186
- # value, bmap = ast.bimap()
187
- # assert value[-1] == TokenGen.from_string("b")
175
+ value, bmap = ast.bimap()
176
+ assert gen.generate(syntax, bmap(value)) == ast
188
177
 
189
178
  def test_deep_nesting() -> None:
190
179
  A = literal("a")
@@ -209,9 +198,8 @@ def test_named_many() -> None:
209
198
  syntax = A.many()
210
199
  sql = "a a"
211
200
  ast = parse(syntax, sql, dialect="sqlite")
212
- # Expect [Marked("alpha", "a"), Marked("alpha", "a")]
213
- # flattened, _ = ast.bimap()
214
- # assert all(isinstance(x, Marked) for x in flattened)
201
+ value, bmap = ast.bimap()
202
+ assert gen.generate(syntax, bmap(value)) == ast
215
203
 
216
204
 
217
205
  def test_or_named() -> None:
@@ -220,10 +208,8 @@ def test_or_named() -> None:
220
208
  syntax = A | B
221
209
  sql = "b"
222
210
  ast = parse(syntax, sql, dialect="sqlite")
223
- # Either Marked("y", "b") or just "b", depending on your design
224
- # assert isinstance(ast, Choice)
225
- # value, _ = ast.bimap()
226
- # assert value == Marked(name="y", value=TokenGen.from_string("b"))
211
+ value, bmap = ast.bimap()
212
+ assert gen.generate(syntax, bmap(value)) == ast
227
213
 
228
214
 
229
215
  def test_then_associativity() -> None:
@@ -269,11 +255,11 @@ def test_optional():
269
255
  A = literal("a").mark("a")
270
256
  syntax = A.optional()
271
257
  ast1 = parse(syntax, "", dialect="sqlite")
272
- # v1, _ = ast1.bimap()
273
- # assert v1 is None
274
- # ast2 = parse(syntax, "a", dialect="sqlite")
275
- # v2, _ = ast2.bimap()
276
- # assert v2 == Marked(name='a', value=TokenGen.from_string('a'))
258
+ v1, _ = ast1.bimap()
259
+ assert isinstance(v1, Nothing)
260
+ ast2 = parse(syntax, "a", dialect="sqlite")
261
+ v2, _ = ast2.bimap()
262
+ assert v2 == Marked(name='a', value=TokenGen.from_string('a'))
277
263
 
278
264
 
279
265
 
@@ -16,6 +16,8 @@ def test_between()->None:
16
16
  ast:AST = parse(syntax, sql, dialect='sqlite')
17
17
  generated = gen.generate(syntax, ast)
18
18
  assert ast == generated, "Parsed and generated results do not match."
19
+ x, f = generated.bimap()
20
+ assert gen.generate(syntax, f(x)) == ast
19
21
 
20
22
 
21
23
  def test_sep_by()->None:
@@ -24,6 +26,8 @@ def test_sep_by()->None:
24
26
  ast:AST = parse(syntax, sql, dialect='sqlite')
25
27
  generated = gen.generate(syntax, ast)
26
28
  assert ast == generated, "Parsed and generated results do not match."
29
+ x, f = generated.bimap()
30
+ assert gen.generate(syntax, f(x)) == ast
27
31
 
28
32
  def test_many_or()->None:
29
33
  IF = literal("if")
@@ -34,3 +38,5 @@ def test_many_or()->None:
34
38
  ast:AST = parse(syntax, sql, dialect='sqlite')
35
39
  generated = gen.generate(syntax, ast)
36
40
  assert ast == generated, "Parsed and generated results do not match."
41
+ x, f = generated.bimap()
42
+ assert gen.generate(syntax, f(x)) == ast
File without changes
File without changes
File without changes
File without changes
File without changes