syncraft 0.1.17__tar.gz → 0.1.19__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.
- {syncraft-0.1.17 → syncraft-0.1.19}/PKG-INFO +1 -1
- {syncraft-0.1.17 → syncraft-0.1.19}/pyproject.toml +1 -1
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/diagnostic.py +2 -2
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/generator.py +31 -3
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/parser.py +19 -18
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/sqlite3.py +7 -7
- syncraft-0.1.17/syncraft/dsl.py → syncraft-0.1.19/syncraft/syntax.py +56 -56
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/PKG-INFO +1 -1
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/SOURCES.txt +1 -1
- {syncraft-0.1.17 → syncraft-0.1.19}/tests/test_bimap.py +42 -30
- {syncraft-0.1.17 → syncraft-0.1.19}/tests/test_parse.py +7 -7
- {syncraft-0.1.17 → syncraft-0.1.19}/tests/test_until.py +4 -4
- {syncraft-0.1.17 → syncraft-0.1.19}/LICENSE +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/README.md +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/setup.cfg +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/__init__.py +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/algebra.py +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/ast.py +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/cmd.py +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/py.typed +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/top_level.txt +0 -0
|
@@ -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)
|
|
@@ -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
10
|
Algebra, ThenResult, Either, Left, Right, Error, Insptectable,
|
|
11
|
-
OrResult, ManyResult
|
|
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
|
|
@@ -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
|
+
|
|
@@ -11,7 +11,7 @@ from syncraft.algebra import (
|
|
|
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
|
|
|
@@ -187,15 +187,16 @@ class Parser(Algebra[T, ParserState[T]]):
|
|
|
187
187
|
return Right((tuple(tokens), tmp_state))
|
|
188
188
|
return cls(until_run, name=cls.__name__ + '.until')
|
|
189
189
|
|
|
190
|
-
def sqlglot(parser:
|
|
191
|
-
dialect: str) ->
|
|
190
|
+
def sqlglot(parser: Syntax[Any, Any],
|
|
191
|
+
dialect: str) -> Syntax[List[exp.Expression], ParserState[Any]]:
|
|
192
192
|
gp = GlotParser(dialect=dialect)
|
|
193
193
|
return parser.map(lambda tokens: [e for e in gp.parse(raw_tokens=tokens) if e is not None])
|
|
194
194
|
|
|
195
195
|
|
|
196
|
-
def parse(
|
|
196
|
+
def parse(syntax: Syntax[Any, Any],
|
|
197
197
|
sql: str,
|
|
198
198
|
dialect: str) -> AST[Any] | Any:
|
|
199
|
+
parser = syntax(Parser)
|
|
199
200
|
input: ParserState[Token] = token_state(sql, dialect=dialect)
|
|
200
201
|
result = parser.run(input, True)
|
|
201
202
|
if isinstance(result, Right):
|
|
@@ -212,34 +213,34 @@ def token(token_type: Optional[Enum] = None,
|
|
|
212
213
|
text: Optional[str] = None,
|
|
213
214
|
case_sensitive: bool = False,
|
|
214
215
|
regex: Optional[re.Pattern[str]] = None
|
|
215
|
-
) ->
|
|
216
|
+
) -> Syntax[Any, Any]:
|
|
216
217
|
token_type_txt = token_type.name if token_type is not None else None
|
|
217
218
|
token_value_txt = text if text is not None else None
|
|
218
219
|
msg = 'token(' + ','.join([x for x in [token_type_txt, token_value_txt, str(regex)] if x is not None]) + ')'
|
|
219
|
-
return
|
|
220
|
+
return Syntax(
|
|
220
221
|
lambda cls: cls.factory('token', token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
|
|
221
222
|
).describe(name=msg, fixity='prefix')
|
|
222
223
|
|
|
223
224
|
|
|
224
|
-
def identifier(value: str | None = None) ->
|
|
225
|
+
def identifier(value: str | None = None) -> Syntax[Any, Any]:
|
|
225
226
|
if value is None:
|
|
226
227
|
return token(TokenType.IDENTIFIER)
|
|
227
228
|
else:
|
|
228
229
|
return token(TokenType.IDENTIFIER, text=value)
|
|
229
230
|
|
|
230
|
-
def variable(value: str | None = None) ->
|
|
231
|
+
def variable(value: str | None = None) -> Syntax[Any, Any]:
|
|
231
232
|
if value is None:
|
|
232
233
|
return token(TokenType.VAR)
|
|
233
234
|
else:
|
|
234
235
|
return token(TokenType.VAR, text=value)
|
|
235
236
|
|
|
236
|
-
def literal(lit: str) ->
|
|
237
|
+
def literal(lit: str) -> Syntax[Any, Any]:
|
|
237
238
|
return token(token_type=None, text=lit, case_sensitive=True)
|
|
238
239
|
|
|
239
|
-
def regex(regex: re.Pattern[str]) ->
|
|
240
|
+
def regex(regex: re.Pattern[str]) -> Syntax[Any, Any]:
|
|
240
241
|
return token(token_type=None, regex=regex, case_sensitive=True)
|
|
241
242
|
|
|
242
|
-
def lift(value: Any)->
|
|
243
|
+
def lift(value: Any)-> Syntax[Any, Any]:
|
|
243
244
|
if isinstance(value, str):
|
|
244
245
|
return literal(value)
|
|
245
246
|
elif isinstance(value, re.Pattern):
|
|
@@ -247,22 +248,22 @@ def lift(value: Any)-> DSL[Any, Any]:
|
|
|
247
248
|
elif isinstance(value, Enum):
|
|
248
249
|
return token(value)
|
|
249
250
|
else:
|
|
250
|
-
return
|
|
251
|
+
return Syntax(lambda cls: cls.success(value))
|
|
251
252
|
|
|
252
|
-
def number() ->
|
|
253
|
+
def number() -> Syntax[Any, Any]:
|
|
253
254
|
return token(TokenType.NUMBER)
|
|
254
255
|
|
|
255
256
|
|
|
256
|
-
def string() ->
|
|
257
|
+
def string() -> Syntax[Any, Any]:
|
|
257
258
|
return token(TokenType.STRING)
|
|
258
259
|
|
|
259
260
|
|
|
260
261
|
|
|
261
|
-
def until(*open_close: Tuple[
|
|
262
|
-
terminator: Optional[
|
|
262
|
+
def until(*open_close: Tuple[Syntax[Tuple[T, ...] | T, ParserState[T]], Syntax[Tuple[T, ...] | T, ParserState[T]]],
|
|
263
|
+
terminator: Optional[Syntax[Tuple[T, ...] | T, ParserState[T]]] = None,
|
|
263
264
|
inclusive: bool = True,
|
|
264
|
-
strict: bool = True) ->
|
|
265
|
-
return
|
|
265
|
+
strict: bool = True) -> Syntax[Any, Any]:
|
|
266
|
+
return Syntax(
|
|
266
267
|
lambda cls: cls.factory('until',
|
|
267
268
|
*[(left.alg(cls), right.alg(cls)) for left, right in open_close],
|
|
268
269
|
terminator=terminator.alg(cls) if terminator else None,
|
|
@@ -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)
|
|
@@ -67,11 +67,11 @@ class Description(Insptectable):
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
@dataclass(frozen=True)
|
|
70
|
-
class
|
|
70
|
+
class Syntax(Generic[A, S], Insptectable):
|
|
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)
|
|
@@ -6,11 +6,11 @@ syncraft/algebra.py
|
|
|
6
6
|
syncraft/ast.py
|
|
7
7
|
syncraft/cmd.py
|
|
8
8
|
syncraft/diagnostic.py
|
|
9
|
-
syncraft/dsl.py
|
|
10
9
|
syncraft/generator.py
|
|
11
10
|
syncraft/parser.py
|
|
12
11
|
syncraft/py.typed
|
|
13
12
|
syncraft/sqlite3.py
|
|
13
|
+
syncraft/syntax.py
|
|
14
14
|
syncraft.egg-info/PKG-INFO
|
|
15
15
|
syncraft.egg-info/SOURCES.txt
|
|
16
16
|
syncraft.egg-info/dependency_links.txt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from syncraft.algebra import NamedResult, Error, ManyResult, OrResult, ThenResult, ThenKind
|
|
3
|
-
from syncraft.parser import literal, parse
|
|
3
|
+
from syncraft.parser import literal, parse
|
|
4
4
|
import syncraft.generator as gen
|
|
5
5
|
from syncraft.generator import TokenGen
|
|
6
6
|
from rich import print
|
|
@@ -10,10 +10,10 @@ def test1_simple_then() -> None:
|
|
|
10
10
|
A, B, C = literal("a"), literal("b"), literal("c")
|
|
11
11
|
syntax = A // B // C
|
|
12
12
|
sql = "a b c"
|
|
13
|
-
ast = parse(syntax
|
|
13
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
14
14
|
print("---" * 40)
|
|
15
15
|
print(ast)
|
|
16
|
-
generated = gen.generate(syntax
|
|
16
|
+
generated = gen.generate(syntax, ast)
|
|
17
17
|
print("---" * 40)
|
|
18
18
|
print(generated)
|
|
19
19
|
assert ast == generated
|
|
@@ -26,10 +26,10 @@ def test2_named_results() -> None:
|
|
|
26
26
|
A, B = literal("a").bind("x").bind('z'), literal("b").bind("y")
|
|
27
27
|
syntax = A // B
|
|
28
28
|
sql = "a b"
|
|
29
|
-
ast = parse(syntax
|
|
29
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
30
30
|
print("---" * 40)
|
|
31
31
|
print(ast)
|
|
32
|
-
generated = gen.generate(syntax
|
|
32
|
+
generated = gen.generate(syntax, ast)
|
|
33
33
|
print("---" * 40)
|
|
34
34
|
print(generated)
|
|
35
35
|
assert ast == generated
|
|
@@ -43,10 +43,10 @@ def test3_many_literals() -> None:
|
|
|
43
43
|
A = literal("a")
|
|
44
44
|
syntax = A.many()
|
|
45
45
|
sql = "a a a"
|
|
46
|
-
ast = parse(syntax
|
|
46
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
47
47
|
print("---" * 40)
|
|
48
48
|
print(ast)
|
|
49
|
-
generated = gen.generate(syntax
|
|
49
|
+
generated = gen.generate(syntax, ast)
|
|
50
50
|
print("---" * 40)
|
|
51
51
|
print(generated)
|
|
52
52
|
assert ast == generated
|
|
@@ -60,10 +60,10 @@ def test4_mixed_many_named() -> None:
|
|
|
60
60
|
B = literal("b")
|
|
61
61
|
syntax = (A | B).many()
|
|
62
62
|
sql = "a b a"
|
|
63
|
-
ast = parse(syntax
|
|
63
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
64
64
|
print("---" * 40)
|
|
65
65
|
print(ast)
|
|
66
|
-
generated = gen.generate(syntax
|
|
66
|
+
generated = gen.generate(syntax, ast)
|
|
67
67
|
print("---" * 40)
|
|
68
68
|
print(generated)
|
|
69
69
|
assert ast == generated
|
|
@@ -76,10 +76,10 @@ def test5_nested_then_many() -> None:
|
|
|
76
76
|
IF, THEN, END = literal("if"), literal("then"), literal("end")
|
|
77
77
|
syntax = (IF.many() // THEN.many()).many() // END
|
|
78
78
|
sql = "if if then end"
|
|
79
|
-
ast = parse(syntax
|
|
79
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
80
80
|
print("---" * 40)
|
|
81
81
|
print(ast)
|
|
82
|
-
generated = gen.generate(syntax
|
|
82
|
+
generated = gen.generate(syntax, ast)
|
|
83
83
|
print("---" * 40)
|
|
84
84
|
print(generated)
|
|
85
85
|
# assert ast == generated
|
|
@@ -93,9 +93,9 @@ def test_then_flatten():
|
|
|
93
93
|
A, B, C = literal("a"), literal("b"), literal("c")
|
|
94
94
|
syntax = A + (B + C)
|
|
95
95
|
sql = "a b c"
|
|
96
|
-
ast = parse(syntax
|
|
96
|
+
ast = parse(syntax, sql, dialect='sqlite')
|
|
97
97
|
print(ast)
|
|
98
|
-
generated = gen.generate(syntax
|
|
98
|
+
generated = gen.generate(syntax, ast)
|
|
99
99
|
assert ast == generated
|
|
100
100
|
value, bmap = ast.bimap(None)
|
|
101
101
|
assert bmap(value) == ast
|
|
@@ -108,9 +108,9 @@ def test_named_in_then():
|
|
|
108
108
|
C = literal("c").bind("third")
|
|
109
109
|
syntax = A + B + C
|
|
110
110
|
sql = "a b c"
|
|
111
|
-
ast = parse(syntax
|
|
111
|
+
ast = parse(syntax, sql, dialect='sqlite')
|
|
112
112
|
print(ast)
|
|
113
|
-
generated = gen.generate(syntax
|
|
113
|
+
generated = gen.generate(syntax, ast)
|
|
114
114
|
assert ast == generated
|
|
115
115
|
value, bmap = ast.bimap(None)
|
|
116
116
|
assert isinstance(value, tuple)
|
|
@@ -123,9 +123,9 @@ def test_named_in_many():
|
|
|
123
123
|
A = literal("x").bind("x")
|
|
124
124
|
syntax = A.many()
|
|
125
125
|
sql = "x x x"
|
|
126
|
-
ast = parse(syntax
|
|
126
|
+
ast = parse(syntax, sql, dialect='sqlite')
|
|
127
127
|
print(ast)
|
|
128
|
-
generated = gen.generate(syntax
|
|
128
|
+
generated = gen.generate(syntax, ast)
|
|
129
129
|
assert ast == generated
|
|
130
130
|
value, bmap = ast.bimap(None)
|
|
131
131
|
assert isinstance(value, list)
|
|
@@ -138,9 +138,9 @@ def test_named_in_or():
|
|
|
138
138
|
B = literal("b").bind("b")
|
|
139
139
|
syntax = A | B
|
|
140
140
|
sql = "b"
|
|
141
|
-
ast = parse(syntax
|
|
141
|
+
ast = parse(syntax, sql, dialect='sqlite')
|
|
142
142
|
print(ast)
|
|
143
|
-
generated = gen.generate(syntax
|
|
143
|
+
generated = gen.generate(syntax, ast)
|
|
144
144
|
assert ast == generated
|
|
145
145
|
value, bmap = ast.bimap(None)
|
|
146
146
|
assert isinstance(value, NamedResult)
|
|
@@ -157,9 +157,9 @@ def test_deep_mix():
|
|
|
157
157
|
C = literal("c").bind("c")
|
|
158
158
|
syntax = ((A + B) | C).many() + B
|
|
159
159
|
sql = "a b a b c b"
|
|
160
|
-
ast = parse(syntax
|
|
160
|
+
ast = parse(syntax, sql, dialect='sqlite')
|
|
161
161
|
print(ast)
|
|
162
|
-
generated = gen.generate(syntax
|
|
162
|
+
generated = gen.generate(syntax, ast)
|
|
163
163
|
print('---' * 40)
|
|
164
164
|
print(generated)
|
|
165
165
|
assert ast == generated
|
|
@@ -171,7 +171,7 @@ def test_empty_many() -> None:
|
|
|
171
171
|
A = literal("a")
|
|
172
172
|
syntax = A.many() # This should allow empty matches
|
|
173
173
|
sql = ""
|
|
174
|
-
ast = parse(syntax
|
|
174
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
175
175
|
assert isinstance(ast, Error)
|
|
176
176
|
|
|
177
177
|
|
|
@@ -180,7 +180,7 @@ def test_backtracking_many() -> None:
|
|
|
180
180
|
B = literal("b")
|
|
181
181
|
syntax = (A.many() + B) # must not eat the final "a" needed for B
|
|
182
182
|
sql = "a a a a b"
|
|
183
|
-
ast = parse(syntax
|
|
183
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
184
184
|
value, bmap = ast.bimap(None)
|
|
185
185
|
assert value[-1] == TokenGen.from_string("b")
|
|
186
186
|
|
|
@@ -190,7 +190,7 @@ def test_deep_nesting() -> None:
|
|
|
190
190
|
for _ in range(100):
|
|
191
191
|
syntax = syntax + A
|
|
192
192
|
sql = " " .join("a" for _ in range(101))
|
|
193
|
-
ast = parse(syntax
|
|
193
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
194
194
|
assert ast is not None
|
|
195
195
|
|
|
196
196
|
|
|
@@ -198,7 +198,7 @@ def test_nested_many() -> None:
|
|
|
198
198
|
A = literal("a")
|
|
199
199
|
syntax = (A.many().many()) # groups of groups of "a"
|
|
200
200
|
sql = "a a a"
|
|
201
|
-
ast = parse(syntax
|
|
201
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
202
202
|
assert isinstance(ast.focus, ManyResult)
|
|
203
203
|
|
|
204
204
|
|
|
@@ -206,7 +206,7 @@ def test_named_many() -> None:
|
|
|
206
206
|
A = literal("a").bind("alpha")
|
|
207
207
|
syntax = A.many()
|
|
208
208
|
sql = "a a"
|
|
209
|
-
ast = parse(syntax
|
|
209
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
210
210
|
# Expect [NamedResult("alpha", "a"), NamedResult("alpha", "a")]
|
|
211
211
|
flattened, _ = ast.bimap(None)
|
|
212
212
|
assert all(isinstance(x, NamedResult) for x in flattened)
|
|
@@ -217,7 +217,7 @@ def test_or_named() -> None:
|
|
|
217
217
|
B = literal("b").bind("y")
|
|
218
218
|
syntax = A | B
|
|
219
219
|
sql = "b"
|
|
220
|
-
ast = parse(syntax
|
|
220
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
221
221
|
# Either NamedResult("y", "b") or just "b", depending on your design
|
|
222
222
|
assert isinstance(ast.focus, OrResult)
|
|
223
223
|
value, _ = ast.bimap(None)
|
|
@@ -230,7 +230,7 @@ def test_then_associativity() -> None:
|
|
|
230
230
|
C = literal("c")
|
|
231
231
|
syntax = A + B + C
|
|
232
232
|
sql = "a b c"
|
|
233
|
-
ast = parse(syntax
|
|
233
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
234
234
|
# Should be ThenResult(ThenResult(A,B),C)
|
|
235
235
|
assert ast.focus == ThenResult(kind=ThenKind.BOTH,
|
|
236
236
|
left=ThenResult(kind=ThenKind.BOTH,
|
|
@@ -244,7 +244,7 @@ def test_ambiguous() -> None:
|
|
|
244
244
|
B = literal("a") + literal("b")
|
|
245
245
|
syntax = A | B
|
|
246
246
|
sql = "a"
|
|
247
|
-
ast = parse(syntax
|
|
247
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
248
248
|
# Does it prefer A (shorter) or B (fails)? Depends on design.
|
|
249
249
|
assert ast.focus == OrResult(TokenGen.from_string("a"))
|
|
250
250
|
|
|
@@ -256,6 +256,18 @@ def test_combo() -> None:
|
|
|
256
256
|
syntax = ((A + B).many() | C) + B
|
|
257
257
|
sql = "a b a b c b"
|
|
258
258
|
# Should fail, as we discussed earlier
|
|
259
|
-
ast = parse(syntax
|
|
259
|
+
ast = parse(syntax, sql, dialect="sqlite")
|
|
260
260
|
assert isinstance(ast, Error)
|
|
261
261
|
|
|
262
|
+
|
|
263
|
+
def test_optional():
|
|
264
|
+
A = literal("a").bind("a")
|
|
265
|
+
syntax = A.optional()
|
|
266
|
+
ast1 = parse(syntax, "", dialect="sqlite")
|
|
267
|
+
v1, _ = ast1.bimap(None)
|
|
268
|
+
assert v1 is None
|
|
269
|
+
ast2 = parse(syntax, "a", dialect="sqlite")
|
|
270
|
+
v2, _ = ast2.bimap(None)
|
|
271
|
+
assert v2 == NamedResult(name='a', value=TokenGen.from_string('a'))
|
|
272
|
+
|
|
273
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from syncraft.parser import AST, literal, variable, parse
|
|
1
|
+
from syncraft.parser import AST, literal, variable, parse
|
|
2
2
|
import syncraft.generator as gen
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
@@ -12,16 +12,16 @@ var = variable()
|
|
|
12
12
|
def test_between()->None:
|
|
13
13
|
sql = "then if then"
|
|
14
14
|
syntax = IF.between(THEN, THEN)
|
|
15
|
-
ast:AST[Any] = parse(syntax
|
|
16
|
-
generated = gen.generate(syntax
|
|
15
|
+
ast:AST[Any] = parse(syntax, sql, dialect='sqlite')
|
|
16
|
+
generated = gen.generate(syntax, ast)
|
|
17
17
|
assert ast == generated, "Parsed and generated results do not match."
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def test_sep_by()->None:
|
|
21
21
|
sql = "if then if then if then if"
|
|
22
22
|
syntax = IF.sep_by(THEN)
|
|
23
|
-
ast:AST[Any] = parse(syntax
|
|
24
|
-
generated = gen.generate(syntax
|
|
23
|
+
ast:AST[Any] = parse(syntax, sql, dialect='sqlite')
|
|
24
|
+
generated = gen.generate(syntax, ast)
|
|
25
25
|
assert ast == generated, "Parsed and generated results do not match."
|
|
26
26
|
|
|
27
27
|
def test_many_or()->None:
|
|
@@ -30,6 +30,6 @@ def test_many_or()->None:
|
|
|
30
30
|
END = literal("end")
|
|
31
31
|
syntax = (IF.many() | THEN.many()).many() // END
|
|
32
32
|
sql = "if if then end"
|
|
33
|
-
ast:AST[Any] = parse(syntax
|
|
34
|
-
generated = gen.generate(syntax
|
|
33
|
+
ast:AST[Any] = parse(syntax, sql, dialect='sqlite')
|
|
34
|
+
generated = gen.generate(syntax, ast)
|
|
35
35
|
assert ast == generated, "Parsed and generated results do not match."
|
|
@@ -10,7 +10,7 @@ LB, RB = literal("["), literal("]")
|
|
|
10
10
|
def test_until_accepts_proper_nesting() -> None:
|
|
11
11
|
sql = "([])"
|
|
12
12
|
syntax = until((LP, RP), (LB, RB))
|
|
13
|
-
ast: AST[Any] | Any = parse(syntax
|
|
13
|
+
ast: AST[Any] | Any = parse(syntax, sql, dialect="sqlite")
|
|
14
14
|
assert isinstance(ast, AST), f"Expected AST for proper nesting, got {ast}"
|
|
15
15
|
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ def test_until_rejects_mismatched_pairs() -> None:
|
|
|
18
18
|
# Mismatched: ( ] should fail immediately
|
|
19
19
|
sql = "(]"
|
|
20
20
|
syntax = until((LP, RP), (LB, RB))
|
|
21
|
-
res = parse(syntax
|
|
21
|
+
res = parse(syntax, sql, dialect="sqlite")
|
|
22
22
|
from syncraft.algebra import Error
|
|
23
23
|
assert isinstance(res, Error), "Mismatched pairs should be rejected with an Error"
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@ def test_until_rejects_unterminated_group() -> None:
|
|
|
26
26
|
# Unterminated: ( ... EOF
|
|
27
27
|
sql = "("
|
|
28
28
|
syntax = until((LP, RP))
|
|
29
|
-
res = parse(syntax
|
|
29
|
+
res = parse(syntax, sql, dialect="sqlite")
|
|
30
30
|
from syncraft.algebra import Error
|
|
31
31
|
assert isinstance(res, Error), "Unterminated group should be rejected with an Error"
|
|
32
32
|
|
|
@@ -35,6 +35,6 @@ def test_until_rejects_crossing_pairs() -> None:
|
|
|
35
35
|
sql = "([)]"
|
|
36
36
|
syntax = until((LP, RP), (LB, RB))
|
|
37
37
|
# Use postgres dialect so [ and ] are tokenized distinctly (not as bracketed identifier)
|
|
38
|
-
res = parse(syntax
|
|
38
|
+
res = parse(syntax, sql, dialect="postgres")
|
|
39
39
|
from syncraft.algebra import Error
|
|
40
40
|
assert isinstance(res, Error), "Crossing pairs should be rejected with an Error"
|
|
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
|