syncraft 0.1.18__py3-none-any.whl → 0.1.20__py3-none-any.whl
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.
- syncraft/algebra.py +145 -227
- syncraft/ast.py +13 -10
- syncraft/diagnostic.py +2 -2
- syncraft/generator.py +33 -5
- syncraft/parser.py +21 -27
- syncraft/sqlite3.py +7 -7
- syncraft/{dsl.py → syntax.py} +58 -58
- {syncraft-0.1.18.dist-info → syncraft-0.1.20.dist-info}/METADATA +5 -4
- syncraft-0.1.20.dist-info/RECORD +15 -0
- syncraft-0.1.18.dist-info/RECORD +0 -15
- {syncraft-0.1.18.dist-info → syncraft-0.1.20.dist-info}/WHEEL +0 -0
- {syncraft-0.1.18.dist-info → syncraft-0.1.20.dist-info}/licenses/LICENSE +0 -0
- {syncraft-0.1.18.dist-info → syncraft-0.1.20.dist-info}/top_level.txt +0 -0
syncraft/algebra.py
CHANGED
|
@@ -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 ########################################################
|
syncraft/ast.py
CHANGED
|
@@ -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)
|
syncraft/diagnostic.py
CHANGED
|
@@ -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.
|
|
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,
|
|
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)
|
syncraft/generator.py
CHANGED
|
@@ -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,
|
|
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]
|
|
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(
|
|
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
|
+
|
syncraft/parser.py
CHANGED
|
@@ -6,12 +6,12 @@ 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
|
|
13
13
|
from functools import reduce
|
|
14
|
-
from syncraft.
|
|
14
|
+
from syncraft.syntax import Syntax
|
|
15
15
|
|
|
16
16
|
from syncraft.ast import Token, TokenSpec, AST, T
|
|
17
17
|
|
|
@@ -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:
|
|
@@ -187,15 +180,16 @@ class Parser(Algebra[T, ParserState[T]]):
|
|
|
187
180
|
return Right((tuple(tokens), tmp_state))
|
|
188
181
|
return cls(until_run, name=cls.__name__ + '.until')
|
|
189
182
|
|
|
190
|
-
def sqlglot(parser:
|
|
191
|
-
dialect: str) ->
|
|
183
|
+
def sqlglot(parser: Syntax[Any, Any],
|
|
184
|
+
dialect: str) -> Syntax[List[exp.Expression], ParserState[Any]]:
|
|
192
185
|
gp = GlotParser(dialect=dialect)
|
|
193
186
|
return parser.map(lambda tokens: [e for e in gp.parse(raw_tokens=tokens) if e is not None])
|
|
194
187
|
|
|
195
188
|
|
|
196
|
-
def parse(
|
|
189
|
+
def parse(syntax: Syntax[Any, Any],
|
|
197
190
|
sql: str,
|
|
198
191
|
dialect: str) -> AST[Any] | Any:
|
|
192
|
+
parser = syntax(Parser)
|
|
199
193
|
input: ParserState[Token] = token_state(sql, dialect=dialect)
|
|
200
194
|
result = parser.run(input, True)
|
|
201
195
|
if isinstance(result, Right):
|
|
@@ -212,34 +206,34 @@ def token(token_type: Optional[Enum] = None,
|
|
|
212
206
|
text: Optional[str] = None,
|
|
213
207
|
case_sensitive: bool = False,
|
|
214
208
|
regex: Optional[re.Pattern[str]] = None
|
|
215
|
-
) ->
|
|
209
|
+
) -> Syntax[Any, Any]:
|
|
216
210
|
token_type_txt = token_type.name if token_type is not None else None
|
|
217
211
|
token_value_txt = text if text is not None else None
|
|
218
212
|
msg = 'token(' + ','.join([x for x in [token_type_txt, token_value_txt, str(regex)] if x is not None]) + ')'
|
|
219
|
-
return
|
|
213
|
+
return Syntax(
|
|
220
214
|
lambda cls: cls.factory('token', token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
|
|
221
215
|
).describe(name=msg, fixity='prefix')
|
|
222
216
|
|
|
223
217
|
|
|
224
|
-
def identifier(value: str | None = None) ->
|
|
218
|
+
def identifier(value: str | None = None) -> Syntax[Any, Any]:
|
|
225
219
|
if value is None:
|
|
226
220
|
return token(TokenType.IDENTIFIER)
|
|
227
221
|
else:
|
|
228
222
|
return token(TokenType.IDENTIFIER, text=value)
|
|
229
223
|
|
|
230
|
-
def variable(value: str | None = None) ->
|
|
224
|
+
def variable(value: str | None = None) -> Syntax[Any, Any]:
|
|
231
225
|
if value is None:
|
|
232
226
|
return token(TokenType.VAR)
|
|
233
227
|
else:
|
|
234
228
|
return token(TokenType.VAR, text=value)
|
|
235
229
|
|
|
236
|
-
def literal(lit: str) ->
|
|
230
|
+
def literal(lit: str) -> Syntax[Any, Any]:
|
|
237
231
|
return token(token_type=None, text=lit, case_sensitive=True)
|
|
238
232
|
|
|
239
|
-
def regex(regex: re.Pattern[str]) ->
|
|
233
|
+
def regex(regex: re.Pattern[str]) -> Syntax[Any, Any]:
|
|
240
234
|
return token(token_type=None, regex=regex, case_sensitive=True)
|
|
241
235
|
|
|
242
|
-
def lift(value: Any)->
|
|
236
|
+
def lift(value: Any)-> Syntax[Any, Any]:
|
|
243
237
|
if isinstance(value, str):
|
|
244
238
|
return literal(value)
|
|
245
239
|
elif isinstance(value, re.Pattern):
|
|
@@ -247,22 +241,22 @@ def lift(value: Any)-> DSL[Any, Any]:
|
|
|
247
241
|
elif isinstance(value, Enum):
|
|
248
242
|
return token(value)
|
|
249
243
|
else:
|
|
250
|
-
return
|
|
244
|
+
return Syntax(lambda cls: cls.success(value))
|
|
251
245
|
|
|
252
|
-
def number() ->
|
|
246
|
+
def number() -> Syntax[Any, Any]:
|
|
253
247
|
return token(TokenType.NUMBER)
|
|
254
248
|
|
|
255
249
|
|
|
256
|
-
def string() ->
|
|
250
|
+
def string() -> Syntax[Any, Any]:
|
|
257
251
|
return token(TokenType.STRING)
|
|
258
252
|
|
|
259
253
|
|
|
260
254
|
|
|
261
|
-
def until(*open_close: Tuple[
|
|
262
|
-
terminator: Optional[
|
|
255
|
+
def until(*open_close: Tuple[Syntax[Tuple[T, ...] | T, ParserState[T]], Syntax[Tuple[T, ...] | T, ParserState[T]]],
|
|
256
|
+
terminator: Optional[Syntax[Tuple[T, ...] | T, ParserState[T]]] = None,
|
|
263
257
|
inclusive: bool = True,
|
|
264
|
-
strict: bool = True) ->
|
|
265
|
-
return
|
|
258
|
+
strict: bool = True) -> Syntax[Any, Any]:
|
|
259
|
+
return Syntax(
|
|
266
260
|
lambda cls: cls.factory('until',
|
|
267
261
|
*[(left.alg(cls), right.alg(cls)) for left, right in open_close],
|
|
268
262
|
terminator=terminator.alg(cls) if terminator else None,
|
syncraft/sqlite3.py
CHANGED
|
@@ -4,7 +4,7 @@ https://www.sqlite.org/syntaxdiagrams.html
|
|
|
4
4
|
"""
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
from typing import Any
|
|
7
|
-
from syncraft.
|
|
7
|
+
from syncraft.syntax import Syntax, lazy, choice
|
|
8
8
|
import syncraft.parser as dsl
|
|
9
9
|
from syncraft.diagnostic import rich_error, rich_debug, rich_parser
|
|
10
10
|
from sqlglot import TokenType
|
|
@@ -607,16 +607,16 @@ update_stmt_limited = (
|
|
|
607
607
|
)
|
|
608
608
|
|
|
609
609
|
|
|
610
|
-
def table_or_subquery()->
|
|
610
|
+
def table_or_subquery()->Syntax[Any, Any]:
|
|
611
611
|
t1 = ~schema_name >> table_as_alias >> ~((INDEXED >> BY >> index_name)|(NOT >> INDEXED))
|
|
612
612
|
t2 = ~schema_name >> table_function_name >> expr.parens(COMMA, L_PAREN, R_PAREN) >> ~(~AS >> var)
|
|
613
613
|
t3 = select_stmt.between(L_PAREN, R_PAREN) >> ~(~AS >> var)
|
|
614
614
|
t4 = table_subquery.parens(COMMA, L_PAREN, R_PAREN)
|
|
615
615
|
t5 = join_clause.between(L_PAREN, R_PAREN)
|
|
616
|
-
return (t1 | t2 | t3 | t4 | t5).as_(
|
|
616
|
+
return (t1 | t2 | t3 | t4 | t5).as_(Syntax[Any, Any])
|
|
617
617
|
|
|
618
618
|
|
|
619
|
-
def expression() ->
|
|
619
|
+
def expression() -> Syntax[Any, Any]:
|
|
620
620
|
return choice(
|
|
621
621
|
literal_value,
|
|
622
622
|
bind_parameter,
|
|
@@ -639,9 +639,9 @@ def expression() -> DSL[Any, Any]:
|
|
|
639
639
|
expr >> ~NOT >> IN >> ~schema_name >> (table_name | (function_name >> expr.parens(COMMA, L_PAREN, R_PAREN))),
|
|
640
640
|
~NOT >> ~EXISTS >> select_stmt.between(L_PAREN, R_PAREN),
|
|
641
641
|
CASE >> ~expr >> (WHEN >> expr >> THEN >> expr).many() >> ~(ELSE >> expr) // END,
|
|
642
|
-
).as_(
|
|
642
|
+
).as_(Syntax[Any, Any])
|
|
643
643
|
|
|
644
|
-
def select_statement() ->
|
|
644
|
+
def select_statement() -> Syntax[Any, Any]:
|
|
645
645
|
select_clause = SELECT >> ~(DISTINCT | ALL) >> result_columns.sep_by(COMMA)
|
|
646
646
|
from_clause = FROM >> (table_subquery.sep_by(COMMA) | join_clause)
|
|
647
647
|
where_clause = WHERE >> expr
|
|
@@ -657,7 +657,7 @@ def select_statement() -> DSL[Any, Any]:
|
|
|
657
657
|
>> select_core.sep_by(compound_operator)
|
|
658
658
|
>> ~(ordering_clause >> ~limit_clause)
|
|
659
659
|
>> ~SEMICOLON
|
|
660
|
-
).as_(
|
|
660
|
+
).as_(Syntax[Any, Any])
|
|
661
661
|
|
|
662
662
|
column_constraint = ~(CONSTRAINT >> constraint_name) >> (
|
|
663
663
|
(PRIMARY >> KEY >> ~(ASC | DESC) >> ~conflict_clause >> AUTO_INCREMENT)
|
syncraft/{dsl.py → syntax.py}
RENAMED
|
@@ -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,11 +67,11 @@ class Description(Insptectable):
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
@dataclass(frozen=True)
|
|
70
|
-
class
|
|
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
|
|
|
74
|
-
def algebra(self, name: str | MethodType | FunctionType, *args: Any, **kwargs: Any)->
|
|
74
|
+
def algebra(self, name: str | MethodType | FunctionType, *args: Any, **kwargs: Any)-> Syntax[A, S]:
|
|
75
75
|
def algebra_run(cls: Type[Algebra[Any, S]]) -> Algebra[Any, S]:
|
|
76
76
|
a = self.alg(cls)
|
|
77
77
|
if isinstance(name, str):
|
|
@@ -115,33 +115,33 @@ class DSL(Generic[A, S], Insptectable):
|
|
|
115
115
|
newline: Optional[str] = None,
|
|
116
116
|
name: Optional[str] = None,
|
|
117
117
|
fixity: Optional[Literal['infix', 'prefix', 'postfix']] = None,
|
|
118
|
-
parameter: Optional[List[
|
|
118
|
+
parameter: Optional[List[Syntax[Any, S]]] = None) -> Syntax[A, S]:
|
|
119
119
|
return self.__class__(alg=self.alg,
|
|
120
120
|
meta=self.meta.update(name=name,
|
|
121
121
|
newline=newline,
|
|
122
122
|
fixity=fixity,
|
|
123
123
|
parameter=parameter))
|
|
124
124
|
|
|
125
|
-
def newline(self, info: str='')->
|
|
125
|
+
def newline(self, info: str='')-> Syntax[A, S]:
|
|
126
126
|
return self.describe(newline=info)
|
|
127
127
|
|
|
128
|
-
def terminal(self, name: str)->
|
|
128
|
+
def terminal(self, name: str)->Syntax[A, S]:
|
|
129
129
|
return self.describe(name=name, fixity='prefix', parameter=[])
|
|
130
130
|
|
|
131
131
|
######################################################## value transformation ########################################################
|
|
132
|
-
def map(self, f: Callable[[A], B]) ->
|
|
132
|
+
def map(self, f: Callable[[A], B]) -> Syntax[B, S]:
|
|
133
133
|
return self.__class__(lambda cls: self.alg(cls).map(f), meta = self.meta) # type: ignore
|
|
134
134
|
|
|
135
|
-
def map_error(self, f: Callable[[Optional[Any]], Any]) ->
|
|
135
|
+
def map_error(self, f: Callable[[Optional[Any]], Any]) -> Syntax[A, S]:
|
|
136
136
|
return self.__class__(lambda cls: self.alg(cls).map_error(f), meta=self.meta)
|
|
137
137
|
|
|
138
|
-
def map_state(self, f: Callable[[S], S]) ->
|
|
138
|
+
def map_state(self, f: Callable[[S], S]) -> Syntax[A, S]:
|
|
139
139
|
return self.__class__(lambda cls: self.alg(cls).map_state(f), meta=self.meta)
|
|
140
140
|
|
|
141
|
-
def flat_map(self, f: Callable[[A], Algebra[B, S]]) ->
|
|
141
|
+
def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
|
|
142
142
|
return self.__class__(lambda cls: self.alg(cls).flat_map(f)) # type: ignore
|
|
143
143
|
|
|
144
|
-
def many(self, *, at_least: int = 1, at_most: Optional[int] = None) ->
|
|
144
|
+
def many(self, *, at_least: int = 1, at_most: Optional[int] = None) -> Syntax[ManyResult[A], S]:
|
|
145
145
|
return self.__class__(lambda cls:self.alg(cls).many(at_least=at_least, at_most=at_most)).describe(name='*', # type: ignore
|
|
146
146
|
fixity='prefix',
|
|
147
147
|
parameter=[self])
|
|
@@ -150,83 +150,83 @@ class DSL(Generic[A, S], Insptectable):
|
|
|
150
150
|
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
def between(self, left:
|
|
153
|
+
def between(self, left: Syntax[Any, S], right: Syntax[Any, S]) -> Syntax[ThenResult[None, ThenResult[A, None]], S]:
|
|
154
154
|
return left >> self // right
|
|
155
155
|
|
|
156
|
-
def sep_by(self, sep:
|
|
156
|
+
def sep_by(self, sep: Syntax[Any, S]) -> Syntax[ThenResult[A, ManyResult[ThenResult[None, A]]], S]:
|
|
157
157
|
return (self + (sep >> self).many()).describe(
|
|
158
158
|
name='sep_by',
|
|
159
159
|
fixity='prefix',
|
|
160
160
|
parameter=[self, sep]
|
|
161
161
|
)
|
|
162
162
|
|
|
163
|
-
def parens(self, sep:
|
|
163
|
+
def parens(self, sep: Syntax[Any, S], open: Syntax[Any, S], close: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
164
164
|
return self.sep_by(sep=sep).between(left=open, right=close)
|
|
165
165
|
|
|
166
|
-
def optional(self, default: Optional[B] = None) ->
|
|
166
|
+
def optional(self, default: Optional[B] = None) -> Syntax[Optional[A | B], S]:
|
|
167
167
|
return (self | success(default)).describe(name='~', fixity='prefix', parameter=[self])
|
|
168
168
|
|
|
169
169
|
|
|
170
|
-
def cut(self) ->
|
|
170
|
+
def cut(self) -> Syntax[A, S]:
|
|
171
171
|
return self.__class__(lambda cls:self.alg(cls).cut())
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
####################################################### operator overloading #############################################
|
|
175
|
-
def __ge__(self, f: Callable[[A], Algebra[B, S]]) ->
|
|
175
|
+
def __ge__(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
|
|
176
176
|
return self.flat_map(f).describe(name='>=', fixity='infix', parameter=[self])
|
|
177
177
|
|
|
178
178
|
|
|
179
|
-
def __gt__(self, other: Callable[[A], B])->
|
|
179
|
+
def __gt__(self, other: Callable[[A], B])->Syntax[B, S]:
|
|
180
180
|
return self.map(other)
|
|
181
181
|
|
|
182
182
|
|
|
183
|
-
def __floordiv__(self, other:
|
|
184
|
-
other = other if isinstance(other,
|
|
183
|
+
def __floordiv__(self, other: Syntax[B, S]) -> Syntax[ThenResult[A, None], S]:
|
|
184
|
+
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
185
185
|
return self.__class__(
|
|
186
186
|
lambda cls: self.alg(cls).then_left(other.alg(cls)) # type: ignore
|
|
187
|
-
).describe(name=ThenKind.LEFT.value, fixity='infix', parameter=[self, other]).as_(
|
|
187
|
+
).describe(name=ThenKind.LEFT.value, fixity='infix', parameter=[self, other]).as_(Syntax[ThenResult[A, None], S])
|
|
188
188
|
|
|
189
|
-
def __rfloordiv__(self, other:
|
|
190
|
-
other = other if isinstance(other,
|
|
189
|
+
def __rfloordiv__(self, other: Syntax[B, S]) -> Syntax[ThenResult[B, None], S]:
|
|
190
|
+
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
191
191
|
return other.__floordiv__(self)
|
|
192
192
|
|
|
193
|
-
def __invert__(self) ->
|
|
193
|
+
def __invert__(self) -> Syntax[A | None, S]:
|
|
194
194
|
return self.optional()
|
|
195
195
|
|
|
196
|
-
def __radd__(self, other:
|
|
197
|
-
other = other if isinstance(other,
|
|
196
|
+
def __radd__(self, other: Syntax[B, S]) -> Syntax[ThenResult[B, A], S]:
|
|
197
|
+
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
198
198
|
return other.__add__(self)
|
|
199
199
|
|
|
200
|
-
def __add__(self, other:
|
|
201
|
-
other = other if isinstance(other,
|
|
200
|
+
def __add__(self, other: Syntax[B, S]) -> Syntax[ThenResult[A, B], S]:
|
|
201
|
+
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
202
202
|
return self.__class__(
|
|
203
203
|
lambda cls: self.alg(cls).then_both(other.alg(cls)) # type: ignore
|
|
204
204
|
).describe(name=ThenKind.BOTH.value, fixity='infix', parameter=[self, other])
|
|
205
205
|
|
|
206
|
-
def __rshift__(self, other:
|
|
207
|
-
other = other if isinstance(other,
|
|
206
|
+
def __rshift__(self, other: Syntax[B, S]) -> Syntax[ThenResult[None, B], S]:
|
|
207
|
+
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
208
208
|
return self.__class__(
|
|
209
209
|
lambda cls: self.alg(cls).then_right(other.alg(cls)) # type: ignore
|
|
210
|
-
).describe(name=ThenKind.RIGHT.value, fixity='infix', parameter=[self, other]).as_(
|
|
210
|
+
).describe(name=ThenKind.RIGHT.value, fixity='infix', parameter=[self, other]).as_(Syntax[ThenResult[None, B], S])
|
|
211
211
|
|
|
212
212
|
|
|
213
|
-
def __rrshift__(self, other:
|
|
214
|
-
other = other if isinstance(other,
|
|
213
|
+
def __rrshift__(self, other: Syntax[B, S]) -> Syntax[ThenResult[None, A], S]:
|
|
214
|
+
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
215
215
|
return other.__rshift__(self)
|
|
216
216
|
|
|
217
217
|
|
|
218
|
-
def __or__(self, other:
|
|
219
|
-
other = other if isinstance(other,
|
|
218
|
+
def __or__(self, other: Syntax[B, S]) -> Syntax[A | B, S]:
|
|
219
|
+
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
220
220
|
return self.__class__(lambda cls: self.alg(cls).or_else(other.alg(cls))).describe(name='|', fixity='infix', parameter=[self, other]) # type: ignore
|
|
221
221
|
|
|
222
222
|
|
|
223
|
-
def __ror__(self, other:
|
|
224
|
-
other = other if isinstance(other,
|
|
225
|
-
return other.__or__(self).as_(
|
|
223
|
+
def __ror__(self, other: Syntax[B, S]) -> Syntax[A | B, S]:
|
|
224
|
+
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
225
|
+
return other.__or__(self).as_(Syntax[A | B, S])
|
|
226
226
|
|
|
227
227
|
|
|
228
228
|
######################################################################## data processing combinators #########################################################
|
|
229
|
-
def bind(self, name: str) ->
|
|
229
|
+
def bind(self, name: str) -> Syntax[NamedResult[A], S]:
|
|
230
230
|
def bind_f(value: A) -> NamedResult[A]:
|
|
231
231
|
if isinstance(value, NamedResult):
|
|
232
232
|
return replace(value, name=name)
|
|
@@ -235,7 +235,7 @@ class DSL(Generic[A, S], Insptectable):
|
|
|
235
235
|
return self.map(bind_f).describe(name=f'bind("{name}")', fixity='postfix', parameter=[self])
|
|
236
236
|
|
|
237
237
|
|
|
238
|
-
def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) ->
|
|
238
|
+
def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> Syntax[A, S]:
|
|
239
239
|
def dump_error_run(err: Any)->Any:
|
|
240
240
|
if isinstance(err, Error) and formatter is not None:
|
|
241
241
|
formatter(err)
|
|
@@ -245,21 +245,21 @@ class DSL(Generic[A, S], Insptectable):
|
|
|
245
245
|
|
|
246
246
|
def debug(self,
|
|
247
247
|
label: str,
|
|
248
|
-
formatter: Optional[Callable[[Algebra[Any, S], S, Either[Any, Tuple[Any, S]]], None]] = None) ->
|
|
248
|
+
formatter: Optional[Callable[[Algebra[Any, S], S, Either[Any, Tuple[Any, S]]], None]] = None) -> Syntax[A, S]:
|
|
249
249
|
return self.__class__(lambda cls:self.alg(cls).debug(label, formatter), meta=self.meta)
|
|
250
250
|
|
|
251
251
|
|
|
252
252
|
|
|
253
|
-
def lazy(thunk: Callable[[],
|
|
254
|
-
return
|
|
253
|
+
def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
|
|
254
|
+
return Syntax(lambda cls: cls.lazy(lambda: thunk()(cls))).describe(name='lazy(?)', fixity='postfix')
|
|
255
255
|
|
|
256
|
-
def fail(error: Any) ->
|
|
257
|
-
return
|
|
256
|
+
def fail(error: Any) -> Syntax[Any, Any]:
|
|
257
|
+
return Syntax(lambda alg: alg.fail(error)).describe(name=f'fail({error})', fixity='prefix')
|
|
258
258
|
|
|
259
|
-
def success(value: Any) ->
|
|
260
|
-
return
|
|
259
|
+
def success(value: Any) -> Syntax[Any, Any]:
|
|
260
|
+
return Syntax(lambda alg: alg.success(value)).describe(name=f'success({value})', fixity='prefix')
|
|
261
261
|
|
|
262
|
-
def choice(*parsers:
|
|
262
|
+
def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
263
263
|
return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(None)
|
|
264
264
|
|
|
265
265
|
|
|
@@ -269,27 +269,27 @@ def choice(*parsers: DSL[Any, S]) -> DSL[Any, S]:
|
|
|
269
269
|
|
|
270
270
|
|
|
271
271
|
|
|
272
|
-
def all(*parsers:
|
|
272
|
+
def all(*parsers: Syntax[Any, S]) -> Syntax[ThenResult[Any, Any], S]:
|
|
273
273
|
return reduce(lambda a, b: a + b, parsers) if len(parsers) > 0 else success(None)
|
|
274
274
|
|
|
275
|
-
def first(*parsers:
|
|
275
|
+
def first(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
276
276
|
return reduce(lambda a, b: a // b, parsers) if len(parsers) > 0 else success(None)
|
|
277
277
|
|
|
278
|
-
def last(*parsers:
|
|
278
|
+
def last(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
279
279
|
return reduce(lambda a, b: a >> b, parsers) if len(parsers) > 0 else success(None)
|
|
280
280
|
|
|
281
|
-
def named(* parsers:
|
|
281
|
+
def named(* parsers: Syntax[Any, S] | Tuple[str, Syntax[Any, S]]) -> Syntax[Any, S]:
|
|
282
282
|
def is_named_parser(x: Any) -> bool:
|
|
283
|
-
return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1],
|
|
283
|
+
return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax)
|
|
284
284
|
|
|
285
|
-
def to_parser(x:
|
|
286
|
-
if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1],
|
|
285
|
+
def to_parser(x: Syntax[Any, S] | Tuple[str, Syntax[Any, S]])->Syntax[Any, S]:
|
|
286
|
+
if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax):
|
|
287
287
|
return x[1].bind(x[0])
|
|
288
|
-
elif isinstance(x,
|
|
288
|
+
elif isinstance(x, Syntax):
|
|
289
289
|
return x
|
|
290
290
|
else:
|
|
291
291
|
raise ValueError(f"Invalid parser or tuple: {x}", x)
|
|
292
|
-
ret: Optional[
|
|
292
|
+
ret: Optional[Syntax[Any, S]] = None
|
|
293
293
|
has_data = False
|
|
294
294
|
for p in parsers:
|
|
295
295
|
just_parser = to_parser(p)
|
|
@@ -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,15 @@
|
|
|
1
|
+
syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
syncraft/algebra.py,sha256=utnhCbrF5GFG6P2Oef_Ru6YVgVCgsGccMh6wx80ok5E,20519
|
|
3
|
+
syncraft/ast.py,sha256=Hn88_xOqSzrHYtPjNFKREJuE6yISmNI-VVTcU6PJKI0,4737
|
|
4
|
+
syncraft/cmd.py,sha256=DzXgU8QLVOg0YTFKpmOyzbf02LKPphQ4EeKSLzqpJ_s,2012
|
|
5
|
+
syncraft/diagnostic.py,sha256=mnzzceE9HpN2jVa7ztecZCz3sbCD79JxgkdLhJRBsvY,2822
|
|
6
|
+
syncraft/generator.py,sha256=ygt5HXfKG5z1LcTxhU8adqEePegqmokLjbP6pi34hGA,12355
|
|
7
|
+
syncraft/parser.py,sha256=hk67kvOTBToOB2GhAh4weJQHR9xv73YPNQnqrW_qYv8,11295
|
|
8
|
+
syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
syncraft/sqlite3.py,sha256=Pq09IHZOwuWg5W82l9D1flzd36QV0TOHQpTJ5U02V8g,34701
|
|
10
|
+
syncraft/syntax.py,sha256=NtxkfPvSNsmpbRcESxEgRAW9d04ZHukWQoSJqIHsHNk,13842
|
|
11
|
+
syncraft-0.1.20.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
|
|
12
|
+
syncraft-0.1.20.dist-info/METADATA,sha256=j3Ylao4YeT_HxO4Gv95chaRvpoZLBallacoeoaoLQjk,1381
|
|
13
|
+
syncraft-0.1.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
syncraft-0.1.20.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
|
|
15
|
+
syncraft-0.1.20.dist-info/RECORD,,
|
syncraft-0.1.18.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
syncraft/algebra.py,sha256=l_ptzSkXbRGSLzu46GXixcNQPdXAi1s5R9nRDI86JkY,22785
|
|
3
|
-
syncraft/ast.py,sha256=9CuHqXRiw_7UMDLd4x2def3kHgTVQ0hnPxyIbBlx1Zc,4640
|
|
4
|
-
syncraft/cmd.py,sha256=DzXgU8QLVOg0YTFKpmOyzbf02LKPphQ4EeKSLzqpJ_s,2012
|
|
5
|
-
syncraft/diagnostic.py,sha256=_kLbl1LlU4Y2PtsLLZFA_OLqeZqq1f2rjT_LRXLnDaI,2813
|
|
6
|
-
syncraft/dsl.py,sha256=AxAZKu6MBfaG7Wzss3UISbQsrFaigc1L-hS3p_CAXWQ,13620
|
|
7
|
-
syncraft/generator.py,sha256=FpKfRPYvA0Wu-eKy7GUj_Xahz3e58sD0fZ3bXoTVIzY,11432
|
|
8
|
-
syncraft/parser.py,sha256=c1cCCW0RNw2y4XlNIUkrMf7q5lkm8wes4v5xbjLAUGg,11573
|
|
9
|
-
syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
syncraft/sqlite3.py,sha256=WR4wFf2CraRkb7C5rph3wsu6sGbG_7EjEZDUsPH8xqU,34677
|
|
11
|
-
syncraft-0.1.18.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
|
|
12
|
-
syncraft-0.1.18.dist-info/METADATA,sha256=Ga4qLGYK64owCQOyGXgQocU-e4FXtK2jNv5RQT3eDS0,946
|
|
13
|
-
syncraft-0.1.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
syncraft-0.1.18.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
|
|
15
|
-
syncraft-0.1.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|