syncraft 0.2.0__tar.gz → 0.2.1__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.2.0 → syncraft-0.2.1}/PKG-INFO +1 -1
- {syncraft-0.2.0 → syncraft-0.2.1}/pyproject.toml +1 -1
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/algebra.py +5 -2
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/generator.py +9 -10
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/parser.py +10 -13
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/syntax.py +15 -49
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft.egg-info/PKG-INFO +1 -1
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft.egg-info/SOURCES.txt +1 -2
- {syncraft-0.2.0 → syncraft-0.2.1}/tests/test_bimap.py +58 -45
- {syncraft-0.2.0 → syncraft-0.2.1}/tests/test_find.py +1 -1
- {syncraft-0.2.0 → syncraft-0.2.1}/tests/test_parse.py +12 -9
- {syncraft-0.2.0 → syncraft-0.2.1}/tests/test_to.py +7 -5
- {syncraft-0.2.0 → syncraft-0.2.1}/tests/test_until.py +4 -4
- syncraft-0.2.0/tests/test_var.py +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/LICENSE +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/README.md +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/setup.cfg +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/__init__.py +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/ast.py +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/constraint.py +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/diagnostic.py +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/finder.py +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/py.typed +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft/sqlite3.py +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.2.0 → syncraft-0.2.1}/syncraft.egg-info/top_level.txt +0 -0
|
@@ -7,7 +7,6 @@ from typing import (
|
|
|
7
7
|
import traceback
|
|
8
8
|
from dataclasses import dataclass, replace
|
|
9
9
|
from weakref import WeakKeyDictionary
|
|
10
|
-
from abc import ABC
|
|
11
10
|
from syncraft.ast import ThenKind, Then, Choice, Many, ChoiceKind, shallow_dict
|
|
12
11
|
from syncraft.constraint import Bindable
|
|
13
12
|
|
|
@@ -75,12 +74,16 @@ class Error:
|
|
|
75
74
|
|
|
76
75
|
|
|
77
76
|
@dataclass(frozen=True)
|
|
78
|
-
class Algebra(
|
|
77
|
+
class Algebra(Generic[A, S]):
|
|
79
78
|
######################################################## shared among all subclasses ########################################################
|
|
80
79
|
run_f: Callable[[S, bool], Either[Any, Tuple[A, S]]]
|
|
81
80
|
name: Hashable
|
|
82
81
|
_cache: ClassVar[WeakKeyDictionary[Any, Dict[Any, object | Either[Any, Tuple[Any, Any]]]]] = WeakKeyDictionary()
|
|
83
82
|
|
|
83
|
+
@classmethod
|
|
84
|
+
def state(cls, *args:Any, **kwargs:Any)->Optional[S]:
|
|
85
|
+
return None
|
|
86
|
+
|
|
84
87
|
def named(self, name: Hashable) -> 'Algebra[A, S]':
|
|
85
88
|
return replace(self, name=name)
|
|
86
89
|
|
|
@@ -16,14 +16,14 @@ from syncraft.ast import (
|
|
|
16
16
|
Choice, Many, ChoiceKind,
|
|
17
17
|
Then, ThenKind, Marked
|
|
18
18
|
)
|
|
19
|
-
|
|
19
|
+
from syncraft.constraint import FrozenDict
|
|
20
20
|
from syncraft.syntax import Syntax
|
|
21
21
|
from sqlglot import TokenType
|
|
22
22
|
import re
|
|
23
23
|
import rstr
|
|
24
24
|
from functools import lru_cache
|
|
25
25
|
import random
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
from syncraft.constraint import Bindable
|
|
28
28
|
|
|
29
29
|
T = TypeVar('T', bound=TokenProtocol)
|
|
@@ -144,6 +144,10 @@ class TokenGen(TokenSpec):
|
|
|
144
144
|
|
|
145
145
|
@dataclass(frozen=True)
|
|
146
146
|
class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
147
|
+
@classmethod
|
|
148
|
+
def state(cls, ast: Optional[ParseResult[T]] = None, seed: int = 0, restore_pruned: bool = False)->GenState[T]:
|
|
149
|
+
return GenState.from_ast(ast=ast, seed=seed, restore_pruned=restore_pruned)
|
|
150
|
+
|
|
147
151
|
def flat_map(self, f: Callable[[ParseResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
|
|
148
152
|
def flat_map_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
|
|
149
153
|
try:
|
|
@@ -293,14 +297,9 @@ class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
|
293
297
|
def generate(syntax: Syntax[Any, Any],
|
|
294
298
|
data: Optional[ParseResult[Any]] = None,
|
|
295
299
|
seed: int = 0,
|
|
296
|
-
restore_pruned: bool = False) -> AST | Any:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
result = gen.run(state, use_cache=False)
|
|
300
|
-
if isinstance(result, Right):
|
|
301
|
-
return result.value[0]
|
|
302
|
-
assert isinstance(result, Left), "Generator must return Either[Any, Tuple[Any, Any]]"
|
|
303
|
-
return result.value
|
|
300
|
+
restore_pruned: bool = False) -> Tuple[AST, FrozenDict[str, AST]] | Tuple[Any, None]:
|
|
301
|
+
from syncraft.syntax import run
|
|
302
|
+
return run(syntax, Generator, False, ast=data, seed=seed, restore_pruned=restore_pruned)
|
|
304
303
|
|
|
305
304
|
|
|
306
305
|
|
|
@@ -5,6 +5,7 @@ from typing import (
|
|
|
5
5
|
Optional, List, Any, Tuple, TypeVar,
|
|
6
6
|
Generic
|
|
7
7
|
)
|
|
8
|
+
from syncraft.constraint import FrozenDict
|
|
8
9
|
from syncraft.algebra import (
|
|
9
10
|
Either, Left, Right, Error, Algebra
|
|
10
11
|
)
|
|
@@ -67,6 +68,11 @@ class ParserState(Bindable, Generic[T]):
|
|
|
67
68
|
|
|
68
69
|
@dataclass(frozen=True)
|
|
69
70
|
class Parser(Algebra[T, ParserState[T]]):
|
|
71
|
+
@classmethod
|
|
72
|
+
def state(cls, sql: str, dialect: str) -> ParserState[Token]:
|
|
73
|
+
tokens = tuple([Token(token_type=token.token_type, text=token.text) for token in tokenize(sql, dialect=dialect)])
|
|
74
|
+
return ParserState.from_tokens(tokens)
|
|
75
|
+
|
|
70
76
|
@classmethod
|
|
71
77
|
def token(cls,
|
|
72
78
|
token_type: Optional[Enum] = None,
|
|
@@ -182,21 +188,12 @@ def sqlglot(parser: Syntax[Any, Any],
|
|
|
182
188
|
return parser.map(lambda tokens: [e for e in gp.parse(raw_tokens=tokens) if e is not None])
|
|
183
189
|
|
|
184
190
|
|
|
185
|
-
def parse(syntax: Syntax[Any, Any],
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
input: ParserState[Token] = token_state(sql, dialect=dialect)
|
|
190
|
-
result = parser.run(input, True)
|
|
191
|
-
if isinstance(result, Right):
|
|
192
|
-
return result.value[0]
|
|
193
|
-
assert isinstance(result, Left), "Parser must return Either[E, Tuple[A, S]]"
|
|
194
|
-
return result.value
|
|
191
|
+
def parse(syntax: Syntax[Any, Any], sql: str, dialect: str) -> Tuple[AST, FrozenDict[str, AST]] | Tuple[Any, None]:
|
|
192
|
+
from syncraft.syntax import run
|
|
193
|
+
return run(syntax, Parser, True, sql=sql, dialect=dialect)
|
|
194
|
+
|
|
195
195
|
|
|
196
196
|
|
|
197
|
-
def token_state(sql: str, dialect: str) -> ParserState[Token]:
|
|
198
|
-
tokens = tuple([Token(token_type=token.token_type, text=token.text) for token in tokenize(sql, dialect=dialect)])
|
|
199
|
-
return ParserState.from_tokens(tokens)
|
|
200
197
|
|
|
201
198
|
def token(token_type: Optional[Enum] = None,
|
|
202
199
|
text: Optional[str] = None,
|
|
@@ -2,17 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
4
|
Optional, Any, TypeVar, Generic, Callable, Tuple, cast,
|
|
5
|
-
Type, Literal, List
|
|
5
|
+
Type, Literal, List
|
|
6
6
|
)
|
|
7
7
|
from dataclasses import dataclass, field, replace
|
|
8
8
|
from functools import reduce
|
|
9
|
-
from syncraft.algebra import Algebra, Error, Either, Right
|
|
10
|
-
from syncraft.constraint import Bindable
|
|
9
|
+
from syncraft.algebra import Algebra, Error, Either, Right, Left
|
|
10
|
+
from syncraft.constraint import Bindable, FrozenDict
|
|
11
11
|
from syncraft.ast import Then, ThenKind, Marked, Choice, Many, ChoiceKind, Nothing, Collect, E, Collector
|
|
12
12
|
from types import MethodType, FunctionType
|
|
13
13
|
import keyword
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
|
|
17
17
|
def valid_name(name: str) -> bool:
|
|
18
18
|
return (name.isidentifier()
|
|
@@ -319,49 +319,15 @@ def success(value: Any) -> Syntax[Any, Any]:
|
|
|
319
319
|
def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
320
320
|
return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(Nothing())
|
|
321
321
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
def is_named_parser(x: Any) -> bool:
|
|
334
|
-
return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax)
|
|
335
|
-
|
|
336
|
-
def to_parser(x: Syntax[Any, S] | Tuple[str, Syntax[Any, S]])->Syntax[Any, S]:
|
|
337
|
-
if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax):
|
|
338
|
-
if isinstance(x[0], str):
|
|
339
|
-
|
|
340
|
-
return x[1].bind(x[0])
|
|
341
|
-
else:
|
|
342
|
-
raise ValueError(f"Invalid variable type(must be str | Variable): {x[0]}", x)
|
|
343
|
-
elif isinstance(x, Syntax):
|
|
344
|
-
return x
|
|
345
|
-
else:
|
|
346
|
-
raise ValueError(f"Invalid parser or tuple: {x}", x)
|
|
347
|
-
ret: Optional[Syntax[Any, S]] = None
|
|
348
|
-
has_data = False
|
|
349
|
-
for p in parsers:
|
|
350
|
-
just_parser = to_parser(p)
|
|
351
|
-
if has_data:
|
|
352
|
-
if ret is not None:
|
|
353
|
-
if is_named_parser(p):
|
|
354
|
-
ret = ret + just_parser
|
|
355
|
-
else:
|
|
356
|
-
ret = ret // just_parser
|
|
357
|
-
else:
|
|
358
|
-
ret = just_parser
|
|
359
|
-
else:
|
|
360
|
-
has_data = is_named_parser(p)
|
|
361
|
-
if ret is None:
|
|
362
|
-
ret = just_parser
|
|
363
|
-
else:
|
|
364
|
-
ret = ret >> just_parser
|
|
365
|
-
|
|
366
|
-
return ret if ret is not None else success(Nothing())
|
|
322
|
+
def run(syntax: Syntax[A, S], alg: Type[Algebra[A, S]], use_cache:bool, *args: Any, **kwargs: Any) -> Tuple[Any, FrozenDict[str, Any]] | Tuple[Any, None]:
|
|
323
|
+
parser = syntax(alg)
|
|
324
|
+
input: Optional[S] = alg.state(*args, **kwargs)
|
|
325
|
+
if input:
|
|
326
|
+
result = parser.run(input, use_cache=use_cache)
|
|
327
|
+
if isinstance(result, Right):
|
|
328
|
+
return result.value[0], result.value[1].binding.bound()
|
|
329
|
+
assert isinstance(result, Left), "Algebra must return Either[E, Tuple[A, S]]"
|
|
330
|
+
return result.value, None
|
|
331
|
+
else:
|
|
332
|
+
return Error(this=None, message="Algebra failed to create initial state"), None
|
|
367
333
|
|
|
@@ -12,31 +12,33 @@ def test1_simple_then() -> None:
|
|
|
12
12
|
A, B, C = literal("a"), literal("b"), literal("c")
|
|
13
13
|
syntax = A // B // C
|
|
14
14
|
sql = "a b c"
|
|
15
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
15
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
16
16
|
print("---" * 40)
|
|
17
17
|
print(ast)
|
|
18
|
-
generated = gen.generate(syntax, ast)
|
|
18
|
+
generated, bound = gen.generate(syntax, ast)
|
|
19
19
|
print("---" * 40)
|
|
20
20
|
print(generated)
|
|
21
21
|
assert ast == generated
|
|
22
22
|
value, bmap = generated.bimap()
|
|
23
23
|
print(value)
|
|
24
|
-
|
|
24
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
25
|
+
assert u == generated
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def test2_named_results() -> None:
|
|
28
29
|
A, B = literal("a").mark("x").mark('z'), literal("b").mark("y")
|
|
29
30
|
syntax = A // B
|
|
30
31
|
sql = "a b"
|
|
31
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
32
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
32
33
|
print("---" * 40)
|
|
33
34
|
print(ast)
|
|
34
|
-
generated = gen.generate(syntax, ast)
|
|
35
|
+
generated, bound = gen.generate(syntax, ast)
|
|
35
36
|
print("---" * 40)
|
|
36
37
|
print(generated)
|
|
37
38
|
assert ast == generated
|
|
38
39
|
value, bmap = generated.bimap()
|
|
39
|
-
|
|
40
|
+
u,v = gen.generate(syntax, bmap(value))
|
|
41
|
+
assert u == generated
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
|
|
@@ -44,15 +46,16 @@ def test3_many_literals() -> None:
|
|
|
44
46
|
A = literal("a")
|
|
45
47
|
syntax = A.many()
|
|
46
48
|
sql = "a a a"
|
|
47
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
49
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
48
50
|
print("---" * 40)
|
|
49
51
|
print(ast)
|
|
50
|
-
generated = gen.generate(syntax, ast)
|
|
52
|
+
generated, bound = gen.generate(syntax, ast)
|
|
51
53
|
print("---" * 40)
|
|
52
54
|
print(generated)
|
|
53
55
|
assert ast == generated
|
|
54
56
|
value, bmap = generated.bimap()
|
|
55
|
-
|
|
57
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
58
|
+
assert u == generated
|
|
56
59
|
|
|
57
60
|
|
|
58
61
|
def test4_mixed_many_named() -> None:
|
|
@@ -60,30 +63,32 @@ def test4_mixed_many_named() -> None:
|
|
|
60
63
|
B = literal("b")
|
|
61
64
|
syntax = (A | B).many()
|
|
62
65
|
sql = "a b a"
|
|
63
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
66
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
64
67
|
print("---" * 40)
|
|
65
68
|
print(ast)
|
|
66
|
-
generated = gen.generate(syntax, ast)
|
|
69
|
+
generated, bound = gen.generate(syntax, ast)
|
|
67
70
|
print("---" * 40)
|
|
68
71
|
print(generated)
|
|
69
72
|
assert ast == generated
|
|
70
73
|
value, bmap = generated.bimap()
|
|
71
|
-
|
|
74
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
75
|
+
assert u == generated
|
|
72
76
|
|
|
73
77
|
|
|
74
78
|
def test5_nested_then_many() -> None:
|
|
75
79
|
IF, THEN, END = literal("if"), literal("then"), literal("end")
|
|
76
80
|
syntax = (IF.many() // THEN.many()).many() // END
|
|
77
81
|
sql = "if if then end"
|
|
78
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
82
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
79
83
|
print("---" * 40)
|
|
80
84
|
print(ast)
|
|
81
|
-
generated = gen.generate(syntax, ast, restore_pruned=True)
|
|
85
|
+
generated, bound = gen.generate(syntax, ast, restore_pruned=True)
|
|
82
86
|
print("---" * 40)
|
|
83
87
|
print(generated)
|
|
84
88
|
assert ast == generated
|
|
85
89
|
value, bmap = generated.bimap()
|
|
86
|
-
|
|
90
|
+
u, v = gen.generate(syntax, bmap(value), restore_pruned=True)
|
|
91
|
+
assert u == generated
|
|
87
92
|
|
|
88
93
|
|
|
89
94
|
|
|
@@ -91,12 +96,13 @@ def test_then_flatten():
|
|
|
91
96
|
A, B, C = literal("a"), literal("b"), literal("c")
|
|
92
97
|
syntax = A + (B + C)
|
|
93
98
|
sql = "a b c"
|
|
94
|
-
ast = parse(syntax, sql, dialect='sqlite')
|
|
99
|
+
ast, bound = parse(syntax, sql, dialect='sqlite')
|
|
95
100
|
print(ast)
|
|
96
|
-
generated = gen.generate(syntax, ast)
|
|
101
|
+
generated, bound = gen.generate(syntax, ast)
|
|
97
102
|
assert ast == generated
|
|
98
103
|
value, bmap = generated.bimap()
|
|
99
|
-
|
|
104
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
105
|
+
assert u == generated
|
|
100
106
|
|
|
101
107
|
|
|
102
108
|
|
|
@@ -106,24 +112,26 @@ def test_named_in_then():
|
|
|
106
112
|
C = literal("c").mark("third")
|
|
107
113
|
syntax = A + B + C
|
|
108
114
|
sql = "a b c"
|
|
109
|
-
ast = parse(syntax, sql, dialect='sqlite')
|
|
115
|
+
ast, bound = parse(syntax, sql, dialect='sqlite')
|
|
110
116
|
print(ast)
|
|
111
|
-
generated = gen.generate(syntax, ast)
|
|
117
|
+
generated, bound = gen.generate(syntax, ast)
|
|
112
118
|
assert ast == generated
|
|
113
119
|
value, bmap = generated.bimap()
|
|
114
|
-
|
|
120
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
121
|
+
assert u == generated
|
|
115
122
|
|
|
116
123
|
|
|
117
124
|
def test_named_in_many():
|
|
118
125
|
A = literal("x").mark("x")
|
|
119
126
|
syntax = A.many()
|
|
120
127
|
sql = "x x x"
|
|
121
|
-
ast = parse(syntax, sql, dialect='sqlite')
|
|
128
|
+
ast, bound = parse(syntax, sql, dialect='sqlite')
|
|
122
129
|
print(ast)
|
|
123
|
-
generated = gen.generate(syntax, ast)
|
|
130
|
+
generated, bound = gen.generate(syntax, ast)
|
|
124
131
|
assert ast == generated
|
|
125
132
|
value, bmap = generated.bimap()
|
|
126
|
-
|
|
133
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
134
|
+
assert u == generated
|
|
127
135
|
|
|
128
136
|
|
|
129
137
|
def test_named_in_or():
|
|
@@ -131,12 +139,13 @@ def test_named_in_or():
|
|
|
131
139
|
B = literal("b").mark("b")
|
|
132
140
|
syntax = A | B
|
|
133
141
|
sql = "b"
|
|
134
|
-
ast = parse(syntax, sql, dialect='sqlite')
|
|
142
|
+
ast, bound = parse(syntax, sql, dialect='sqlite')
|
|
135
143
|
print(ast)
|
|
136
|
-
generated = gen.generate(syntax, ast)
|
|
144
|
+
generated, bound = gen.generate(syntax, ast)
|
|
137
145
|
assert ast == generated
|
|
138
146
|
value, bmap = generated.bimap()
|
|
139
|
-
|
|
147
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
148
|
+
assert u == generated
|
|
140
149
|
|
|
141
150
|
|
|
142
151
|
|
|
@@ -148,21 +157,22 @@ def test_deep_mix():
|
|
|
148
157
|
C = literal("c").mark("c")
|
|
149
158
|
syntax = ((A + B) | C).many() + B
|
|
150
159
|
sql = "a b a b c b"
|
|
151
|
-
ast = parse(syntax, sql, dialect='sqlite')
|
|
160
|
+
ast, bound = parse(syntax, sql, dialect='sqlite')
|
|
152
161
|
print(ast)
|
|
153
|
-
generated = gen.generate(syntax, ast)
|
|
162
|
+
generated, bound = gen.generate(syntax, ast)
|
|
154
163
|
print('---' * 40)
|
|
155
164
|
print(generated)
|
|
156
165
|
assert ast == generated
|
|
157
166
|
value, bmap = generated.bimap()
|
|
158
|
-
|
|
167
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
168
|
+
assert u == generated
|
|
159
169
|
|
|
160
170
|
|
|
161
171
|
def test_empty_many() -> None:
|
|
162
172
|
A = literal("a")
|
|
163
173
|
syntax = A.many() # This should allow empty matches
|
|
164
174
|
sql = ""
|
|
165
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
175
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
166
176
|
assert isinstance(ast, Error)
|
|
167
177
|
|
|
168
178
|
|
|
@@ -171,9 +181,10 @@ def test_backtracking_many() -> None:
|
|
|
171
181
|
B = literal("b")
|
|
172
182
|
syntax = (A.many() + B) # must not eat the final "a" needed for B
|
|
173
183
|
sql = "a a a a b"
|
|
174
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
184
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
175
185
|
value, bmap = ast.bimap()
|
|
176
|
-
|
|
186
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
187
|
+
assert u == ast
|
|
177
188
|
|
|
178
189
|
def test_deep_nesting() -> None:
|
|
179
190
|
A = literal("a")
|
|
@@ -181,7 +192,7 @@ def test_deep_nesting() -> None:
|
|
|
181
192
|
for _ in range(100):
|
|
182
193
|
syntax = syntax + A
|
|
183
194
|
sql = " " .join("a" for _ in range(101))
|
|
184
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
195
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
185
196
|
assert ast is not None
|
|
186
197
|
|
|
187
198
|
|
|
@@ -189,7 +200,7 @@ def test_nested_many() -> None:
|
|
|
189
200
|
A = literal("a")
|
|
190
201
|
syntax = (A.many().many()) # groups of groups of "a"
|
|
191
202
|
sql = "a a a"
|
|
192
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
203
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
193
204
|
assert isinstance(ast, Many)
|
|
194
205
|
|
|
195
206
|
|
|
@@ -197,9 +208,10 @@ def test_named_many() -> None:
|
|
|
197
208
|
A = literal("a").mark("alpha")
|
|
198
209
|
syntax = A.many()
|
|
199
210
|
sql = "a a"
|
|
200
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
211
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
201
212
|
value, bmap = ast.bimap()
|
|
202
|
-
|
|
213
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
214
|
+
assert u == ast
|
|
203
215
|
|
|
204
216
|
|
|
205
217
|
def test_or_named() -> None:
|
|
@@ -207,9 +219,10 @@ def test_or_named() -> None:
|
|
|
207
219
|
B = literal("b").mark("y")
|
|
208
220
|
syntax = A | B
|
|
209
221
|
sql = "b"
|
|
210
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
222
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
211
223
|
value, bmap = ast.bimap()
|
|
212
|
-
|
|
224
|
+
u, v = gen.generate(syntax, bmap(value))
|
|
225
|
+
assert u == ast
|
|
213
226
|
|
|
214
227
|
|
|
215
228
|
def test_then_associativity() -> None:
|
|
@@ -218,7 +231,7 @@ def test_then_associativity() -> None:
|
|
|
218
231
|
C = literal("c")
|
|
219
232
|
syntax = A + B + C
|
|
220
233
|
sql = "a b c"
|
|
221
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
234
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
222
235
|
# Should be Then(Then(A,B),C)
|
|
223
236
|
assert ast == Then(kind=ThenKind.BOTH,
|
|
224
237
|
left=Then(kind=ThenKind.BOTH,
|
|
@@ -232,7 +245,7 @@ def test_ambiguous() -> None:
|
|
|
232
245
|
B = literal("a") + literal("b")
|
|
233
246
|
syntax = A | B
|
|
234
247
|
sql = "a"
|
|
235
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
248
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
236
249
|
# Does it prefer A (shorter) or B (fails)? Depends on design.
|
|
237
250
|
assert ast == Choice[Token, Token](value=TokenGen.from_string("a"), kind=ChoiceKind.LEFT)
|
|
238
251
|
|
|
@@ -245,19 +258,19 @@ def test_combo() -> None:
|
|
|
245
258
|
sql = "a b a b c b"
|
|
246
259
|
# Should fail, as we discussed earlier
|
|
247
260
|
# the working syntax should be ((A + B) | C).many() + B
|
|
248
|
-
ast = parse(syntax, sql, dialect="sqlite")
|
|
261
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
249
262
|
assert isinstance(ast, Error)
|
|
250
|
-
ast = parse(((A + B) | C).many() + B, sql, dialect="sqlite")
|
|
263
|
+
ast, bound = parse(((A + B) | C).many() + B, sql, dialect="sqlite")
|
|
251
264
|
assert not isinstance(ast, Error)
|
|
252
265
|
|
|
253
266
|
|
|
254
267
|
def test_optional():
|
|
255
268
|
A = literal("a").mark("a")
|
|
256
269
|
syntax = A.optional()
|
|
257
|
-
ast1 = parse(syntax, "", dialect="sqlite")
|
|
270
|
+
ast1, bound = parse(syntax, "", dialect="sqlite")
|
|
258
271
|
v1, _ = ast1.bimap()
|
|
259
272
|
assert isinstance(v1, Nothing)
|
|
260
|
-
ast2 = parse(syntax, "a", dialect="sqlite")
|
|
273
|
+
ast2, bound = parse(syntax, "a", dialect="sqlite")
|
|
261
274
|
v2, _ = ast2.bimap()
|
|
262
275
|
assert v2 == Marked(name='a', value=TokenGen.from_string('a'))
|
|
263
276
|
|
|
@@ -13,21 +13,23 @@ var = variable()
|
|
|
13
13
|
def test_between()->None:
|
|
14
14
|
sql = "then if then"
|
|
15
15
|
syntax = IF.between(THEN, THEN)
|
|
16
|
-
ast
|
|
17
|
-
generated = gen.generate(syntax, ast)
|
|
16
|
+
ast, bound = parse(syntax, sql, dialect='sqlite')
|
|
17
|
+
generated, bound = gen.generate(syntax, ast)
|
|
18
18
|
assert ast == generated, "Parsed and generated results do not match."
|
|
19
19
|
x, f = generated.bimap()
|
|
20
|
-
|
|
20
|
+
u, v = gen.generate(syntax, f(x))
|
|
21
|
+
assert u == ast
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def test_sep_by()->None:
|
|
24
25
|
sql = "if then if then if then if"
|
|
25
26
|
syntax = IF.sep_by(THEN)
|
|
26
|
-
ast
|
|
27
|
-
generated = gen.generate(syntax, ast)
|
|
27
|
+
ast, bound = parse(syntax, sql, dialect='sqlite')
|
|
28
|
+
generated, bound = gen.generate(syntax, ast)
|
|
28
29
|
assert ast == generated, "Parsed and generated results do not match."
|
|
29
30
|
x, f = generated.bimap()
|
|
30
|
-
|
|
31
|
+
u, v = gen.generate(syntax, f(x))
|
|
32
|
+
assert u == ast
|
|
31
33
|
|
|
32
34
|
def test_many_or()->None:
|
|
33
35
|
IF = literal("if")
|
|
@@ -35,8 +37,9 @@ def test_many_or()->None:
|
|
|
35
37
|
END = literal("end")
|
|
36
38
|
syntax = (IF.many() | THEN.many()).many() // END
|
|
37
39
|
sql = "if if then end"
|
|
38
|
-
ast
|
|
39
|
-
generated = gen.generate(syntax, ast)
|
|
40
|
+
ast, bound = parse(syntax, sql, dialect='sqlite')
|
|
41
|
+
generated, bound = gen.generate(syntax, ast)
|
|
40
42
|
assert ast == generated, "Parsed and generated results do not match."
|
|
41
43
|
x, f = generated.bimap()
|
|
42
|
-
|
|
44
|
+
u, v = gen.generate(syntax, f(x))
|
|
45
|
+
assert u == ast
|
|
@@ -43,19 +43,21 @@ def test_to() -> None:
|
|
|
43
43
|
+ ifthenelse.mark('body')
|
|
44
44
|
// ~END).to(While)
|
|
45
45
|
sql = 'while b 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')
|
|
46
|
+
ast, bound = parse(syntax, sql, dialect='sqlite')
|
|
47
47
|
print(ast)
|
|
48
|
-
g = gen.generate(syntax, ast, restore_pruned=True)
|
|
48
|
+
g, bound = gen.generate(syntax, ast, restore_pruned=True)
|
|
49
49
|
assert ast == g
|
|
50
50
|
x, f = g.bimap()
|
|
51
51
|
print(1, x)
|
|
52
|
-
|
|
52
|
+
u,v = gen.generate(syntax, f(x), restore_pruned=True)
|
|
53
|
+
assert u == ast
|
|
53
54
|
x.body.append(x.body[0])
|
|
54
55
|
print(2, x)
|
|
55
56
|
print(f(x))
|
|
56
|
-
ast2 = gen.generate(syntax, f(x), restore_pruned=True)
|
|
57
|
+
ast2, bound = gen.generate(syntax, f(x), restore_pruned=True)
|
|
57
58
|
print(ast2)
|
|
58
59
|
y, fy = ast2.bimap()
|
|
59
60
|
print(3, y)
|
|
60
61
|
assert y == x
|
|
61
|
-
|
|
62
|
+
u, v = gen.generate(syntax, fy(y), restore_pruned=True)
|
|
63
|
+
assert u == ast2
|
|
@@ -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
|
|
13
|
+
ast, bound = parse(syntax, sql, dialect="sqlite")
|
|
14
14
|
assert isinstance(ast, tuple), 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, sql, dialect="sqlite")
|
|
21
|
+
res, bound = 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, sql, dialect="sqlite")
|
|
29
|
+
res, bound = 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, sql, dialect="postgres")
|
|
38
|
+
res, bound = 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"
|
syncraft-0.2.0/tests/test_var.py
DELETED
|
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
|