syncraft 0.1.13__tar.gz → 0.1.15__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.13 → syncraft-0.1.15}/PKG-INFO +1 -1
- {syncraft-0.1.13 → syncraft-0.1.15}/pyproject.toml +1 -1
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/algebra.py +13 -4
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/ast.py +16 -9
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/dsl.py +9 -70
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/generator.py +33 -15
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/parser.py +4 -4
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/sqlite3.py +2 -2
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft.egg-info/PKG-INFO +1 -1
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft.egg-info/SOURCES.txt +1 -0
- syncraft-0.1.15/tests/test_bimap.py +88 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/LICENSE +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/README.md +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/setup.cfg +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/__init__.py +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/cmd.py +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/diagnostic.py +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft/py.typed +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/syncraft.egg-info/top_level.txt +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/tests/test_parse.py +0 -0
- {syncraft-0.1.13 → syncraft-0.1.15}/tests/test_until.py +0 -0
|
@@ -127,7 +127,13 @@ class NamedResult(Generic[A], StructuralResult):
|
|
|
127
127
|
value: A
|
|
128
128
|
def bimap(self, ctx: Any)->Tuple[NamedResult[Any], Callable[[NamedResult[Any]], StructuralResult]]:
|
|
129
129
|
value, backward = self.value.bimap(ctx) if isinstance(self.value, StructuralResult) else (self.value, lambda x: x)
|
|
130
|
-
|
|
130
|
+
def named_back(data: Any)->NamedResult[Any]:
|
|
131
|
+
v = backward(data)
|
|
132
|
+
if isinstance(v, NamedResult):
|
|
133
|
+
return replace(v, name=self.name)
|
|
134
|
+
else:
|
|
135
|
+
return NamedResult(name=self.name, value=v)
|
|
136
|
+
return NamedResult(self.name, value), named_back
|
|
131
137
|
|
|
132
138
|
@dataclass(eq=True, frozen=True)
|
|
133
139
|
class ManyResult(Generic[A], StructuralResult):
|
|
@@ -165,9 +171,12 @@ class ThenResult(Generic[A, B], StructuralResult):
|
|
|
165
171
|
def bimap(self, ctx: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
|
|
166
172
|
def branch(b: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
|
|
167
173
|
if isinstance(b, ThenResult):
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
value, backward = b.bimap(ctx)
|
|
175
|
+
if isinstance(value, tuple):
|
|
176
|
+
x, y = ThenResult.flat(value)
|
|
177
|
+
return x, lambda data: ThenResult(self.kind, y(data), self.right)
|
|
178
|
+
else:
|
|
179
|
+
return value, backward
|
|
171
180
|
elif isinstance(b, StructuralResult):
|
|
172
181
|
return b.bimap(ctx)
|
|
173
182
|
else:
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import re
|
|
5
5
|
from typing import (
|
|
6
6
|
Optional, Any, TypeVar, Tuple, runtime_checkable, Dict,
|
|
7
|
-
Protocol, Generic, Callable, Union
|
|
7
|
+
Protocol, Generic, Callable, Union, cast
|
|
8
8
|
)
|
|
9
9
|
from syncraft.algebra import (
|
|
10
10
|
OrResult,ThenResult, ManyResult, ThenKind,NamedResult, StructuralResult,
|
|
@@ -65,26 +65,32 @@ class AST(Generic[T]):
|
|
|
65
65
|
pruned: bool = False
|
|
66
66
|
parent: Optional[AST[T]] = None
|
|
67
67
|
|
|
68
|
+
|
|
68
69
|
def bimap(self, ctx: Any) -> Tuple[Any, Callable[[Any], AST[T]]]:
|
|
69
70
|
value, backward = self.focus.bimap(ctx) if isinstance(self.focus, StructuralResult) else (self.focus, lambda x: x)
|
|
70
71
|
def back2ast(data: Any) -> AST[T]:
|
|
71
72
|
return replace(self, focus=backward(data)) # type: ignore
|
|
72
73
|
return value, back2ast
|
|
73
74
|
|
|
74
|
-
def
|
|
75
|
-
|
|
75
|
+
def wrapper(self)-> Callable[[Any], Any]:
|
|
76
|
+
if isinstance(self.focus, NamedResult):
|
|
77
|
+
focus = cast(NamedResult[Any], self.focus)
|
|
78
|
+
return lambda x: NamedResult(name = focus.name, value = x)
|
|
79
|
+
else:
|
|
80
|
+
return lambda x: x
|
|
81
|
+
|
|
82
|
+
def is_named(self) -> bool:
|
|
83
|
+
return isinstance(self.focus, NamedResult)
|
|
76
84
|
|
|
77
85
|
def left(self) -> Optional[AST[T]]:
|
|
78
|
-
|
|
79
|
-
match focus:
|
|
86
|
+
match self.focus:
|
|
80
87
|
case ThenResult(left=left, kind=kind):
|
|
81
88
|
return replace(self, focus=left, parent=self, pruned = self.pruned or kind == ThenKind.RIGHT)
|
|
82
89
|
case _:
|
|
83
90
|
raise TypeError(f"Invalid focus type({self.focus}) for left traversal")
|
|
84
91
|
|
|
85
92
|
def right(self) -> Optional[AST[T]]:
|
|
86
|
-
|
|
87
|
-
match focus:
|
|
93
|
+
match self.focus:
|
|
88
94
|
case ThenResult(right=right, kind=kind):
|
|
89
95
|
return replace(self, focus=right, parent=self, pruned = self.pruned or kind == ThenKind.LEFT)
|
|
90
96
|
case _:
|
|
@@ -92,8 +98,7 @@ class AST(Generic[T]):
|
|
|
92
98
|
|
|
93
99
|
|
|
94
100
|
def down(self, index: int) -> Optional[AST[T]]:
|
|
95
|
-
|
|
96
|
-
match focus:
|
|
101
|
+
match self.focus:
|
|
97
102
|
case ManyResult(value=children):
|
|
98
103
|
if 0 <= index < len(children):
|
|
99
104
|
return replace(self, focus=children[index], parent=self, pruned=self.pruned)
|
|
@@ -104,6 +109,8 @@ class AST(Generic[T]):
|
|
|
104
109
|
return replace(self, focus=value, parent=self, pruned=self.pruned)
|
|
105
110
|
else:
|
|
106
111
|
raise IndexError(f"Index {index} out of bounds for OrResult")
|
|
112
|
+
case NamedResult(value=value):
|
|
113
|
+
return replace(self, focus=value, parent=self, pruned=self.pruned)
|
|
107
114
|
case _:
|
|
108
115
|
raise TypeError(f"Invalid focus type({self.focus}) for down traversal")
|
|
109
116
|
|
|
@@ -4,9 +4,9 @@ from typing import (
|
|
|
4
4
|
Optional, List, Any, TypeVar, Generic, Callable, Tuple, cast,
|
|
5
5
|
Type, Literal
|
|
6
6
|
)
|
|
7
|
-
from dataclasses import dataclass, field
|
|
7
|
+
from dataclasses import dataclass, field, replace
|
|
8
8
|
from functools import reduce
|
|
9
|
-
from syncraft.algebra import Algebra, Error, Either, Insptectable, ThenResult, ManyResult, ThenKind
|
|
9
|
+
from syncraft.algebra import Algebra, Error, Either, Insptectable, ThenResult, ManyResult, ThenKind, NamedResult
|
|
10
10
|
from types import MethodType, FunctionType
|
|
11
11
|
|
|
12
12
|
|
|
@@ -226,75 +226,14 @@ class DSL(Generic[A, S], Insptectable):
|
|
|
226
226
|
|
|
227
227
|
|
|
228
228
|
######################################################################## data processing combinators #########################################################
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
forward_map: Callable[[B], C] | None,
|
|
234
|
-
backward_map: Callable[[C], B] | None,
|
|
235
|
-
aggregator_f: Callable[..., Any] | None,
|
|
236
|
-
) -> DSL[NamedResult[A, C], S]:
|
|
237
|
-
def attach_f(x: A | NamedResult[A, B]) -> NamedResult[A, C]:
|
|
238
|
-
if isinstance(x, NamedResult):
|
|
239
|
-
if x.backward_map is not None:
|
|
240
|
-
b_f = x.backward_map
|
|
241
|
-
def combined_bf(a: Any)->A:
|
|
242
|
-
if backward_map is not None:
|
|
243
|
-
return b_f(backward_map(a))
|
|
244
|
-
else:
|
|
245
|
-
return b_f(a)
|
|
246
|
-
if x.forward_map is not None:
|
|
247
|
-
f_f = x.forward_map
|
|
248
|
-
def combined_ff(a: Any)->Any:
|
|
249
|
-
if forward_map is not None:
|
|
250
|
-
return forward_map(f_f(a))
|
|
251
|
-
else:
|
|
252
|
-
return f_f(a)
|
|
253
|
-
if x.aggregator is not None:
|
|
254
|
-
agg_f = x.aggregator
|
|
255
|
-
def combined_agg(a: Any)->Any:
|
|
256
|
-
if aggregator_f is not None:
|
|
257
|
-
return aggregator_f(agg_f(a))
|
|
258
|
-
else:
|
|
259
|
-
return agg_f(a)
|
|
260
|
-
return NamedResult(
|
|
261
|
-
name=name,
|
|
262
|
-
value=x.value,
|
|
263
|
-
forward_map=forward_map if x.forward_map is None else combined_ff, # type: ignore
|
|
264
|
-
backward_map=backward_map if x.backward_map is None else combined_bf, # type: ignore
|
|
265
|
-
aggregator=aggregator_f if x.aggregator is None else combined_agg
|
|
266
|
-
)
|
|
229
|
+
def bind(self, name: str) -> DSL[NamedResult[A], S]:
|
|
230
|
+
def bind_f(value: A) -> NamedResult[A]:
|
|
231
|
+
if isinstance(value, NamedResult):
|
|
232
|
+
return replace(value, name=name)
|
|
267
233
|
else:
|
|
268
|
-
return NamedResult(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
forward_map=forward_map, # type: ignore
|
|
272
|
-
backward_map=backward_map, # type: ignore
|
|
273
|
-
aggregator=aggregator_f,
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
return self.map(attach_f)
|
|
278
|
-
|
|
279
|
-
def bind(self, name: str) -> DSL[NamedResult[Any, Any], S]:
|
|
280
|
-
return self._attach(name,
|
|
281
|
-
forward_map=None,
|
|
282
|
-
backward_map=None,
|
|
283
|
-
aggregator_f=None,
|
|
284
|
-
).describe(name=f'bind("{name}")', fixity='postfix', parameter=[self])
|
|
285
|
-
|
|
286
|
-
def bimap(self, name: str, *, forward_map: Callable[[Any], Any], backward_map: Callable[[Any], Any]) -> DSL[NamedResult[Any, Any], S]:
|
|
287
|
-
return self._attach(name,
|
|
288
|
-
forward_map=forward_map,
|
|
289
|
-
backward_map=backward_map,
|
|
290
|
-
aggregator_f=None,
|
|
291
|
-
).describe(name=f'bimap("{name}")', fixity='postfix', parameter=[self])
|
|
292
|
-
def to(self, name: str, aggregator_f: Callable[..., Any]) -> DSL[NamedResult[A, Any], S]:
|
|
293
|
-
return self._attach(name,
|
|
294
|
-
forward_map=None,
|
|
295
|
-
backward_map=None,
|
|
296
|
-
aggregator_f=aggregator_f,
|
|
297
|
-
).describe(name=f'to("{name}")', fixity='postfix', parameter=[self])
|
|
234
|
+
return NamedResult(name=name, value=value)
|
|
235
|
+
return self.map(bind_f).describe(name=f'bind("{name}")', fixity='postfix', parameter=[self])
|
|
236
|
+
|
|
298
237
|
|
|
299
238
|
def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> DSL[A, S]:
|
|
300
239
|
def dump_error_run(err: Any)->Any:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
|
-
Any, TypeVar, Tuple, Optional, Callable, Generic, Union,
|
|
4
|
+
Any, TypeVar, Tuple, Optional, Callable, Generic, Union,
|
|
5
5
|
List
|
|
6
6
|
)
|
|
7
7
|
from functools import cached_property
|
|
@@ -24,7 +24,7 @@ GenResult = Union[
|
|
|
24
24
|
ThenResult['GenResult[T]', 'GenResult[T]'],
|
|
25
25
|
ManyResult['GenResult[T]'],
|
|
26
26
|
OrResult['GenResult[T]'],
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
T
|
|
29
29
|
]
|
|
30
30
|
|
|
@@ -52,7 +52,16 @@ class GenState(Generic[T], Insptectable):
|
|
|
52
52
|
if self.ast is None:
|
|
53
53
|
return None
|
|
54
54
|
return self.ast.focus
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def is_named(self)->bool:
|
|
58
|
+
return self.ast is not None and self.ast.is_named()
|
|
55
59
|
|
|
60
|
+
def wrapper(self)->Callable[[Any], Any]:
|
|
61
|
+
if self.ast is not None:
|
|
62
|
+
return self.ast.wrapper()
|
|
63
|
+
else:
|
|
64
|
+
return lambda x: x
|
|
56
65
|
|
|
57
66
|
def left(self)-> GenState[T]:
|
|
58
67
|
if self.ast is None:
|
|
@@ -64,10 +73,7 @@ class GenState(Generic[T], Insptectable):
|
|
|
64
73
|
return self
|
|
65
74
|
return replace(self, ast=self.ast.right())
|
|
66
75
|
|
|
67
|
-
|
|
68
|
-
if self.ast is None:
|
|
69
|
-
return self
|
|
70
|
-
return replace(self, ast=self.ast.up())
|
|
76
|
+
|
|
71
77
|
|
|
72
78
|
def down(self, index: int) -> GenState[T]:
|
|
73
79
|
if self.ast is None:
|
|
@@ -142,13 +148,19 @@ class TokenGen(TokenSpec):
|
|
|
142
148
|
class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
143
149
|
def flat_map(self, f: Callable[[GenResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
|
|
144
150
|
def flat_map_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
|
|
145
|
-
|
|
151
|
+
wrapper = input.wrapper()
|
|
152
|
+
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
153
|
+
lft = input.left()
|
|
146
154
|
match self.run(lft, use_cache=use_cache):
|
|
147
155
|
case Left(error):
|
|
148
156
|
return Left(error)
|
|
149
157
|
case Right((value, next_input)):
|
|
150
|
-
r = input.right()
|
|
151
|
-
|
|
158
|
+
r = input.right()
|
|
159
|
+
match f(value).run(r, use_cache):
|
|
160
|
+
case Left(e):
|
|
161
|
+
return Left(e)
|
|
162
|
+
case Right((result, next_input)):
|
|
163
|
+
return Right((wrapper(result), next_input))
|
|
152
164
|
raise ValueError("flat_map should always return a value or an error.")
|
|
153
165
|
return Generator(run_f = flat_map_run, name=self.name) # type: ignore
|
|
154
166
|
|
|
@@ -158,6 +170,8 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
158
170
|
assert at_least > 0, "at_least must be greater than 0"
|
|
159
171
|
assert at_most is None or at_least <= at_most, "at_least must be less than or equal to at_most"
|
|
160
172
|
def many_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ManyResult[GenResult[T]], GenState[T]]]:
|
|
173
|
+
wrapper = input.wrapper()
|
|
174
|
+
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
161
175
|
if input.pruned:
|
|
162
176
|
upper = at_most if at_most is not None else at_least + 2
|
|
163
177
|
count = input.rng("many").randint(at_least, upper)
|
|
@@ -169,7 +183,7 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
169
183
|
ret.append(value)
|
|
170
184
|
case Left(_):
|
|
171
185
|
pass
|
|
172
|
-
return Right((ManyResult(tuple(ret)), input))
|
|
186
|
+
return Right((wrapper(ManyResult(tuple(ret))), input))
|
|
173
187
|
else:
|
|
174
188
|
ret = []
|
|
175
189
|
for index in range(input.how_many):
|
|
@@ -190,7 +204,7 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
190
204
|
this=self,
|
|
191
205
|
state=input.down(index)
|
|
192
206
|
))
|
|
193
|
-
return Right((ManyResult(tuple(ret)), input))
|
|
207
|
+
return Right((wrapper(ManyResult(tuple(ret))), input))
|
|
194
208
|
return self.__class__(many_run, name=f"many({self.name})") # type: ignore
|
|
195
209
|
|
|
196
210
|
|
|
@@ -198,21 +212,23 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
198
212
|
other: Algebra[GenResult[T], GenState[T]]
|
|
199
213
|
) -> Algebra[OrResult[GenResult[T]], GenState[T]]:
|
|
200
214
|
def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[OrResult[GenResult[T]], GenState[T]]]:
|
|
215
|
+
wrapper = input.wrapper()
|
|
216
|
+
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
201
217
|
if input.pruned:
|
|
202
218
|
forked_input = input.fork(tag="or_else")
|
|
203
219
|
match forked_input.rng("or_else").choice((self, other)).run(forked_input, use_cache):
|
|
204
220
|
case Right((value, next_input)):
|
|
205
|
-
return Right((OrResult(value), next_input))
|
|
221
|
+
return Right((wrapper(OrResult(value)), next_input))
|
|
206
222
|
case Left(error):
|
|
207
223
|
return Left(error)
|
|
208
224
|
else:
|
|
209
225
|
match self.run(input.down(0), use_cache):
|
|
210
226
|
case Right((value, next_input)):
|
|
211
|
-
return Right((OrResult(value), next_input))
|
|
227
|
+
return Right((wrapper(OrResult(value)), next_input))
|
|
212
228
|
case Left(error):
|
|
213
229
|
match other.run(input.down(0), use_cache):
|
|
214
230
|
case Right((value, next_input)):
|
|
215
|
-
return Right((OrResult(value), next_input))
|
|
231
|
+
return Right((wrapper(OrResult(value)), next_input))
|
|
216
232
|
case Left(error):
|
|
217
233
|
return Left(error)
|
|
218
234
|
raise ValueError("or_else should always return a value or an error.")
|
|
@@ -228,6 +244,8 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
228
244
|
gen = TokenGen(token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
|
|
229
245
|
lazy_self: Algebra[GenResult[T], GenState[T]]
|
|
230
246
|
def token_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[GenResult[Token], GenState[T]]]:
|
|
247
|
+
wrapper = input.wrapper()
|
|
248
|
+
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
231
249
|
if input.pruned:
|
|
232
250
|
return Right((gen.gen(), input))
|
|
233
251
|
else:
|
|
@@ -236,7 +254,7 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
236
254
|
return Left(Error(None,
|
|
237
255
|
message=f"Expected a Token, but got {type(current)}.",
|
|
238
256
|
state=input))
|
|
239
|
-
return Right((current, input))
|
|
257
|
+
return Right((wrapper(current), input))
|
|
240
258
|
lazy_self = cls(token_run, name=cls.__name__ + f'.token({token_type or text or regex})') # type: ignore
|
|
241
259
|
return lazy_self
|
|
242
260
|
|
|
@@ -77,23 +77,23 @@ class ParserState(Generic[T], Insptectable):
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
@dataclass(frozen=True)
|
|
80
|
-
class Parser(Algebra[
|
|
80
|
+
class Parser(Algebra[T, ParserState[T]]):
|
|
81
81
|
@classmethod
|
|
82
82
|
def token(cls,
|
|
83
83
|
token_type: Optional[Enum] = None,
|
|
84
84
|
text: Optional[str] = None,
|
|
85
85
|
case_sensitive: bool = False,
|
|
86
86
|
regex: Optional[re.Pattern[str]] = None
|
|
87
|
-
)-> Algebra[
|
|
87
|
+
)-> Algebra[T, ParserState[T]]:
|
|
88
88
|
spec = TokenSpec(token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
|
|
89
|
-
def token_run(state: ParserState[T], use_cache:bool) -> Either[Any, Tuple[
|
|
89
|
+
def token_run(state: ParserState[T], use_cache:bool) -> Either[Any, Tuple[T, ParserState[T]]]:
|
|
90
90
|
if state.ended():
|
|
91
91
|
return Left(state)
|
|
92
92
|
token = state.current()
|
|
93
93
|
if token is None or not spec.is_valid(token):
|
|
94
94
|
return Left(state)
|
|
95
95
|
return Right((Token(token_type = token.token_type, text=token.text), state.advance())) # type: ignore
|
|
96
|
-
captured: Algebra[
|
|
96
|
+
captured: Algebra[T, ParserState[T]] = cls(token_run, name=cls.__name__ + f'.token({token_type}, {text})')
|
|
97
97
|
def error_fn(err: Any) -> Error:
|
|
98
98
|
if isinstance(err, ParserState):
|
|
99
99
|
return Error(message=f"Cannot match token at {err}", this=captured, state=err)
|
|
@@ -280,7 +280,7 @@ HAVING = dsl.lift(TokenType.HAVING)
|
|
|
280
280
|
HINT = dsl.lift(TokenType.HINT)
|
|
281
281
|
IGNORE = dsl.lift(TokenType.IGNORE)
|
|
282
282
|
ILIKE = dsl.lift(TokenType.ILIKE)
|
|
283
|
-
|
|
283
|
+
|
|
284
284
|
IN = dsl.lift(TokenType.IN)
|
|
285
285
|
INDEX = dsl.lift(TokenType.INDEX)
|
|
286
286
|
INNER = dsl.lift(TokenType.INNER)
|
|
@@ -301,7 +301,7 @@ LANGUAGE = dsl.lift(TokenType.LANGUAGE)
|
|
|
301
301
|
LATERAL = dsl.lift(TokenType.LATERAL)
|
|
302
302
|
LEFT = dsl.lift(TokenType.LEFT)
|
|
303
303
|
LIKE = dsl.lift(TokenType.LIKE)
|
|
304
|
-
|
|
304
|
+
|
|
305
305
|
LIMIT = dsl.lift(TokenType.LIMIT)
|
|
306
306
|
LIST = dsl.lift(TokenType.LIST)
|
|
307
307
|
LOAD = dsl.lift(TokenType.LOAD)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from syncraft.parser import literal, variable, parse, Parser
|
|
3
|
+
from syncraft.ast import AST
|
|
4
|
+
import syncraft.generator as gen
|
|
5
|
+
from typing import Any
|
|
6
|
+
from rich import print
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test1_simple_then() -> None:
|
|
10
|
+
A, B, C = literal("a"), literal("b"), literal("c")
|
|
11
|
+
syntax = A // B // C
|
|
12
|
+
sql = "a b c"
|
|
13
|
+
ast = parse(syntax(Parser), sql, dialect="sqlite")
|
|
14
|
+
print("---" * 40)
|
|
15
|
+
print(ast)
|
|
16
|
+
generated = gen.generate(syntax(gen.Generator), ast)
|
|
17
|
+
print("---" * 40)
|
|
18
|
+
print(generated)
|
|
19
|
+
assert ast == generated
|
|
20
|
+
value, bmap = generated.bimap(None)
|
|
21
|
+
print(value)
|
|
22
|
+
assert bmap(value) == generated
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test2_named_results() -> None:
|
|
26
|
+
A, B = literal("a").bind("x").bind('z'), literal("b").bind("y")
|
|
27
|
+
syntax = A // B
|
|
28
|
+
sql = "a b"
|
|
29
|
+
ast = parse(syntax(Parser), sql, dialect="sqlite")
|
|
30
|
+
print("---" * 40)
|
|
31
|
+
print(ast)
|
|
32
|
+
generated = gen.generate(syntax(gen.Generator), ast)
|
|
33
|
+
print("---" * 40)
|
|
34
|
+
print(generated)
|
|
35
|
+
assert ast == generated
|
|
36
|
+
value, bmap = generated.bimap(None)
|
|
37
|
+
print(value)
|
|
38
|
+
print(bmap(value))
|
|
39
|
+
assert bmap(value) == generated
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test3_many_literals() -> None:
|
|
43
|
+
A = literal("a")
|
|
44
|
+
syntax = A.many()
|
|
45
|
+
sql = "a a a"
|
|
46
|
+
ast = parse(syntax(Parser), sql, dialect="sqlite")
|
|
47
|
+
print("---" * 40)
|
|
48
|
+
print(ast)
|
|
49
|
+
generated = gen.generate(syntax(gen.Generator), ast)
|
|
50
|
+
print("---" * 40)
|
|
51
|
+
print(generated)
|
|
52
|
+
assert ast == generated
|
|
53
|
+
value, bmap = generated.bimap(None)
|
|
54
|
+
print(value)
|
|
55
|
+
assert bmap(value) == generated
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test4_mixed_many_named() -> None:
|
|
59
|
+
A = literal("a").bind("x")
|
|
60
|
+
B = literal("b")
|
|
61
|
+
syntax = (A | B).many()
|
|
62
|
+
sql = "a b a"
|
|
63
|
+
ast = parse(syntax(Parser), sql, dialect="sqlite")
|
|
64
|
+
print("---" * 40)
|
|
65
|
+
print(ast)
|
|
66
|
+
generated = gen.generate(syntax(gen.Generator), ast)
|
|
67
|
+
print("---" * 40)
|
|
68
|
+
print(generated)
|
|
69
|
+
assert ast == generated
|
|
70
|
+
value, bmap = generated.bimap(None)
|
|
71
|
+
print(value)
|
|
72
|
+
assert bmap(value) == generated
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test5_nested_then_many() -> None:
|
|
76
|
+
IF, THEN, END = literal("if"), literal("then"), literal("end")
|
|
77
|
+
syntax = (IF.many() // THEN.many()).many() // END
|
|
78
|
+
sql = "if if then end"
|
|
79
|
+
ast = parse(syntax(Parser), sql, dialect="sqlite")
|
|
80
|
+
print("---" * 40)
|
|
81
|
+
print(ast)
|
|
82
|
+
generated = gen.generate(syntax(gen.Generator), ast)
|
|
83
|
+
print("---" * 40)
|
|
84
|
+
print(generated)
|
|
85
|
+
# assert ast == generated
|
|
86
|
+
value, bmap = generated.bimap(None)
|
|
87
|
+
print(value)
|
|
88
|
+
assert bmap(value) == generated
|
|
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
|