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.
- {syncraft-0.1.19 → syncraft-0.1.20}/PKG-INFO +5 -4
- syncraft-0.1.20/README.md +22 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/pyproject.toml +1 -1
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/algebra.py +145 -227
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/ast.py +13 -10
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/generator.py +2 -2
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/parser.py +2 -9
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/syntax.py +3 -3
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/PKG-INFO +5 -4
- {syncraft-0.1.19 → syncraft-0.1.20}/tests/test_bimap.py +135 -16
- syncraft-0.1.19/README.md +0 -21
- {syncraft-0.1.19 → syncraft-0.1.20}/LICENSE +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/setup.cfg +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/__init__.py +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/cmd.py +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/diagnostic.py +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/py.typed +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft/sqlite3.py +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/SOURCES.txt +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/syncraft.egg-info/top_level.txt +0 -0
- {syncraft-0.1.19 → syncraft-0.1.20}/tests/test_parse.py +0 -0
- {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.
|
|
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
|
-
- [ ]
|
|
35
|
-
- [ ]
|
|
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,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,
|
|
32
|
-
from functools import cached_property
|
|
8
|
+
from dataclasses import dataclass, replace
|
|
33
9
|
from weakref import WeakKeyDictionary
|
|
34
|
-
from abc import ABC
|
|
10
|
+
from abc import ABC
|
|
35
11
|
from enum import Enum
|
|
12
|
+
from functools import reduce
|
|
36
13
|
|
|
37
14
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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,
|
|
121
|
-
return (
|
|
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,
|
|
129
|
-
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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,
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
172
|
-
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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,
|
|
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]
|
|
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,
|
|
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]
|
|
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,
|
|
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
|
|
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]
|
|
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.
|
|
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
|
-
- [ ]
|
|
35
|
-
- [ ]
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
268
|
+
v1, _ = ast1.bimap()
|
|
268
269
|
assert v1 is None
|
|
269
270
|
ast2 = parse(syntax, "a", dialect="sqlite")
|
|
270
|
-
v2, _ = ast2.bimap(
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|