syncraft 0.1.19__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.19 → syncraft-0.1.20}/PKG-INFO +5 -4
  2. syncraft-0.1.20/README.md +22 -0
  3. {syncraft-0.1.19 → syncraft-0.1.20}/pyproject.toml +1 -1
  4. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/algebra.py +145 -227
  5. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/ast.py +13 -10
  6. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/generator.py +2 -2
  7. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/parser.py +2 -9
  8. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/syntax.py +3 -3
  9. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/PKG-INFO +5 -4
  10. {syncraft-0.1.19 → syncraft-0.1.20}/tests/test_bimap.py +135 -16
  11. syncraft-0.1.19/README.md +0 -21
  12. {syncraft-0.1.19 → syncraft-0.1.20}/LICENSE +0 -0
  13. {syncraft-0.1.19 → syncraft-0.1.20}/setup.cfg +0 -0
  14. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/__init__.py +0 -0
  15. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/cmd.py +0 -0
  16. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/diagnostic.py +0 -0
  17. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/py.typed +0 -0
  18. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/sqlite3.py +0 -0
  19. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/SOURCES.txt +0 -0
  20. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/dependency_links.txt +0 -0
  21. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/requires.txt +0 -0
  22. {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/top_level.txt +0 -0
  23. {syncraft-0.1.19 → syncraft-0.1.20}/tests/test_parse.py +0 -0
  24. {syncraft-0.1.19 → syncraft-0.1.20}/tests/test_until.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.19
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.19"
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)
@@ -7,7 +7,7 @@ from typing import (
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,
10
+ Algebra, ThenResult, Either, Left, Right, Error,
11
11
  OrResult, ManyResult, NamedResult
12
12
  )
13
13
 
@@ -31,7 +31,7 @@ GenResult = Union[
31
31
  ]
32
32
 
33
33
  @dataclass(frozen=True)
34
- class GenState(Generic[T], Insptectable):
34
+ class GenState(Generic[T]):
35
35
  ast: Optional[AST[T]]
36
36
  seed: int
37
37
 
@@ -6,7 +6,7 @@ from typing import (
6
6
  Generic, Callable
7
7
  )
8
8
  from syncraft.algebra import (
9
- Either, Left, Right, Error, Insptectable, Algebra
9
+ Either, Left, Right, Error, Algebra
10
10
  )
11
11
  from dataclasses import dataclass, field, replace
12
12
  from enum import Enum
@@ -24,7 +24,7 @@ from syncraft.ast import Token, TokenSpec, AST, T
24
24
 
25
25
 
26
26
  @dataclass(frozen=True)
27
- class ParserState(Generic[T], Insptectable):
27
+ class ParserState(Generic[T]):
28
28
  input: Tuple[T, ...] = field(default_factory=tuple)
29
29
  index: int = 0
30
30
 
@@ -40,13 +40,6 @@ class ParserState(Generic[T], Insptectable):
40
40
  def after(self, length: Optional[int] = 5)->str:
41
41
  length = min(length, len(self.input) - self.index) if length is not None else len(self.input) - self.index
42
42
  return " ".join(token.text for token in self.input[self.index:self.index + length])
43
-
44
- def to_string(self, interested: Callable[[Any], bool])->str:
45
- return f"ParserState(\n"\
46
- f"index={self.index}, \n"\
47
- f"input({len(self.input)})=[{self.token_sample_string()}, ...]), \n"\
48
- f"before=({self.before()}), \n"\
49
- f"after=({self.after()})"
50
43
 
51
44
 
52
45
  def current(self)->T:
@@ -6,7 +6,7 @@ from typing import (
6
6
  )
7
7
  from dataclasses import dataclass, field, replace
8
8
  from functools import reduce
9
- from syncraft.algebra import Algebra, Error, Either, Insptectable, ThenResult, ManyResult, ThenKind, NamedResult
9
+ from syncraft.algebra import Algebra, Error, Either, ThenResult, ManyResult, ThenKind, NamedResult
10
10
  from types import MethodType, FunctionType
11
11
 
12
12
 
@@ -18,7 +18,7 @@ C = TypeVar('C') # Result type for else branch
18
18
  S = TypeVar('S') # State type
19
19
 
20
20
  @dataclass(frozen=True)
21
- class Description(Insptectable):
21
+ class Description:
22
22
  name: Optional[str] = None
23
23
  newline: Optional[str] = None
24
24
  fixity: Literal['infix', 'prefix', 'postfix'] = 'infix'
@@ -67,7 +67,7 @@ class Description(Insptectable):
67
67
 
68
68
 
69
69
  @dataclass(frozen=True)
70
- class Syntax(Generic[A, S], Insptectable):
70
+ class Syntax(Generic[A, S]):
71
71
  alg: Callable[[Type[Algebra[Any, Any]]], Algebra[A, S]]
72
72
  meta: Description = field(default_factory=Description, repr=False)
73
73
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.19
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.
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
- from syncraft.algebra import NamedResult, Error, ManyResult, OrResult, ThenResult, ThenKind
2
+ from typing import Any, List, Tuple
3
+ from syncraft.algebra import NamedResult, Error, ManyResult, OrResult, ThenResult, ThenKind, Bimap
3
4
  from syncraft.parser import literal, parse
4
5
  import syncraft.generator as gen
5
6
  from syncraft.generator import TokenGen
@@ -17,7 +18,7 @@ def test1_simple_then() -> None:
17
18
  print("---" * 40)
18
19
  print(generated)
19
20
  assert ast == generated
20
- value, bmap = generated.bimap(None)
21
+ value, bmap = generated.bimap()
21
22
  print(value)
22
23
  assert bmap(value) == generated
23
24
 
@@ -33,7 +34,7 @@ def test2_named_results() -> None:
33
34
  print("---" * 40)
34
35
  print(generated)
35
36
  assert ast == generated
36
- value, bmap = generated.bimap(None)
37
+ value, bmap = generated.bimap()
37
38
  print(value)
38
39
  print(bmap(value))
39
40
  assert bmap(value) == generated
@@ -50,7 +51,7 @@ def test3_many_literals() -> None:
50
51
  print("---" * 40)
51
52
  print(generated)
52
53
  assert ast == generated
53
- value, bmap = generated.bimap(None)
54
+ value, bmap = generated.bimap()
54
55
  print(value)
55
56
  assert bmap(value) == generated
56
57
 
@@ -67,7 +68,7 @@ def test4_mixed_many_named() -> None:
67
68
  print("---" * 40)
68
69
  print(generated)
69
70
  assert ast == generated
70
- value, bmap = generated.bimap(None)
71
+ value, bmap = generated.bimap()
71
72
  print(value)
72
73
  assert bmap(value) == generated
73
74
 
@@ -83,7 +84,7 @@ def test5_nested_then_many() -> None:
83
84
  print("---" * 40)
84
85
  print(generated)
85
86
  # assert ast == generated
86
- value, bmap = generated.bimap(None)
87
+ value, bmap = generated.bimap()
87
88
  print(value)
88
89
  assert bmap(value) == generated
89
90
 
@@ -97,7 +98,7 @@ def test_then_flatten():
97
98
  print(ast)
98
99
  generated = gen.generate(syntax, ast)
99
100
  assert ast == generated
100
- value, bmap = ast.bimap(None)
101
+ value, bmap = ast.bimap()
101
102
  assert bmap(value) == ast
102
103
 
103
104
 
@@ -112,7 +113,7 @@ def test_named_in_then():
112
113
  print(ast)
113
114
  generated = gen.generate(syntax, ast)
114
115
  assert ast == generated
115
- value, bmap = ast.bimap(None)
116
+ value, bmap = ast.bimap()
116
117
  assert isinstance(value, tuple)
117
118
  print(value)
118
119
  assert set(x.name for x in value if isinstance(x, NamedResult)) == {"first", "second", "third"}
@@ -127,7 +128,7 @@ def test_named_in_many():
127
128
  print(ast)
128
129
  generated = gen.generate(syntax, ast)
129
130
  assert ast == generated
130
- value, bmap = ast.bimap(None)
131
+ value, bmap = ast.bimap()
131
132
  assert isinstance(value, list)
132
133
  assert all(isinstance(v, NamedResult) for v in value if isinstance(v, NamedResult))
133
134
  assert bmap(value) == ast
@@ -142,7 +143,7 @@ def test_named_in_or():
142
143
  print(ast)
143
144
  generated = gen.generate(syntax, ast)
144
145
  assert ast == generated
145
- value, bmap = ast.bimap(None)
146
+ value, bmap = ast.bimap()
146
147
  assert isinstance(value, NamedResult)
147
148
  assert value.name == "b"
148
149
  assert bmap(value) == ast
@@ -163,7 +164,7 @@ def test_deep_mix():
163
164
  print('---' * 40)
164
165
  print(generated)
165
166
  assert ast == generated
166
- value, bmap = ast.bimap(None)
167
+ value, bmap = ast.bimap()
167
168
  assert bmap(value) == ast
168
169
 
169
170
 
@@ -181,7 +182,7 @@ def test_backtracking_many() -> None:
181
182
  syntax = (A.many() + B) # must not eat the final "a" needed for B
182
183
  sql = "a a a a b"
183
184
  ast = parse(syntax, sql, dialect="sqlite")
184
- value, bmap = ast.bimap(None)
185
+ value, bmap = ast.bimap()
185
186
  assert value[-1] == TokenGen.from_string("b")
186
187
 
187
188
  def test_deep_nesting() -> None:
@@ -208,7 +209,7 @@ def test_named_many() -> None:
208
209
  sql = "a a"
209
210
  ast = parse(syntax, sql, dialect="sqlite")
210
211
  # Expect [NamedResult("alpha", "a"), NamedResult("alpha", "a")]
211
- flattened, _ = ast.bimap(None)
212
+ flattened, _ = ast.bimap()
212
213
  assert all(isinstance(x, NamedResult) for x in flattened)
213
214
 
214
215
 
@@ -220,7 +221,7 @@ def test_or_named() -> None:
220
221
  ast = parse(syntax, sql, dialect="sqlite")
221
222
  # Either NamedResult("y", "b") or just "b", depending on your design
222
223
  assert isinstance(ast.focus, OrResult)
223
- value, _ = ast.bimap(None)
224
+ value, _ = ast.bimap()
224
225
  assert value == NamedResult(name="y", value=TokenGen.from_string("b"))
225
226
 
226
227
 
@@ -264,10 +265,128 @@ def test_optional():
264
265
  A = literal("a").bind("a")
265
266
  syntax = A.optional()
266
267
  ast1 = parse(syntax, "", dialect="sqlite")
267
- v1, _ = ast1.bimap(None)
268
+ v1, _ = ast1.bimap()
268
269
  assert v1 is None
269
270
  ast2 = parse(syntax, "a", dialect="sqlite")
270
- v2, _ = ast2.bimap(None)
271
+ v2, _ = ast2.bimap()
271
272
  assert v2 == NamedResult(name='a', value=TokenGen.from_string('a'))
272
273
 
273
274
 
275
+ def test_or()->None:
276
+ inc: Bimap[Any, int, int] = Bimap(
277
+ forward=lambda s, x: (s, x + 1),
278
+ inverse=lambda s, x: (s, x - 1),
279
+ )
280
+ data = OrResult(value=1)
281
+ b = data.bimap()
282
+ b = b >> inc
283
+ s, x = b.forward(None, data)
284
+ assert x == 2
285
+ s, y = b.inverse(s, x)
286
+ assert y == data
287
+
288
+ def test_named()->None:
289
+ inc: Bimap[Any, NamedResult[int], int] = Bimap(
290
+ forward=lambda s, x: (s, x.value + 1),
291
+ inverse=lambda s, y: (s, NamedResult(name="", value=y - 1)),
292
+ )
293
+ data = NamedResult(name="test", value=1)
294
+ b = data.bimap()
295
+ c = b >> inc
296
+ s, x = c.forward(None, data)
297
+ assert x == 2
298
+ s, y = c.inverse(s, x)
299
+ assert y == data
300
+
301
+ def test_many()->None:
302
+ inc: Bimap[Any, int, int] = Bimap(
303
+ forward=lambda s, x: (s, x + 1),
304
+ inverse=lambda s, y: (s, y - 1),
305
+ )
306
+
307
+ inc2: Bimap[Any, List[int], List[int]] = Bimap(
308
+ forward=lambda s, x: (s, [xx + 1 for xx in x]),
309
+ inverse=lambda s, y: (s, [yy - 1 for yy in y]),
310
+ )
311
+ data = ManyResult(value=(1,2))
312
+ b = data.bimap(inc)
313
+ c = b >> inc2
314
+ s, x = c.forward(None, data)
315
+ assert x == [3,4]
316
+ s, y = c.inverse(s, x)
317
+ assert y == data
318
+
319
+ def test_then()->None:
320
+ inc: Bimap[Any, int, int] = Bimap(
321
+ forward=lambda s, x: (s, x + 1),
322
+ inverse=lambda s, y: (s, y - 1),
323
+ )
324
+ inc2: Bimap[Any, Tuple[int, ...], Tuple[int, ...]] = Bimap(
325
+ forward=lambda s, x: (s, tuple(xx + 1 for xx in x)),
326
+ inverse=lambda s, y: (s, tuple(yy - 1 for yy in y)),
327
+ )
328
+
329
+ data = ThenResult(kind=ThenKind.BOTH, left=1, right=2)
330
+ b = data.bimap(inc)
331
+ c = b >> inc2
332
+ s, x = c.forward(None, data)
333
+ assert x == (3, 4)
334
+ s, y = c.inverse(s, x)
335
+ assert y == data
336
+
337
+
338
+ def test_left()->None:
339
+ inc: Bimap[Any, int, int] = Bimap(
340
+ forward=lambda s, x: (s, x + 1),
341
+ inverse=lambda s, y: (s, y - 1),
342
+ )
343
+ inc2: Bimap[Any, int, int] = Bimap(
344
+ forward=lambda s, x: (s, x + 1),
345
+ inverse=lambda s, y: (s, y - 1),
346
+ )
347
+
348
+ data = ThenResult(kind=ThenKind.LEFT, left=1, right=2)
349
+ b = data.bimap(inc)
350
+ c = b >> inc2
351
+ s, x = c.forward(None, data)
352
+ assert x == 3
353
+ s, y = c.inverse(s, x)
354
+ assert y == data
355
+
356
+ def test_right()->None:
357
+ inc: Bimap[Any, int, int] = Bimap(
358
+ forward=lambda s, x: (s, x + 1),
359
+ inverse=lambda s, y: (s, y - 1),
360
+ )
361
+ inc2: Bimap[Any, int, int] = Bimap(
362
+ forward=lambda s, x: (s, x + 1),
363
+ inverse=lambda s, y: (s, y - 1),
364
+ )
365
+
366
+ data = ThenResult(kind=ThenKind.RIGHT, left=1, right=2)
367
+ b = data.bimap(inc)
368
+ c = b >> inc2
369
+ s, x = c.forward(None, data)
370
+ assert x == 4
371
+ s, y = c.inverse(s, x)
372
+ assert y == data
373
+
374
+ def test_nested()->None:
375
+ inc: Bimap[Any, int, int] = Bimap(
376
+ forward=lambda s, x: (s, x + 1),
377
+ inverse=lambda s, y: (s, y - 1),
378
+ )
379
+ inc2: Bimap[Any, Tuple[int, ...], Tuple[int, ...]] = Bimap(
380
+ forward=lambda s, x: (s, tuple(xx + 1 for xx in x)),
381
+ inverse=lambda s, y: (s, tuple(yy - 1 for yy in y)),
382
+ )
383
+
384
+ data = ThenResult(kind=ThenKind.BOTH, left=ThenResult(kind=ThenKind.BOTH, left=0, right=1), right=ThenResult(kind=ThenKind.BOTH, left=2, right=3))
385
+ b = data.bimap(inc)
386
+ c = b >> inc2
387
+ s, x = c.forward(None, data)
388
+ assert x == (2,3,4,5)
389
+ s, y = c.inverse(s, x)
390
+ assert y == data
391
+
392
+
syncraft-0.1.19/README.md DELETED
@@ -1,21 +0,0 @@
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
- - [ ] Test Walker.get/set for bidirectional mapping between source code and data class
20
- - [ ] Annotate sqlite3 grammar with named nodes and data classes
21
- - [ ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes