syncraft 0.1.25__tar.gz → 0.1.26__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 (26) hide show
  1. {syncraft-0.1.25 → syncraft-0.1.26}/PKG-INFO +3 -3
  2. {syncraft-0.1.25 → syncraft-0.1.26}/README.md +2 -2
  3. {syncraft-0.1.25 → syncraft-0.1.26}/pyproject.toml +1 -1
  4. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft/algebra.py +40 -24
  5. syncraft-0.1.26/syncraft/ast.py +187 -0
  6. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft/constraint.py +12 -4
  7. syncraft-0.1.26/syncraft/finder.py +57 -0
  8. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft/generator.py +92 -94
  9. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft/parser.py +5 -7
  10. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft/syntax.py +41 -39
  11. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft.egg-info/PKG-INFO +3 -3
  12. {syncraft-0.1.25 → syncraft-0.1.26}/tests/test_bimap.py +56 -56
  13. {syncraft-0.1.25 → syncraft-0.1.26}/tests/test_parse.py +3 -3
  14. {syncraft-0.1.25 → syncraft-0.1.26}/tests/test_until.py +2 -2
  15. syncraft-0.1.25/syncraft/ast.py +0 -348
  16. syncraft-0.1.25/syncraft/finder.py +0 -79
  17. {syncraft-0.1.25 → syncraft-0.1.26}/LICENSE +0 -0
  18. {syncraft-0.1.25 → syncraft-0.1.26}/setup.cfg +0 -0
  19. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft/__init__.py +0 -0
  20. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft/diagnostic.py +0 -0
  21. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft/py.typed +0 -0
  22. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft/sqlite3.py +0 -0
  23. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft.egg-info/SOURCES.txt +0 -0
  24. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft.egg-info/dependency_links.txt +0 -0
  25. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft.egg-info/requires.txt +0 -0
  26. {syncraft-0.1.25 → syncraft-0.1.26}/syncraft.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.25
3
+ Version: 0.1.26
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -31,7 +31,7 @@ pip install syncraft
31
31
 
32
32
 
33
33
  ## TODO
34
- - [ ] Add a collect method to AST to collect all named entries and pack them into a dict or a custom dataclass. This method will be called as the last step of my current bimap. So it shares the signature of bimap and can combine with the current bimap
35
- - [ ] Amend all, first, last, and named helper functions to support bimap and named results.
34
+ - [ ] simplify the result of then_left and then_right by bimap the result in syntax.
35
+ - [ ] simplify the result of sep_by and between by bimap the result in syntax
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.
@@ -16,7 +16,7 @@ pip install syncraft
16
16
 
17
17
 
18
18
  ## TODO
19
- - [ ] Add a collect method to AST to collect all named entries and pack them into a dict or a custom dataclass. This method will be called as the last step of my current bimap. So it shares the signature of bimap and can combine with the current bimap
20
- - [ ] Amend all, first, last, and named helper functions to support bimap and named results.
19
+ - [ ] simplify the result of then_left and then_right by bimap the result in syntax.
20
+ - [ ] simplify the result of sep_by and between by bimap the result in syntax
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.25"
3
+ version = "0.1.26"
4
4
  description = "Parser combinator library"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE"]
@@ -8,7 +8,7 @@ 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, ThenResult, ManyResult, OrResult, S
11
+ from syncraft.ast import ThenKind, Then, S, Choice, Many, ChoiceKind
12
12
 
13
13
  A = TypeVar('A') # Result type
14
14
  B = TypeVar('B') # Mapped result type
@@ -235,15 +235,24 @@ class Algebra(ABC, Generic[A, S]):
235
235
  return lazy_self
236
236
 
237
237
 
238
- ######################################################## fundamental combinators ############################################
239
- def map(self, f: Callable[[A], B]) -> Algebra[B, S]:
240
- def map_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
238
+ ######################################################## fundamental combinators ############################################
239
+ def fmap(self, f: Callable[[A], B]) -> Algebra[B, S]:
240
+ def fmap_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
241
241
  parsed = self.run(input, use_cache)
242
242
  if isinstance(parsed, Right):
243
243
  return Right((f(parsed.value[0]), parsed.value[1]))
244
244
  else:
245
245
  return cast(Either[Any, Tuple[B, S]], parsed)
246
- return self.__class__(map_run, name=self.name) # type: ignore
246
+ return self.__class__(fmap_run, name=self.name) # type: ignore
247
+
248
+ def imap(self, f: Callable[[B], A]) -> Algebra[A, S]:
249
+ return self.map_state(lambda s: s.map(f))
250
+
251
+ def map(self, f: Callable[[A], B]) -> Algebra[B, S]:
252
+ return self.fmap(f)
253
+
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)
247
256
 
248
257
  def map_all(self, f: Callable[[Either[Any, Tuple[A, S]]], Either[Any, Tuple[B, S]]])->Algebra[B, S]:
249
258
  def map_all_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
@@ -275,41 +284,48 @@ class Algebra(ABC, Generic[A, S]):
275
284
  return self.__class__(flat_map_run, name=self.name) # type: ignore
276
285
 
277
286
 
278
- def or_else(self: Algebra[A, S], other: Algebra[B, S]) -> Algebra[OrResult[A | B], S]:
279
- def or_else_run(input: S, use_cache:bool) -> Either[Any, Tuple[OrResult[A | B], S]]:
287
+ def or_else(self: Algebra[A, S], other: Algebra[B, S]) -> Algebra[Choice[A, B], S]:
288
+ def or_else_run(input: S, use_cache:bool) -> Either[Any, Tuple[Choice[A, B], S]]:
280
289
  match self.run(input, use_cache):
281
290
  case Right((value, state)):
282
- return Right((OrResult(value=value), state))
291
+ return Right((Choice(kind=ChoiceKind.LEFT, left=value, right=None), state))
283
292
  case Left(err):
284
293
  if isinstance(err, Error) and err.committed:
285
294
  return Left(err)
286
295
  match other.run(input, use_cache):
287
296
  case Right((other_value, other_state)):
288
- return Right((OrResult(value=other_value), other_state))
297
+ return Right((Choice(kind=ChoiceKind.RIGHT, left=None, right=other_value), other_state))
289
298
  case Left(other_err):
290
299
  return Left(other_err)
291
300
  raise TypeError(f"Unexpected result type from {other}")
292
301
  raise TypeError(f"Unexpected result type from {self}")
293
302
  return self.__class__(or_else_run, name=f'{self.name} | {other.name}') # type: ignore
294
303
 
295
- def then_both(self, other: 'Algebra[B, S]') -> 'Algebra[ThenResult[A, B], S]':
296
- def then_both_f(a: A) -> Algebra[ThenResult[A, B], S]:
297
- def combine(b: B) -> ThenResult[A, B]:
298
- return ThenResult(left=a, right=b, kind=ThenKind.BOTH)
299
- return other.map(combine)
304
+ def then_both(self, other: 'Algebra[B, S]') -> 'Algebra[Then[A, B], S]':
305
+ def then_both_f(a: A) -> Algebra[Then[A, B], S]:
306
+ def combine(b: B) -> Then[A, B]:
307
+ return Then(left=a, right=b, kind=ThenKind.BOTH)
308
+ return other.fmap(combine)
300
309
  return self.flat_map(then_both_f).named(f'{self.name} + {other.name}')
301
-
302
- def then_left(self, other: Algebra[B, S]) -> Algebra[ThenResult[A, B], S]:
303
- return self.then_both(other).map(lambda b: replace(b, kind = ThenKind.LEFT)).named(f'{self.name} // {other.name}')
304
-
305
- def then_right(self, other: Algebra[B, S]) -> Algebra[ThenResult[A, B], S]:
306
- return self.then_both(other).map(lambda b: replace(b, kind=ThenKind.RIGHT)).named(f'{self.name} >> {other.name}')
307
-
308
310
 
309
- def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[A], S]:
311
+ def then_left(self, other: Algebra[B, S]) -> 'Algebra[Then[A, B], S]':
312
+ def then_left_f(a: A) -> Algebra[Then[A, B], S]:
313
+ def combine(b: B) -> Then[A, B]:
314
+ return Then(left=a, right=b, kind=ThenKind.LEFT)
315
+ return other.fmap(combine)
316
+ return self.flat_map(then_left_f).named(f'{self.name} // {other.name}')
317
+
318
+ def then_right(self, other: Algebra[B, S]) -> 'Algebra[Then[A, B], S]':
319
+ def then_right_f(a: A) -> Algebra[Then[A, B], S]:
320
+ def combine(b: B) -> Then[A, B]:
321
+ return Then(left=a, right=b, kind=ThenKind.RIGHT)
322
+ return other.fmap(combine)
323
+ return self.flat_map(then_right_f).named(f'{self.name} >> {other.name}')
324
+
325
+ def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[Many[A], S]:
310
326
  assert at_least > 0, "at_least must be greater than 0"
311
327
  assert at_most is None or at_least <= at_most, "at_least must be less than or equal to at_most"
312
- def many_run(input: S, use_cache:bool) -> Either[Any, Tuple[ManyResult[A], S]]:
328
+ def many_run(input: S, use_cache:bool) -> Either[Any, Tuple[Many[A], S]]:
313
329
  ret: List[A] = []
314
330
  current_input = input
315
331
  while True:
@@ -333,7 +349,7 @@ class Algebra(ABC, Generic[A, S]):
333
349
  this=self,
334
350
  state=current_input
335
351
  ))
336
- return Right((ManyResult(value=tuple(ret)), current_input))
352
+ return Right((Many(value=tuple(ret)), current_input))
337
353
  return self.__class__(many_run, name=f'*({self.name})') # type: ignore
338
354
 
339
355
 
@@ -0,0 +1,187 @@
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
+
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Callable, Generic, Tuple, TypeVar, Optional, Any, Protocol
2
+ from typing import Callable, Generic, Tuple, TypeVar, Optional, Any, Self
3
3
  from enum import Enum
4
4
  from dataclasses import dataclass, field, replace
5
5
  import collections.abc
@@ -109,9 +109,17 @@ class Binding:
109
109
  ret[var].append(node)
110
110
  return FrozenDict({k: tuple(vs) for k, vs in ret.items()})
111
111
 
112
- class Bindable(Protocol):
113
- binding: Binding
114
- def bind(self, var: Variable, node: Any) -> Any: ...
112
+
113
+ A = TypeVar('A')
114
+ @dataclass(frozen=True)
115
+ class Bindable:
116
+ binding: Binding = field(default_factory=Binding)
117
+
118
+ def map(self, f: Callable[[Any], Any])->Self:
119
+ return self
120
+
121
+ def bind(self, var: Variable, node:Any)->Self:
122
+ return replace(self, binding=self.binding.bind(var, node))
115
123
 
116
124
 
117
125
  class Quantifier(Enum):
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import (
4
+ Any, Tuple, Optional, Generator as YieldGen
5
+ )
6
+ from dataclasses import dataclass, replace
7
+ from syncraft.algebra import (
8
+ Algebra, Either, Right,
9
+ )
10
+ from syncraft.ast import T, ParseResult, Choice, Many, Then, Marked
11
+
12
+ from syncraft.generator import GenState, Generator
13
+ from sqlglot import TokenType
14
+ from syncraft.syntax import Syntax
15
+ import re
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class Finder(Generator[T]):
20
+ @classmethod
21
+ def anything(cls)->Algebra[Any, GenState[T]]:
22
+ def anything_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[Any, GenState[T]]]:
23
+ return Right((input.ast, input))
24
+ return cls(anything_run, name=cls.__name__ + '.anything()')
25
+
26
+
27
+
28
+ anything = Syntax(lambda cls: cls.factory('anything')).describe(name="anything", fixity='infix')
29
+
30
+ def matches(syntax: Syntax[Any, Any], data: ParseResult[T])-> bool:
31
+ gen = syntax(Finder)
32
+ state = GenState.from_ast(ast = data, restore_pruned=True)
33
+ result = gen.run(state, use_cache=True)
34
+ return isinstance(result, Right)
35
+
36
+
37
+ def find(syntax: Syntax[Any, Any], data: ParseResult[T]) -> YieldGen[ParseResult[T], None, None]:
38
+ if matches(syntax, data):
39
+ yield data
40
+ match data:
41
+ case Then(left=left, right=right):
42
+ if left is not None:
43
+ yield from find(syntax, left)
44
+ if right is not None:
45
+ yield from find(syntax, right)
46
+ case Many(value = value):
47
+ for e in value:
48
+ yield from find(syntax, e)
49
+ case Marked(value=value):
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)
56
+ case _:
57
+ pass