syncraft 0.1.30__tar.gz → 0.1.32__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.30 → syncraft-0.1.32}/PKG-INFO +1 -1
- {syncraft-0.1.30 → syncraft-0.1.32}/pyproject.toml +1 -1
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/algebra.py +6 -3
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/ast.py +50 -34
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/syntax.py +28 -13
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft.egg-info/PKG-INFO +1 -1
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft.egg-info/SOURCES.txt +1 -0
- syncraft-0.1.32/tests/test_to.py +51 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/LICENSE +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/README.md +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/setup.cfg +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/__init__.py +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/constraint.py +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/diagnostic.py +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/finder.py +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/generator.py +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/parser.py +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/py.typed +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft/sqlite3.py +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/syncraft.egg-info/top_level.txt +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/tests/test_bimap.py +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/tests/test_parse.py +0 -0
- {syncraft-0.1.30 → syncraft-0.1.32}/tests/test_until.py +0 -0
|
@@ -5,12 +5,15 @@ from typing import (
|
|
|
5
5
|
)
|
|
6
6
|
|
|
7
7
|
import traceback
|
|
8
|
-
from dataclasses import dataclass, replace
|
|
8
|
+
from dataclasses import dataclass, replace
|
|
9
9
|
from weakref import WeakKeyDictionary
|
|
10
10
|
from abc import ABC
|
|
11
|
-
from syncraft.ast import ThenKind, Then, Choice, Many, ChoiceKind
|
|
11
|
+
from syncraft.ast import ThenKind, Then, Choice, Many, ChoiceKind, shallow_dict
|
|
12
12
|
from syncraft.constraint import Bindable
|
|
13
13
|
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
14
17
|
S = TypeVar('S', bound=Bindable)
|
|
15
18
|
|
|
16
19
|
A = TypeVar('A') # Result type
|
|
@@ -64,7 +67,7 @@ class Error:
|
|
|
64
67
|
lst = []
|
|
65
68
|
current: Optional[Error] = self
|
|
66
69
|
while current is not None:
|
|
67
|
-
d =
|
|
70
|
+
d = shallow_dict(current)
|
|
68
71
|
lst.append({k:v for k,v in d.items() if v is not None and k != 'previous'})
|
|
69
72
|
current = current.previous
|
|
70
73
|
return lst
|
|
@@ -4,14 +4,20 @@ from __future__ import annotations
|
|
|
4
4
|
import re
|
|
5
5
|
from typing import (
|
|
6
6
|
Optional, Any, TypeVar, Tuple, runtime_checkable, cast,
|
|
7
|
-
Generic, Callable, Union, Protocol, Type, List, ClassVar
|
|
7
|
+
Generic, Callable, Union, Protocol, Type, List, ClassVar,
|
|
8
|
+
Dict
|
|
8
9
|
)
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
from dataclasses import dataclass, replace, is_dataclass,
|
|
12
|
+
from dataclasses import dataclass, replace, is_dataclass, fields
|
|
12
13
|
from enum import Enum
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
def shallow_dict(a: Any)->Dict[str, Any]:
|
|
17
|
+
assert is_dataclass(a), f"Expected dataclass instance for collector inverse, got {type(a)}"
|
|
18
|
+
return {f.name: getattr(a, f.name) for f in fields(a)}
|
|
19
|
+
|
|
20
|
+
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
A = TypeVar('A')
|
|
@@ -182,6 +188,11 @@ class AST:
|
|
|
182
188
|
|
|
183
189
|
@dataclass(frozen=True)
|
|
184
190
|
class Nothing(AST):
|
|
191
|
+
_instance = None
|
|
192
|
+
def __new__(cls):
|
|
193
|
+
if cls._instance is None:
|
|
194
|
+
cls._instance = super(Nothing, cls).__new__(cls)
|
|
195
|
+
return cls._instance
|
|
185
196
|
def __str__(self)->str:
|
|
186
197
|
return self.__class__.__name__
|
|
187
198
|
def __repr__(self)->str:
|
|
@@ -220,8 +231,8 @@ class Many(Generic[A], AST):
|
|
|
220
231
|
if len(bs) <= len(ret):
|
|
221
232
|
return Many(value = tuple(ret[i][1](bs[i]) for i in range(len(bs))))
|
|
222
233
|
else:
|
|
223
|
-
half = [ret[i][1](bs[i]) for i in range(len(
|
|
224
|
-
tmp = [ret[-1][1](bs[i]) for i in range(len(ret)
|
|
234
|
+
half = [ret[i][1](bs[i]) for i in range(len(ret))]
|
|
235
|
+
tmp = [ret[-1][1](bs[i]) for i in range(len(ret), len(bs))]
|
|
225
236
|
return Many(value = tuple(half + tmp))
|
|
226
237
|
return [v[0] for v in ret], inv
|
|
227
238
|
|
|
@@ -287,37 +298,42 @@ class Collect(Generic[A, E], AST):
|
|
|
287
298
|
collector: Collector
|
|
288
299
|
value: A
|
|
289
300
|
def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[B | E, Callable[[B | E], Collect[A, E]]]:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
assert
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
assert is_dataclass(e), f"Expected dataclass instance for collector inverse, got {type(e)}"
|
|
306
|
-
named_dict = asdict(e)
|
|
307
|
-
unnamed = []
|
|
308
|
-
for f in fields(e):
|
|
309
|
-
if f.name not in named:
|
|
310
|
-
unnamed.append(named_dict[f.name])
|
|
311
|
-
tmp = []
|
|
312
|
-
for x in index:
|
|
313
|
-
if isinstance(x, str):
|
|
314
|
-
tmp.append(Marked(name=x, value=named_dict[x]))
|
|
301
|
+
|
|
302
|
+
def inv_one_positional(e: E) -> B:
|
|
303
|
+
assert is_dataclass(e), f"Expected dataclass instance for collector inverse, got {type(e)}"
|
|
304
|
+
named_dict = shallow_dict(e)
|
|
305
|
+
return named_dict[fields(e)[0].name]
|
|
306
|
+
|
|
307
|
+
b, inner_f = self.value.bimap(r) if isinstance(self.value, AST) else r(self.value)
|
|
308
|
+
if isinstance(self.value, Then):
|
|
309
|
+
if isinstance(b, tuple):
|
|
310
|
+
index: List[str | int] = []
|
|
311
|
+
named_count = 0
|
|
312
|
+
for i, v in enumerate(b):
|
|
313
|
+
if isinstance(v, Marked):
|
|
314
|
+
index.append(v.name)
|
|
315
|
+
named_count += 1
|
|
315
316
|
else:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
317
|
+
index.append(i - named_count)
|
|
318
|
+
named = {v.name: v.value for v in b if isinstance(v, Marked)}
|
|
319
|
+
unnamed = [v for v in b if not isinstance(v, Marked)]
|
|
320
|
+
ret: E = self.collector(*unnamed, **named)
|
|
321
|
+
def invf(e: E) -> Tuple[Any, ...]:
|
|
322
|
+
assert is_dataclass(e), f"Expected dataclass instance for collector inverse, got {type(e)}"
|
|
323
|
+
named_dict = shallow_dict(e)
|
|
324
|
+
unnamed = []
|
|
325
|
+
for f in fields(e):
|
|
326
|
+
if f.name not in named:
|
|
327
|
+
unnamed.append(named_dict[f.name])
|
|
328
|
+
tmp = []
|
|
329
|
+
for x in index:
|
|
330
|
+
if isinstance(x, str):
|
|
331
|
+
tmp.append(Marked(name=x, value=named_dict[x]))
|
|
332
|
+
else:
|
|
333
|
+
tmp.append(unnamed[x])
|
|
334
|
+
return tuple(tmp)
|
|
335
|
+
return ret, lambda e: replace(self, value=inner_f(invf(e))) # type: ignore
|
|
336
|
+
return self.collector(b), lambda e: replace(self, value=inner_f(inv_one_positional(e))) # type: ignore
|
|
321
337
|
|
|
322
338
|
#########################################################################################################################
|
|
323
339
|
@dataclass(frozen=True)
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
4
|
Optional, Any, TypeVar, Generic, Callable, Tuple, cast,
|
|
5
|
-
Type, Literal
|
|
5
|
+
Type, Literal, List
|
|
6
6
|
)
|
|
7
7
|
from dataclasses import dataclass, field, replace
|
|
8
8
|
from functools import reduce
|
|
@@ -165,8 +165,29 @@ class Syntax(Generic[A, S]):
|
|
|
165
165
|
def between(self, left: Syntax[B, S], right: Syntax[C, S]) -> Syntax[Then[B, Then[A, C]], S]:
|
|
166
166
|
return left >> self // right
|
|
167
167
|
|
|
168
|
-
def sep_by(self,
|
|
168
|
+
def sep_by(self,
|
|
169
|
+
sep: Syntax[B, S]) -> Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S]:
|
|
169
170
|
ret: Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S] = (self + (sep >> self).many().optional())
|
|
171
|
+
def f(a: Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]]) -> Many[A]:
|
|
172
|
+
match a:
|
|
173
|
+
case Then(kind=ThenKind.BOTH, left=left, right=Choice(kind=ChoiceKind.RIGHT, value=Nothing())):
|
|
174
|
+
return Many(value = (left,))
|
|
175
|
+
case Then(kind=ThenKind.BOTH, left=left, right=Choice(kind=ChoiceKind.LEFT, value=Many(value=bs))):
|
|
176
|
+
return Many(value = (left,) + tuple([b.right for b in bs]))
|
|
177
|
+
case _:
|
|
178
|
+
raise ValueError(f"Bad data shape {a}")
|
|
179
|
+
|
|
180
|
+
def i(a: Many[A]) -> Then[A, Choice[Many[Then[B|None, A]], Optional[Nothing]]]:
|
|
181
|
+
assert len(a.value) >= 1, f"sep_by expect at least one element, got {len(a.value)}. {a}"
|
|
182
|
+
if len(a.value) == 1:
|
|
183
|
+
return Then(kind=ThenKind.BOTH, left=a.value[0], right=Choice(kind=ChoiceKind.RIGHT, value=Nothing()))
|
|
184
|
+
else:
|
|
185
|
+
v: List[Then[B|None, A]] = [Then(kind=ThenKind.RIGHT, right=x, left=None) for x in a.value[1:]]
|
|
186
|
+
return Then(kind= ThenKind.BOTH,
|
|
187
|
+
left=a.value[0],
|
|
188
|
+
right=Choice(kind=ChoiceKind.LEFT,
|
|
189
|
+
value=Many(value=tuple(v))))
|
|
190
|
+
ret = ret.bimap(f,i) # type: ignore
|
|
170
191
|
return ret.describe(
|
|
171
192
|
name='sep_by',
|
|
172
193
|
fixity='prefix',
|
|
@@ -191,17 +212,11 @@ class Syntax(Generic[A, S]):
|
|
|
191
212
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
192
213
|
ret: Syntax[Then[A, B], S] = self.__class__(lambda cls: self.alg(cls).then_left(other.alg(cls))) # type: ignore
|
|
193
214
|
return ret.describe(name=ThenKind.LEFT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
194
|
-
|
|
195
|
-
def __lshift__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
196
|
-
return self.__floordiv__(other)
|
|
197
|
-
|
|
215
|
+
|
|
198
216
|
def __rfloordiv__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
199
217
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
200
218
|
return other.__floordiv__(self)
|
|
201
219
|
|
|
202
|
-
def __rlshift__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
203
|
-
return self.__rfloordiv__(other)
|
|
204
|
-
|
|
205
220
|
def __add__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
206
221
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
207
222
|
ret: Syntax[Then[A, B], S] = self.__class__(lambda cls: self.alg(cls).then_both(other.alg(cls))) # type: ignore
|
|
@@ -260,16 +275,16 @@ class Syntax(Generic[A, S]):
|
|
|
260
275
|
return self.bimap(to_f, ito_f).describe(name=f'to({f})', fixity='postfix', parameter=(self,))
|
|
261
276
|
|
|
262
277
|
|
|
263
|
-
def mark(self,
|
|
278
|
+
def mark(self, name: str) -> Syntax[Marked[A], S]:
|
|
264
279
|
def bind_s(value: A) -> Marked[A]:
|
|
265
280
|
if isinstance(value, Marked):
|
|
266
|
-
return replace(value, name=
|
|
281
|
+
return replace(value, name=name)
|
|
267
282
|
else:
|
|
268
|
-
return Marked(name=
|
|
283
|
+
return Marked(name=name, value=value)
|
|
269
284
|
def ibind_s(m : Marked[A]) -> A:
|
|
270
285
|
return m.value if isinstance(m, Marked) else m
|
|
271
286
|
|
|
272
|
-
return self.bimap(bind_s, ibind_s).describe(name=f'bind("{
|
|
287
|
+
return self.bimap(bind_s, ibind_s).describe(name=f'bind("{name}")', fixity='postfix', parameter=(self,))
|
|
273
288
|
|
|
274
289
|
|
|
275
290
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any, List, Tuple
|
|
3
|
+
from syncraft.algebra import Either, Left, Right, Error
|
|
4
|
+
from syncraft.ast import Marked, Then, ThenKind, Many, Nothing
|
|
5
|
+
from syncraft.parser import literal, variable, parse, Parser, Token
|
|
6
|
+
from syncraft.generator import TokenGen
|
|
7
|
+
from rich import print
|
|
8
|
+
import syncraft.generator as gen
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_to() -> None:
|
|
13
|
+
@dataclass
|
|
14
|
+
class IfThenElse:
|
|
15
|
+
condition: Any
|
|
16
|
+
then: Any
|
|
17
|
+
otherwise: Any
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class While:
|
|
21
|
+
condition:Any
|
|
22
|
+
body:Any
|
|
23
|
+
|
|
24
|
+
WHILE = literal("while")
|
|
25
|
+
IF = literal("if")
|
|
26
|
+
ELSE = literal("else")
|
|
27
|
+
THEN = literal("then")
|
|
28
|
+
END = literal("end")
|
|
29
|
+
A = literal('a')
|
|
30
|
+
B = literal('b')
|
|
31
|
+
C = literal('c')
|
|
32
|
+
D = literal('d')
|
|
33
|
+
M = literal(',')
|
|
34
|
+
var = A | B | C | D
|
|
35
|
+
condition = var.sep_by(M).mark('condition')
|
|
36
|
+
ifthenelse = (IF >> condition
|
|
37
|
+
// THEN
|
|
38
|
+
+ var.sep_by(M).mark('then')
|
|
39
|
+
// ELSE
|
|
40
|
+
+ var.sep_by(M).mark('otherwise')
|
|
41
|
+
// END).to(IfThenElse).many()
|
|
42
|
+
syntax = (WHILE >> condition
|
|
43
|
+
+ ifthenelse.mark('body')
|
|
44
|
+
// ~END).to(While)
|
|
45
|
+
sql = 'while b,c,c if a,b then c,d else a,d end if a,b then c,d else a,d end'
|
|
46
|
+
ast = parse(syntax, sql, dialect='sqlite')
|
|
47
|
+
g = gen.generate(syntax, ast, restore_pruned=True)
|
|
48
|
+
assert ast == g
|
|
49
|
+
x, f = g.bimap()
|
|
50
|
+
print(x)
|
|
51
|
+
assert gen.generate(syntax, f(x), restore_pruned=True) == ast
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|