syncraft 0.1.18__tar.gz → 0.1.20__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 (24) hide show
  1. {syncraft-0.1.18 → syncraft-0.1.20}/PKG-INFO +5 -4
  2. syncraft-0.1.20/README.md +22 -0
  3. {syncraft-0.1.18 → syncraft-0.1.20}/pyproject.toml +1 -1
  4. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/algebra.py +145 -227
  5. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/ast.py +13 -10
  6. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/diagnostic.py +2 -2
  7. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/generator.py +33 -5
  8. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/parser.py +21 -27
  9. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/sqlite3.py +7 -7
  10. syncraft-0.1.18/syncraft/dsl.py → syncraft-0.1.20/syncraft/syntax.py +58 -58
  11. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft.egg-info/PKG-INFO +5 -4
  12. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft.egg-info/SOURCES.txt +1 -1
  13. {syncraft-0.1.18 → syncraft-0.1.20}/tests/test_bimap.py +167 -48
  14. {syncraft-0.1.18 → syncraft-0.1.20}/tests/test_parse.py +7 -7
  15. {syncraft-0.1.18 → syncraft-0.1.20}/tests/test_until.py +4 -4
  16. syncraft-0.1.18/README.md +0 -21
  17. {syncraft-0.1.18 → syncraft-0.1.20}/LICENSE +0 -0
  18. {syncraft-0.1.18 → syncraft-0.1.20}/setup.cfg +0 -0
  19. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/__init__.py +0 -0
  20. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/cmd.py +0 -0
  21. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/py.typed +0 -0
  22. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft.egg-info/dependency_links.txt +0 -0
  23. {syncraft-0.1.18 → syncraft-0.1.20}/syncraft.egg-info/requires.txt +0 -0
  24. {syncraft-0.1.18 → syncraft-0.1.20}/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.18
3
+ Version: 0.1.20
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -31,6 +31,7 @@ pip install syncraft
31
31
 
32
32
 
33
33
  ## TODO
34
- - [ ] Test Walker.get/set for bidirectional mapping between source code and data class
35
- - [ ] Annotate sqlite3 grammar with named nodes and data classes
36
- - [ ]
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.
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
+ - [ ] Make the library as fast as possible and feasible.
@@ -0,0 +1,22 @@
1
+ # Syncraft
2
+
3
+ Syncraft is a parser/generator combinator library with full round-trip support:
4
+
5
+ - Parse source code into AST or dataclasses
6
+ - Generate source code from dataclasses
7
+ - Bidirectional transformations via lenses
8
+ - Convenience combinators: `all`, `first`, `last`, `named`
9
+ - SQLite syntax support included
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install syncraft
15
+ ```
16
+
17
+
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.
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
+ - [ ] Make the library as fast as possible and feasible.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "syncraft"
3
- version = "0.1.18"
3
+ version = "0.1.20"
4
4
  description = "Parser combinator library"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE"]
@@ -1,163 +1,120 @@
1
- """
2
- We want to parse a token stream into an AST, and then generate a new token stream from that AST.
3
- The generation should be a dual to the parsing. By 'dual' we mean that the generation algebra should be
4
- as close as possible to the parsing algebra. The closest algebra to the parsing algebra is the parsing
5
- algebra itself.
6
-
7
- Given:
8
- AST = Syntax(Parser)(ParserState([Token, ...]))
9
- AST =?= Syntax(Parser)(GenState(AST))
10
-
11
- where =?= means the LHS and RHS induce the same text output, e.g. the same token stream
12
- inspite of the token metadata, token types, and/or potentially different structure of the AST.
13
-
14
- With the above setting, Generator as a dual to Parser, can reuse most of the parsing combinator, the
15
- change needed is to introduce randomness in the generation process, e.g. to generate a random variable name, etc.
16
-
17
- [Token, ...] == Syntax(Generator)(GenState(AST))
18
-
19
- """
20
-
21
-
22
1
  from __future__ import annotations
23
-
24
2
  from typing import (
25
3
  Optional, List, Any, TypeVar, Generic, Callable, Tuple, cast,
26
- Dict, Type, ClassVar, Hashable,
27
- Mapping, Iterator
4
+ Dict, Type, ClassVar, Hashable
28
5
  )
29
6
 
30
7
  import traceback
31
- from dataclasses import dataclass, fields, replace, field
32
- from functools import cached_property
8
+ from dataclasses import dataclass, replace
33
9
  from weakref import WeakKeyDictionary
34
- from abc import ABC, abstractmethod
10
+ from abc import ABC
35
11
  from enum import Enum
12
+ from functools import reduce
36
13
 
37
14
 
38
-
39
-
40
- class Insptectable(ABC):
41
- @abstractmethod
42
- def to_string(self, interested: Callable[[Any], bool]) -> Optional[str]:
43
- raise NotImplementedError("Subclasses must implement to_string")
44
-
45
- @cached_property
46
- def _string(self)->Optional[str]:
47
- return self.to_string(lambda _: True)
48
-
49
- def __repr__(self) -> str:
50
- return self._string or self.__class__.__name__
51
- def __str__(self) -> str:
52
- return self._string or self.__class__.__name__
53
-
54
-
55
- A = TypeVar('A') # Result type
56
- B = TypeVar('B') # Result type for mapping
57
-
58
- S = TypeVar('S') # State type for the Algebra
59
-
60
-
61
-
62
- class FrozenDict(Generic[A]):
63
- def __init__(self, items: Mapping[str, A]):
64
- for k, v in items.items():
65
- if not isinstance(k, Hashable) or not isinstance(v, Hashable):
66
- raise TypeError(f"Metadata key or value not hashable: {k} = {v}")
67
- self._items = tuple(sorted(items.items()))
68
-
69
- def __getitem__(self, key: str) -> A:
70
- return dict(self._items)[key]
71
-
72
- def __contains__(self, key: str) -> bool:
73
- return key in dict(self._items)
74
-
75
- def items(self) -> Iterator[tuple[str, A]]:
76
- return iter(self._items)
77
-
78
- def to_dict(self) -> dict[str, A]:
79
- return dict(self._items)
80
-
81
- def __hash__(self) -> int:
82
- return hash(self._items)
83
-
84
- def __eq__(self, other: object) -> bool:
85
- return isinstance(other, FrozenDict) and self._items == other._items
86
-
87
- def __repr__(self) -> str:
88
- return f"FrozenDict({dict(self._items)})"
89
-
15
+ A = TypeVar('A')
16
+ B = TypeVar('B')
17
+ C = TypeVar('C')
18
+ S = TypeVar('S')
90
19
 
91
20
 
92
21
  @dataclass(frozen=True)
93
- class Lens(Generic[S, A]):
94
- get: Callable[[S], A]
95
- set: Callable[[S, A], S]
96
-
97
- def modify(self, source: S, f: Callable[[A], A]) -> S:
98
- return self.set(source, f(self.get(source)))
99
-
100
- def bimap(self, ff: Callable[[A], B], bf: Callable[[B], A]) -> Lens[S, B]:
101
- def getf(data: S) -> B:
102
- return ff(self.get(data))
103
-
104
- def setf(data: S, value: B) -> S:
105
- return self.set(data, bf(value))
106
-
107
- return Lens(get=getf, set=setf)
108
-
109
- def __truediv__(self, other: Lens[A, B]) -> Lens[S, B]:
110
- def get_composed(obj: S) -> B:
111
- return other.get(self.get(obj))
112
- def set_composed(obj: S, value: B) -> S:
113
- return self.set(obj, other.set(self.get(obj), value))
114
- return Lens(get=get_composed, set=set_composed)
22
+ class Bimap(Generic[S, A, B]):
23
+ forward: Callable[[S, A], Tuple[S, B]]
24
+ inverse: Callable[[S, B], Tuple[S, A]]
25
+ def __rshift__(self, other: Bimap[S, B, C]) -> Bimap[S, A, C]:
26
+ def fwd(s: S, a: A) -> Tuple[S, C]:
27
+ s1, b = self.forward(s, a)
28
+ return other.forward(s1, b)
29
+ def inv(s: S, c: C) -> Tuple[S, A]:
30
+ s1, b = other.inverse(s, c)
31
+ return self.inverse(s1, b)
32
+ return Bimap(
33
+ forward=fwd,
34
+ inverse=inv
35
+ )
36
+ @staticmethod
37
+ def identity()->Bimap[S, A, A]:
38
+ return Bimap(
39
+ forward=lambda s, x: (s, x),
40
+ inverse=lambda s, y: (s, y)
41
+ )
42
+ @staticmethod
43
+ def variable(value: A)->Bimap[S, A, A]:
44
+ return Bimap(
45
+ forward=lambda s, _: (s, value),
46
+ inverse=lambda s, y: (s, y)
47
+ )
48
+ @staticmethod
49
+ def const(state: S, value: A)->Bimap[Any, A, A]:
50
+ return Bimap(
51
+ forward=lambda s, _: (state, value),
52
+ inverse=lambda s, y: (state, value)
53
+ )
54
+ @staticmethod
55
+ def combine(*biarrows: Bimap[Any, Any, Any]) -> Bimap[Any, Any, Any]:
56
+ return reduce(lambda a, b: a >> b, biarrows, Bimap.identity())
57
+
115
58
 
116
- def __rtruediv__(self, other: Lens[B, S])->Lens[B, A]:
117
- return other.__truediv__(self)
118
59
 
119
60
  class StructuralResult:
120
- def bimap(self, ctx: Any)->Tuple[Any, Callable[[Any], StructuralResult]]:
121
- return (self, lambda x: self)
122
-
61
+ def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, Any, Any]:
62
+ return Bimap.identity()
123
63
 
124
64
  @dataclass(frozen=True)
125
65
  class NamedResult(Generic[A], StructuralResult):
126
66
  name: str
127
67
  value: A
128
- def bimap(self, ctx: Any)->Tuple[NamedResult[Any], Callable[[NamedResult[Any]], StructuralResult]]:
129
- value, backward = self.value.bimap(ctx) if isinstance(self.value, StructuralResult) else (self.value, lambda x: x)
130
- def named_back(data: Any)->NamedResult[Any]:
131
- v = backward(data)
132
- if isinstance(v, NamedResult):
133
- return replace(v, name=self.name)
134
- else:
135
- return NamedResult(name=self.name, value=v)
136
- return NamedResult(self.name, value), named_back
137
-
68
+ def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, NamedResult[A], NamedResult[Any]]:
69
+ inner_b = self.value.bimap(arr) if isinstance(self.value, StructuralResult) else arr
70
+ def fwd(s: S, a: NamedResult[A])-> Tuple[S, NamedResult[Any]]:
71
+ assert a == self, f"Expected {self}, got {a}"
72
+ inner_s, inner_v = inner_b.forward(s, a.value)
73
+ return (inner_s, replace(a, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, inner_v)
74
+
75
+ def inv(s: S, a: NamedResult[Any]) -> Tuple[S, NamedResult[A]]:
76
+ assert isinstance(a, NamedResult), f"Expected NamedResult, got {type(a)}"
77
+ inner_s, inner_v = inner_b.inverse(s, a.value)
78
+ return (inner_s, replace(self, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, replace(self, value=inner_v.value))
79
+
80
+ return Bimap(
81
+ forward=fwd,
82
+ inverse=inv
83
+ )
138
84
  @dataclass(eq=True, frozen=True)
139
85
  class ManyResult(Generic[A], StructuralResult):
140
86
  value: Tuple[A, ...]
141
- def bimap(self, ctx: Any)->Tuple[List[Any], Callable[[List[Any]], StructuralResult]]:
142
- transformed = [v.bimap(ctx) if isinstance(v, StructuralResult) else (v, lambda x: x) for v in self.value]
143
- backmaps = [b for (_, b) in transformed]
144
- ret = [a for (a, _) in transformed]
145
- def backward(data: List[Any]) -> StructuralResult:
146
- if len(data) != len(transformed):
147
- raise ValueError("Incompatible data length")
148
- return ManyResult(value=tuple([backmaps[i](x) for i, x in enumerate(data)]))
149
- return ret, lambda data: backward(data)
150
-
151
-
152
-
87
+ def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, ManyResult[A], List[A]]:
88
+ inner_b = [v.bimap(arr) if isinstance(v, StructuralResult) else arr for v in self.value]
89
+ def fwd(s: Any, a: ManyResult[A]) -> Tuple[Any, List[A]]:
90
+ assert a == self, f"Expected {self}, got {a}"
91
+ return s, [inner_b[i].forward(s, v)[1] for i, v in enumerate(a.value)]
92
+
93
+ def inv(s: Any, a: List[A]) -> Tuple[Any, ManyResult[A]]:
94
+ assert isinstance(a, list), f"Expected list, got {type(a)}"
95
+ assert len(a) == len(inner_b), f"Expected {len(inner_b)} elements, got {len(a)}"
96
+ return s, ManyResult(value=tuple(inner_b[i].inverse(s, v)[1] for i, v in enumerate(a)))
97
+ return Bimap(
98
+ forward=fwd,
99
+ inverse=inv
100
+ )
153
101
  @dataclass(eq=True, frozen=True)
154
102
  class OrResult(Generic[A], StructuralResult):
155
103
  value: A
156
- def bimap(self, ctx: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
157
- value, backward = self.value.bimap(ctx) if isinstance(self.value, StructuralResult) else (self.value, lambda x: x)
158
- return value, lambda data: OrResult(value=backward(data))
159
-
160
-
104
+ def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, OrResult[A], Any]:
105
+ inner_b = self.value.bimap(arr) if isinstance(self.value, StructuralResult) else arr
106
+ def fwd(s: Any, a: OrResult[A]) -> Tuple[Any, Any]:
107
+ assert a == self, f"Expected {self}, got {a}"
108
+ return inner_b.forward(s, a.value)
109
+
110
+ def inv(s: Any, a: Any) -> Tuple[Any, OrResult[A]]:
111
+ inner_s, inner_v = inner_b.inverse(s, a)
112
+ return inner_s, OrResult(value=inner_v)
113
+
114
+ return Bimap(
115
+ forward=fwd,
116
+ inverse=inv
117
+ )
161
118
  class ThenKind(Enum):
162
119
  BOTH = '+'
163
120
  LEFT = '//'
@@ -168,49 +125,60 @@ class ThenResult(Generic[A, B], StructuralResult):
168
125
  kind: ThenKind
169
126
  left: A
170
127
  right: B
171
- def bimap(self, ctx: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
172
- def branch(b: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
173
- return b.bimap(ctx) if isinstance(b, StructuralResult) else (b, lambda x: x)
174
- match self.kind:
175
- case ThenKind.BOTH:
176
- left_value, left_bmap = branch(self.left)
177
- right_value, right_bmap = branch(self.right)
178
- def backward(x: Tuple[Any, Any]) -> StructuralResult:
179
- return ThenResult(self.kind, left_bmap(x[0]), right_bmap(x[1]))
180
- x, y = ThenResult.flat((left_value, right_value))
181
- return x, lambda data: backward(y(data))
182
- case ThenKind.LEFT:
183
- left_value, left_bmap = branch(self.left)
184
- return left_value, lambda data: ThenResult(self.kind, left_bmap(data), self.right)
185
- case ThenKind.RIGHT:
186
- right_value, right_bmap = branch(self.right)
187
- return right_value, lambda data: ThenResult(self.kind, self.left, right_bmap(data))
188
- @staticmethod
189
- def flat(array: Tuple[Any, Any]) -> Tuple[Tuple[Any, ...], Callable[[Tuple[Any, ...]], Tuple[Any, Any]]]:
190
- index: Dict[int, int] = {}
191
- ret: List[Any] = []
192
- for e in array:
193
- if isinstance(e, tuple):
194
- index[len(ret)] = len(e)
195
- ret.extend(e)
196
- else:
197
- ret.append(e)
198
- def backward(data: Tuple[Any, ...]) -> Tuple[Any, Any]:
199
- tmp: List[Any] = []
200
- skip: int = 0
201
- for i, e in enumerate(data):
202
- if skip <= 0:
203
- if i in index:
204
- tmp.append(tuple(data[i:i + index[i]]))
205
- skip = index[i] - 1
206
- else:
207
- tmp.append(e)
208
- else:
209
- skip -= 1
210
- return tuple(tmp)
211
- return tuple(ret), backward
212
-
213
-
128
+ def arity(self)->int:
129
+ if self.kind == ThenKind.LEFT:
130
+ return self.left.arity() if isinstance(self.left, ThenResult) else 1
131
+ elif self.kind == ThenKind.RIGHT:
132
+ return self.right.arity() if isinstance(self.right, ThenResult) else 1
133
+ elif self.kind == ThenKind.BOTH:
134
+ left_arity = self.left.arity() if isinstance(self.left, ThenResult) else 1
135
+ right_arity = self.right.arity() if isinstance(self.right, ThenResult) else 1
136
+ return left_arity + right_arity
137
+ else:
138
+ return 1
139
+
140
+ def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, ThenResult[A, B], Tuple[Any, ...] | Any]:
141
+ kind = self.kind
142
+ lb = self.left.bimap(arr) if isinstance(self.left, StructuralResult) else arr
143
+ rb = self.right.bimap(arr) if isinstance(self.right, StructuralResult) else arr
144
+ left_size = self.left.arity() if isinstance(self.left, ThenResult) else 1
145
+ right_size = self.right.arity() if isinstance(self.right, ThenResult) else 1
146
+ def fwd(s : S, a : ThenResult[A, B]) -> Tuple[S, Tuple[Any, ...] | Any]:
147
+ assert a == self, f"Expected {self}, got {a}"
148
+ match kind:
149
+ case ThenKind.LEFT:
150
+ return lb.forward(s, a.left)
151
+ case ThenKind.RIGHT:
152
+ return rb.forward(s, a.right)
153
+ case ThenKind.BOTH:
154
+ s1, left_v = lb.forward(s, a.left)
155
+ s2, right_v = rb.forward(s1, a.right)
156
+ left_v = (left_v,) if not isinstance(a.left, ThenResult) else left_v
157
+ right_v = (right_v,) if not isinstance(a.right, ThenResult) else right_v
158
+ return s2, left_v + right_v
159
+
160
+ def inv(s: S, b: Tuple[Any, ...] | Any) -> Tuple[S, ThenResult[A, B]]:
161
+ match kind:
162
+ case ThenKind.LEFT:
163
+ s1, lv = lb.inverse(s, b)
164
+ return s1, replace(self, left=lv)
165
+ case ThenKind.RIGHT:
166
+ s1, rv = rb.inverse(s, b)
167
+ return s1, replace(self, right=rv)
168
+ case ThenKind.BOTH:
169
+ lraw = b[:left_size]
170
+ rraw = b[left_size:left_size + right_size]
171
+ lraw = lraw[0] if left_size == 1 else lraw
172
+ rraw = rraw[0] if right_size == 1 else rraw
173
+ s1, lv = lb.inverse(s, lraw)
174
+ s2, rv = rb.inverse(s1, rraw)
175
+ return s2, replace(self, left=lv, right=rv)
176
+
177
+ return Bimap(
178
+ forward=fwd,
179
+ inverse=inv
180
+ )
181
+
214
182
  InProgress = object() # Marker for in-progress state, used to prevent re-entrance in recursive calls
215
183
  L = TypeVar('L') # Left type for combined results
216
184
  R = TypeVar('R') # Right type for combined results
@@ -233,7 +201,7 @@ class Right(Either[L, R]):
233
201
 
234
202
 
235
203
  @dataclass(frozen=True)
236
- class Error(Insptectable):
204
+ class Error:
237
205
  this: Any
238
206
  message: Optional[str] = None
239
207
  error: Optional[Any] = None
@@ -254,59 +222,9 @@ class Error(Insptectable):
254
222
  state=state,
255
223
  previous=self
256
224
  )
257
-
258
-
259
225
 
260
226
 
261
- def to_list(self, interested: Callable[[Any], bool]) -> List[Dict[str, str]]:
262
-
263
- def to_dict() -> Dict[str, str]:
264
- data: Dict[str, str] = {}
265
- for f in fields(self):
266
- value = getattr(self, f.name)
267
- if isinstance(value, Error):
268
- # self.previous
269
- pass
270
- elif isinstance(value, Insptectable):
271
- # self.this
272
- def inst(x: Any) -> bool:
273
- return x in self.algebras
274
- s = value.to_string(inst)
275
- data[f.name] = s if s is not None else repr(value)
276
- elif value is not None:
277
- # self.committed, self.message, self.expect, self.exception
278
- data[f.name] = repr(value)
279
- return data
280
- ret = []
281
- tmp : None | Error = self
282
- while tmp is not None:
283
- ret.append(to_dict())
284
- tmp = tmp.previous
285
- return ret
286
-
287
- def find(self, predicate: Callable[[Error], bool]) -> Optional[Error]:
288
- if predicate(self):
289
- return self
290
- if self.previous is not None:
291
- return self.previous.find(predicate)
292
- return None
293
-
294
- def to_string(self, interested: Callable[[Any], bool])->str:
295
- lst = self.to_list(interested)
296
- root, leaf = lst[0], lst[-1]
297
- root_fields = ',\n '.join([f"{key}: {value}" for key, value in root.items() if value is not None])
298
- leaf_fields = ',\n '.join([f"{key}: {value}" for key, value in leaf.items() if value is not None])
299
-
300
- return f"{self.__class__.__name__}: ROOT\n"\
301
- f" {root_fields}\n"\
302
- f"\u25cf \u25cf \u25cf LEAF\n"\
303
- f" {leaf_fields}\n"
304
227
 
305
-
306
- @cached_property
307
- def algebras(self) -> List[Any]:
308
- return [self.this] + (self.previous.algebras if self.previous is not None else [])
309
-
310
228
  @dataclass(frozen=True)
311
229
  class Algebra(ABC, Generic[A, S]):
312
230
  ######################################################## shared among all subclasses ########################################################
@@ -3,12 +3,11 @@
3
3
  from __future__ import annotations
4
4
  import re
5
5
  from typing import (
6
- Optional, Any, TypeVar, Tuple, runtime_checkable, Dict,
6
+ Optional, Any, TypeVar, Tuple, runtime_checkable,
7
7
  Protocol, Generic, Callable, Union, cast
8
8
  )
9
9
  from syncraft.algebra import (
10
- OrResult,ThenResult, ManyResult, ThenKind,NamedResult, StructuralResult,
11
- Lens
10
+ OrResult,ThenResult, ManyResult, ThenKind,NamedResult, StructuralResult
12
11
  )
13
12
  from dataclasses import dataclass, replace, is_dataclass, asdict
14
13
  from enum import Enum
@@ -67,13 +66,17 @@ class AST(Generic[T]):
67
66
  pruned: bool = False
68
67
  parent: Optional[AST[T]] = None
69
68
 
70
-
71
- def bimap(self, ctx: Any) -> Tuple[Any, Callable[[Any], AST[T]]]:
72
- value, backward = self.focus.bimap(ctx) if isinstance(self.focus, StructuralResult) else (self.focus, lambda x: x)
73
- def back2ast(data: Any) -> AST[T]:
74
- return replace(self, focus=backward(data)) # type: ignore
75
- return value, back2ast
76
-
69
+ def bimap(self)->Tuple[Any, Callable[[Any], AST[T]]]:
70
+ if isinstance(self.focus, StructuralResult):
71
+ b = self.focus.bimap()
72
+ s, v = b.forward(None, self.focus)
73
+ def inverse(data: Any) -> AST[T]:
74
+ s1, v1 = b.inverse(None, data)
75
+ return replace(self, focus=v1)
76
+ return v, inverse
77
+ else:
78
+ return self.focus, lambda x: replace(self, focus=x)
79
+
77
80
  def wrapper(self)-> Callable[[Any], Any]:
78
81
  if isinstance(self.focus, NamedResult):
79
82
  focus = cast(NamedResult[Any], self.focus)
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
  from rich import print
3
3
  from rich.table import Table as RichTable
4
4
  from typing import Tuple, Any, Set
5
- from syncraft.dsl import DSL
5
+ from syncraft.syntax import Syntax
6
6
  from syncraft.algebra import Left, Right, Error, Either, Algebra
7
7
 
8
8
  from syncraft.parser import ParserState, Token
@@ -44,7 +44,7 @@ def rich_debug(this: Algebra[Any, ParserState[Any]],
44
44
  return prefix + value.sql()
45
45
  elif isinstance(value, Token):
46
46
  return prefix + f"{value.token_type.name}({value.text})"
47
- elif isinstance(value, (Error, ParserState, DSL)):
47
+ elif isinstance(value, (Error, ParserState, Syntax)):
48
48
  return prefix + (value._string or 'N/A')
49
49
  else:
50
50
  return prefix + str(value)
@@ -2,15 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  from typing import (
4
4
  Any, TypeVar, Tuple, Optional, Callable, Generic, Union,
5
- List
5
+ List, Generator as YieldGen
6
6
  )
7
7
  from functools import cached_property
8
8
  from dataclasses import dataclass, replace
9
9
  from syncraft.algebra import (
10
- Algebra, ThenResult, Either, Left, Right, Error, Insptectable,
11
- OrResult, ManyResult
10
+ Algebra, ThenResult, Either, Left, Right, Error,
11
+ OrResult, ManyResult, NamedResult
12
12
  )
13
+
13
14
  from syncraft.ast import TokenProtocol, ParseResult, AST, Token, TokenSpec
15
+ from syncraft.syntax import Syntax
14
16
  from sqlglot import TokenType
15
17
  import re
16
18
  import rstr
@@ -29,7 +31,7 @@ GenResult = Union[
29
31
  ]
30
32
 
31
33
  @dataclass(frozen=True)
32
- class GenState(Generic[T], Insptectable):
34
+ class GenState(Generic[T]):
33
35
  ast: Optional[AST[T]]
34
36
  seed: int
35
37
 
@@ -270,7 +272,8 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
270
272
 
271
273
 
272
274
 
273
- def generate(gen: Algebra[Any, Any], data: Optional[AST[Any]] = None, seed: int = 0) -> AST[Any] | Any:
275
+ def generate(syntax: Syntax[Any, Any], data: Optional[AST[Any]] = None, seed: int = 0) -> AST[Any] | Any:
276
+ gen = syntax(Generator)
274
277
  state = GenState.from_ast(data, seed)
275
278
  result = gen.run(state, use_cache=False)
276
279
  if isinstance(result, Right):
@@ -278,3 +281,28 @@ def generate(gen: Algebra[Any, Any], data: Optional[AST[Any]] = None, seed: int
278
281
  assert isinstance(result, Left), "Generator must return Either[Any, Tuple[Any, Any]]"
279
282
  return result.value
280
283
 
284
+
285
+ def matches(syntax: Syntax[Any, Any], data: AST[Any])-> bool:
286
+ gen = syntax(Generator)
287
+ state = GenState.from_ast(data)
288
+ result = gen.run(state, use_cache=True)
289
+ return isinstance(result, Right)
290
+
291
+
292
+ def search(syntax: Syntax[Any, Any], data: AST[Any]) -> YieldGen[AST[Any], None, None]:
293
+ if matches(syntax, data):
294
+ yield data
295
+ match data.focus:
296
+ case ThenResult(left = left, right=right):
297
+ yield from search(syntax, AST(left))
298
+ yield from search(syntax, AST(right))
299
+ case ManyResult(value = value):
300
+ for e in value:
301
+ yield from search(syntax, AST(e))
302
+ case NamedResult(value=value):
303
+ yield from search(syntax, AST(value))
304
+ case OrResult(value=value):
305
+ yield from search(syntax, AST(value))
306
+ case _:
307
+ pass
308
+