syncraft 0.1.10__py3-none-any.whl → 0.1.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of syncraft might be problematic. Click here for more details.
- syncraft/ast.py +202 -0
- syncraft/dsl.py +1 -1
- syncraft/generator.py +88 -163
- syncraft/parser.py +7 -313
- {syncraft-0.1.10.dist-info → syncraft-0.1.12.dist-info}/METADATA +2 -2
- syncraft-0.1.12.dist-info/RECORD +15 -0
- syncraft-0.1.10.dist-info/RECORD +0 -14
- {syncraft-0.1.10.dist-info → syncraft-0.1.12.dist-info}/WHEEL +0 -0
- {syncraft-0.1.10.dist-info → syncraft-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {syncraft-0.1.10.dist-info → syncraft-0.1.12.dist-info}/top_level.txt +0 -0
syncraft/ast.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import re
|
|
5
|
+
from typing import (
|
|
6
|
+
Optional, Any, TypeVar, Tuple, runtime_checkable, Dict,
|
|
7
|
+
Protocol, Generic, Callable, Union
|
|
8
|
+
)
|
|
9
|
+
from syncraft.algebra import (
|
|
10
|
+
NamedResult, OrResult,ThenResult, ManyResult, ThenKind,
|
|
11
|
+
Lens
|
|
12
|
+
)
|
|
13
|
+
from dataclasses import dataclass, field, replace, is_dataclass, asdict
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from functools import cached_property
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class TokenProtocol(Protocol):
|
|
19
|
+
@property
|
|
20
|
+
def token_type(self) -> Enum: ...
|
|
21
|
+
@property
|
|
22
|
+
def text(self) -> str: ...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class Token:
|
|
27
|
+
token_type: Enum
|
|
28
|
+
text: str
|
|
29
|
+
def __str__(self) -> str:
|
|
30
|
+
return f"{self.token_type.name}({self.text})"
|
|
31
|
+
|
|
32
|
+
def __repr__(self) -> str:
|
|
33
|
+
return self.__str__()
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class TokenSpec:
|
|
37
|
+
token_type: Optional[Enum] = None
|
|
38
|
+
text: Optional[str] = None
|
|
39
|
+
case_sensitive: bool = False
|
|
40
|
+
regex: Optional[re.Pattern[str]] = None
|
|
41
|
+
|
|
42
|
+
def is_valid(self, token: TokenProtocol) -> bool:
|
|
43
|
+
type_match = self.token_type is None or token.token_type == self.token_type
|
|
44
|
+
value_match = self.text is None or (token.text.strip() == self.text.strip() if self.case_sensitive else
|
|
45
|
+
token.text.strip().upper() == self.text.strip().upper())
|
|
46
|
+
value_match = value_match or (self.regex is not None and self.regex.fullmatch(token.text) is not None)
|
|
47
|
+
return type_match and value_match
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
T = TypeVar('T', bound=TokenProtocol)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
ParseResult = Union[
|
|
56
|
+
ThenResult['ParseResult[T]', 'ParseResult[T]'],
|
|
57
|
+
NamedResult['ParseResult[T]', Any],
|
|
58
|
+
ManyResult['ParseResult[T]'],
|
|
59
|
+
OrResult['ParseResult[T]'],
|
|
60
|
+
Tuple[T, ...],
|
|
61
|
+
T,
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class NamedRecord:
|
|
72
|
+
lens: Lens[Any, Any]
|
|
73
|
+
value: Any
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class Walker:
|
|
77
|
+
lens: Optional[Lens[Any, Any]] = None
|
|
78
|
+
def get(self, root: ParseResult[Any]) -> Dict[str, NamedRecord]:
|
|
79
|
+
match root:
|
|
80
|
+
case ManyResult(value=children):
|
|
81
|
+
new_named: Dict[str, NamedRecord] = {}
|
|
82
|
+
for i, child in enumerate(children):
|
|
83
|
+
new_walker = replace(self, lens=(self.lens / ManyResult.lens(i)) if self.lens else ManyResult.lens(i))
|
|
84
|
+
new_named |= new_walker.get(child)
|
|
85
|
+
return new_named
|
|
86
|
+
case OrResult(value=value):
|
|
87
|
+
new_walker = replace(self, lens=(self.lens / OrResult.lens()) if self.lens else OrResult.lens())
|
|
88
|
+
return new_walker.get(value)
|
|
89
|
+
case ThenResult(left=left,
|
|
90
|
+
right=right,
|
|
91
|
+
kind=kind):
|
|
92
|
+
new_walker = replace(self, lens=(self.lens / ThenResult.lens(kind)) if self.lens else ThenResult.lens(kind))
|
|
93
|
+
return new_walker.get(left) | new_walker.get(right)
|
|
94
|
+
case NamedResult(name=name,
|
|
95
|
+
value=value,
|
|
96
|
+
forward_map=forward_map,
|
|
97
|
+
backward_map=backward_map,
|
|
98
|
+
aggregator=aggregator):
|
|
99
|
+
this_lens = (self.lens / NamedResult.lens()) if self.lens else NamedResult.lens()
|
|
100
|
+
if callable(forward_map) and callable(backward_map):
|
|
101
|
+
this_lens = this_lens.bimap(forward_map, backward_map)
|
|
102
|
+
elif callable(forward_map):
|
|
103
|
+
this_lens = this_lens.bimap(forward_map, lambda _: value)
|
|
104
|
+
elif callable(backward_map):
|
|
105
|
+
raise ValueError("backward_map provided without forward_map")
|
|
106
|
+
new_walker = replace(self, lens=this_lens)
|
|
107
|
+
child_named = new_walker.get(value)
|
|
108
|
+
if aggregator is not None:
|
|
109
|
+
return child_named | {name: NamedRecord(lens=this_lens,
|
|
110
|
+
value=aggregator(child_named))}
|
|
111
|
+
else:
|
|
112
|
+
return child_named
|
|
113
|
+
return {}
|
|
114
|
+
|
|
115
|
+
def set(self, root: ParseResult[Any], updated_values: Dict[str, Any]) -> ParseResult[Any]:
|
|
116
|
+
named_records = self.get(root)
|
|
117
|
+
def apply_update(name: str, value: Any, root: ParseResult[Any]) -> ParseResult[Any]:
|
|
118
|
+
if name not in named_records:
|
|
119
|
+
# Skip unknown names safely
|
|
120
|
+
return root
|
|
121
|
+
record = named_records[name]
|
|
122
|
+
target_named: NamedResult[Any, Any] = record.lens.get(root)
|
|
123
|
+
assert isinstance(target_named, NamedResult)
|
|
124
|
+
|
|
125
|
+
if target_named.aggregator is not None:
|
|
126
|
+
# Break apart dataclass/dict into child fields
|
|
127
|
+
if isinstance(value, dict):
|
|
128
|
+
child_updates = value
|
|
129
|
+
elif is_dataclass(value) and not isinstance(value, type):
|
|
130
|
+
child_updates = asdict(value)
|
|
131
|
+
else:
|
|
132
|
+
raise TypeError(f"Unsupported aggregator value for '{name}': {type(value)}")
|
|
133
|
+
|
|
134
|
+
# Recursively apply each child update
|
|
135
|
+
for child_name, child_value in child_updates.items():
|
|
136
|
+
root = apply_update(child_name, child_value, root)
|
|
137
|
+
return root
|
|
138
|
+
|
|
139
|
+
else:
|
|
140
|
+
# Leaf: just replace the value
|
|
141
|
+
updated_named = replace(target_named, value=value)
|
|
142
|
+
return record.lens.set(root, updated_named)
|
|
143
|
+
|
|
144
|
+
for name, value in updated_values.items():
|
|
145
|
+
root = apply_update(name, value, root)
|
|
146
|
+
|
|
147
|
+
return root
|
|
148
|
+
|
|
149
|
+
@dataclass(frozen=True)
|
|
150
|
+
class AST(Generic[T]):
|
|
151
|
+
focus: ParseResult[T]
|
|
152
|
+
pruned: bool = False
|
|
153
|
+
parent: Optional[AST[T]] = None
|
|
154
|
+
|
|
155
|
+
def up(self)->Optional[AST[T]]:
|
|
156
|
+
return self.parent
|
|
157
|
+
|
|
158
|
+
def left(self) -> Optional[AST[T]]:
|
|
159
|
+
match self.focus:
|
|
160
|
+
case ThenResult(left=left, kind=kind):
|
|
161
|
+
return replace(self, focus=left, parent=self, pruned = self.pruned or kind == ThenKind.RIGHT)
|
|
162
|
+
case _:
|
|
163
|
+
raise TypeError(f"Invalid focus type({self.focus}) for left traversal")
|
|
164
|
+
|
|
165
|
+
def right(self) -> Optional[AST[T]]:
|
|
166
|
+
match self.focus:
|
|
167
|
+
case ThenResult(right=right, kind=kind):
|
|
168
|
+
return replace(self, focus=right, parent=self, pruned = self.pruned or kind == ThenKind.LEFT)
|
|
169
|
+
case _:
|
|
170
|
+
raise TypeError(f"Invalid focus type({self.focus}) for right traversal")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def down(self, index: int) -> Optional[AST[T]]:
|
|
174
|
+
match self.focus:
|
|
175
|
+
case ManyResult(value=children):
|
|
176
|
+
if 0 <= index < len(children):
|
|
177
|
+
return replace(self, focus=children[index], parent=self, pruned=self.pruned)
|
|
178
|
+
else:
|
|
179
|
+
raise IndexError(f"Index {index} out of bounds for ManyResult with {len(children)} children")
|
|
180
|
+
case OrResult(value=value):
|
|
181
|
+
if index == 0:
|
|
182
|
+
return replace(self, focus=value, parent=self, pruned=self.pruned)
|
|
183
|
+
else:
|
|
184
|
+
raise IndexError(f"Index {index} out of bounds for OrResult")
|
|
185
|
+
case _:
|
|
186
|
+
raise TypeError(f"Invalid focus type({self.focus}) for down traversal")
|
|
187
|
+
|
|
188
|
+
def how_many(self)->int:
|
|
189
|
+
match self.focus:
|
|
190
|
+
case ManyResult(value=children):
|
|
191
|
+
return len(children)
|
|
192
|
+
case _:
|
|
193
|
+
raise TypeError(f"Invalid focus type({self.focus}) for how_many")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@cached_property
|
|
198
|
+
def root(self) -> AST[T]:
|
|
199
|
+
while self.parent is not None:
|
|
200
|
+
self = self.parent
|
|
201
|
+
return self
|
|
202
|
+
|
syncraft/dsl.py
CHANGED
|
@@ -156,7 +156,7 @@ class DSL(Generic[A, S], Insptectable):
|
|
|
156
156
|
def sep_by(self, sep: DSL[Any, S]) -> DSL[ThenResult[A, ManyResult[ThenResult[None, A]]], S]:
|
|
157
157
|
return (self + (sep >> self).many()).describe(
|
|
158
158
|
name='sep_by',
|
|
159
|
-
fixity='
|
|
159
|
+
fixity='prefix',
|
|
160
160
|
parameter=[self, sep]
|
|
161
161
|
)
|
|
162
162
|
|
syncraft/generator.py
CHANGED
|
@@ -4,22 +4,19 @@ from typing import (
|
|
|
4
4
|
Any, TypeVar, Tuple, Optional, Callable, Generic, Union, Iterable, Hashable,
|
|
5
5
|
cast, List
|
|
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
|
-
NamedResult, OrResult, ManyResult
|
|
11
|
+
NamedResult, OrResult, ManyResult, ThenKind
|
|
12
12
|
)
|
|
13
|
-
from syncraft.
|
|
13
|
+
from syncraft.ast import TokenProtocol, ParseResult, AST, Token, TokenSpec
|
|
14
14
|
from sqlglot import TokenType
|
|
15
15
|
import re
|
|
16
|
-
from rich import print
|
|
17
16
|
import rstr
|
|
18
17
|
from functools import lru_cache
|
|
19
18
|
import random
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
A = TypeVar('A')
|
|
23
20
|
B = TypeVar('B')
|
|
24
21
|
T = TypeVar('T', bound=TokenProtocol)
|
|
25
22
|
|
|
@@ -31,12 +28,6 @@ GenResult = Union[
|
|
|
31
28
|
T
|
|
32
29
|
]
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
E = TypeVar('E', bound=Hashable)
|
|
38
|
-
F = TypeVar('F', bound=Hashable)
|
|
39
|
-
|
|
40
31
|
@dataclass(frozen=True)
|
|
41
32
|
class GenState(Generic[T], Insptectable):
|
|
42
33
|
ast: Optional[AST[T]]
|
|
@@ -51,15 +42,9 @@ class GenState(Generic[T], Insptectable):
|
|
|
51
42
|
def to_string(self, interested: Callable[[Any], bool]) -> str | None:
|
|
52
43
|
return f"GenState(current={self.focus})"
|
|
53
44
|
|
|
54
|
-
@
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
return True
|
|
58
|
-
return self.ast.is_pruned
|
|
59
|
-
|
|
60
|
-
@property
|
|
61
|
-
def ended(self) -> bool:
|
|
62
|
-
return self.ast is None
|
|
45
|
+
@cached_property
|
|
46
|
+
def pruned(self)->bool:
|
|
47
|
+
return self.ast is None or self.ast.pruned
|
|
63
48
|
|
|
64
49
|
|
|
65
50
|
@property
|
|
@@ -69,65 +54,35 @@ class GenState(Generic[T], Insptectable):
|
|
|
69
54
|
return self.ast.focus
|
|
70
55
|
|
|
71
56
|
|
|
72
|
-
def
|
|
57
|
+
def left(self)-> GenState[T]:
|
|
73
58
|
if self.ast is None:
|
|
74
59
|
return self
|
|
75
|
-
return replace(self, ast=self.ast.
|
|
60
|
+
return replace(self, ast=self.ast.left())
|
|
76
61
|
|
|
77
|
-
def down_left(self) -> GenState[T]:
|
|
78
|
-
if self.ast is None:
|
|
79
|
-
return self
|
|
80
|
-
return replace(self, ast=self.ast.down_left())
|
|
81
|
-
|
|
82
62
|
def right(self) -> GenState[T]:
|
|
83
63
|
if self.ast is None:
|
|
84
|
-
return self
|
|
64
|
+
return self
|
|
85
65
|
return replace(self, ast=self.ast.right())
|
|
86
|
-
|
|
87
|
-
def advance(self, filter: Callable[[Any], bool]) -> GenState[T]:
|
|
88
|
-
if self.ast is None:
|
|
89
|
-
return self
|
|
90
|
-
def filtered(z: GenState[T]) -> GenState[T]:
|
|
91
|
-
return z if z.ast is None or filter(z.ast.focus) else z.advance(filter=filter)
|
|
92
|
-
z = self.down_left()
|
|
93
|
-
if z.ast is not None:
|
|
94
|
-
return filtered(z)
|
|
95
|
-
z = self.right()
|
|
96
|
-
if z.ast is not None:
|
|
97
|
-
return filtered(z)
|
|
98
|
-
|
|
99
|
-
zs = self.ast
|
|
100
|
-
while tmp_z := zs.up():
|
|
101
|
-
if next_z := tmp_z.right():
|
|
102
|
-
return filtered(replace(self, ast=next_z))
|
|
103
|
-
zs = tmp_z
|
|
104
|
-
return filtered(replace(self, ast=None))
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@staticmethod
|
|
109
|
-
def only_terminal(node: Any) -> bool:
|
|
110
|
-
return not isinstance(node, (ManyResult, ThenResult, NamedResult, OrResult))
|
|
111
66
|
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return tuple()
|
|
117
|
-
|
|
118
|
-
def scoped(self) -> GenState[T]:
|
|
119
|
-
return ScopedState(ast=self.ast,
|
|
120
|
-
seed=self.seed,
|
|
121
|
-
scope=self.ast.breadcrumbs[-1] if self.ast and self.ast.breadcrumbs else None)
|
|
122
|
-
|
|
123
|
-
def freeform(self) -> GenState[T]:
|
|
124
|
-
return FreeformState(ast=None, seed=self.seed)
|
|
67
|
+
def up(self)->GenState[T]:
|
|
68
|
+
if self.ast is None:
|
|
69
|
+
return self
|
|
70
|
+
return replace(self, ast=self.ast.up())
|
|
125
71
|
|
|
72
|
+
def down(self, index: int) -> GenState[T]:
|
|
73
|
+
if self.ast is None:
|
|
74
|
+
return self
|
|
75
|
+
return replace(self, ast=self.ast.down(index))
|
|
126
76
|
|
|
77
|
+
@cached_property
|
|
78
|
+
def how_many(self) -> int:
|
|
79
|
+
if self.ast is None:
|
|
80
|
+
return 0
|
|
81
|
+
return self.ast.how_many()
|
|
82
|
+
|
|
127
83
|
@classmethod
|
|
128
84
|
def from_ast(cls, ast: Optional[AST[T]], seed: int = 0) -> GenState[T]:
|
|
129
|
-
|
|
130
|
-
return ret if ast is not None else ret.freeform()
|
|
85
|
+
return cls(ast=ast, seed=seed)
|
|
131
86
|
|
|
132
87
|
|
|
133
88
|
@classmethod
|
|
@@ -135,32 +90,7 @@ class GenState(Generic[T], Insptectable):
|
|
|
135
90
|
return cls.from_ast(AST(parse_result) if parse_result else None, seed)
|
|
136
91
|
|
|
137
92
|
|
|
138
|
-
@dataclass(frozen=True)
|
|
139
|
-
class ScopedState(GenState[T]):
|
|
140
|
-
scope: None | Crumb[T]
|
|
141
|
-
@property
|
|
142
|
-
def ended(self) -> bool:
|
|
143
|
-
return self.ast is None or self.scope in self.ast.closed
|
|
144
|
-
|
|
145
|
-
def right(self)-> GenState[T]:
|
|
146
|
-
ret: ScopedState[T] = cast(ScopedState[T], super().right())
|
|
147
|
-
if ret.ast is not None and self.scope is not None:
|
|
148
|
-
if self.scope not in ret.ast.closed and self.scope not in ret.ast.breadcrumbs:
|
|
149
|
-
return replace(ret, scope=ret.ast.breadcrumbs[-1] if ret.ast.breadcrumbs else None)
|
|
150
|
-
return ret
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@dataclass(frozen=True)
|
|
154
|
-
class FreeformState(GenState[T]):
|
|
155
|
-
@property
|
|
156
|
-
def ended(self) -> bool:
|
|
157
|
-
return False
|
|
158
|
-
|
|
159
|
-
def scoped(self) -> GenState[T]:
|
|
160
|
-
return self
|
|
161
93
|
|
|
162
|
-
def advance(self, filter: Callable[[Any], bool]) -> GenState[T]:
|
|
163
|
-
return self
|
|
164
94
|
|
|
165
95
|
|
|
166
96
|
@lru_cache(maxsize=None)
|
|
@@ -212,81 +142,81 @@ class TokenGen(TokenSpec):
|
|
|
212
142
|
class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
213
143
|
def flat_map(self, f: Callable[[GenResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
|
|
214
144
|
def flat_map_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
match self.run(s, use_cache=use_cache):
|
|
145
|
+
lft = input.left()
|
|
146
|
+
match self.run(lft, use_cache=use_cache):
|
|
218
147
|
case Left(error):
|
|
219
148
|
return Left(error)
|
|
220
149
|
case Right((value, next_input)):
|
|
221
|
-
|
|
150
|
+
r = input.right()
|
|
151
|
+
return f(value).run(r, use_cache)
|
|
222
152
|
raise ValueError("flat_map should always return a value or an error.")
|
|
223
153
|
return Generator(run_f = flat_map_run, name=self.name) # type: ignore
|
|
224
|
-
|
|
225
|
-
def gen(self,
|
|
226
|
-
freeform: Algebra[Any, GenState[T]],
|
|
227
|
-
default: Algebra[Any, GenState[T]]
|
|
228
|
-
) -> Algebra[Any, GenState[T]]:
|
|
229
|
-
def gen_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[Any, GenState[T]]]:
|
|
230
|
-
if input.ended:
|
|
231
|
-
return Left(Error(this=self,
|
|
232
|
-
message=f"{input.__class__.__name__} has ended, cannot run many.",
|
|
233
|
-
state=input))
|
|
234
|
-
elif input.is_freeform:
|
|
235
|
-
return freeform.run(input, use_cache)
|
|
236
|
-
else:
|
|
237
|
-
return default.run(input, use_cache)
|
|
238
|
-
return self.__class__(gen_run, name=default.name)
|
|
239
|
-
|
|
240
|
-
def gen_many(self,
|
|
241
|
-
at_least: int,
|
|
242
|
-
at_most: Optional[int] = None
|
|
243
|
-
) -> Algebra[ManyResult[GenResult[T]], GenState[T]]:
|
|
244
|
-
def gen_many_run(input: GenState[T],
|
|
245
|
-
use_cache:bool
|
|
246
|
-
) -> Either[Any, Tuple[ManyResult[GenResult[T]], GenState[T]]]:
|
|
247
|
-
upper = at_most if at_most is not None else at_least + 2
|
|
248
|
-
count = input.rng("many").randint(at_least, upper)
|
|
249
|
-
ret: List[Any] = []
|
|
250
|
-
current_input: GenState[T] = input.freeform()
|
|
251
|
-
for _ in range(count):
|
|
252
|
-
forked_input = current_input.fork(tag=len(ret))
|
|
253
|
-
match self.run(forked_input, use_cache):
|
|
254
|
-
case Right((value, next_input)):
|
|
255
|
-
current_input = next_input
|
|
256
|
-
ret.append(value)
|
|
257
|
-
case Left(_):
|
|
258
|
-
break
|
|
259
|
-
return Right((ManyResult(tuple(ret)), input))
|
|
260
|
-
return self.__class__(run_f=gen_many_run, name=f"free_many({self.name})") # type: ignore
|
|
154
|
+
|
|
261
155
|
|
|
262
156
|
|
|
263
157
|
def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[GenResult[T]], GenState[T]]:
|
|
264
158
|
assert at_least > 0, "at_least must be greater than 0"
|
|
265
159
|
assert at_most is None or at_least <= at_most, "at_least must be less than or equal to at_most"
|
|
266
|
-
|
|
267
|
-
|
|
160
|
+
def many_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ManyResult[GenResult[T]], GenState[T]]]:
|
|
161
|
+
if input.pruned:
|
|
162
|
+
upper = at_most if at_most is not None else at_least + 2
|
|
163
|
+
count = input.rng("many").randint(at_least, upper)
|
|
164
|
+
ret: List[Any] = []
|
|
165
|
+
for i in range(count):
|
|
166
|
+
forked_input = input.down(0).fork(tag=len(ret))
|
|
167
|
+
match self.run(forked_input, use_cache):
|
|
168
|
+
case Right((value, next_input)):
|
|
169
|
+
ret.append(value)
|
|
170
|
+
case Left(_):
|
|
171
|
+
pass
|
|
172
|
+
return Right((ManyResult(tuple(ret)), input))
|
|
173
|
+
else:
|
|
174
|
+
ret = []
|
|
175
|
+
for index in range(input.how_many):
|
|
176
|
+
match self.run(input.down(index), use_cache):
|
|
177
|
+
case Right((value, next_input)):
|
|
178
|
+
ret.append(value)
|
|
179
|
+
if at_most is not None and len(ret) > at_most:
|
|
180
|
+
return Left(Error(
|
|
181
|
+
message=f"Expected at most {at_most} matches, got {len(ret)}",
|
|
182
|
+
this=self,
|
|
183
|
+
state=input.down(index)
|
|
184
|
+
))
|
|
185
|
+
case Left(_):
|
|
186
|
+
pass
|
|
187
|
+
if len(ret) < at_least:
|
|
188
|
+
return Left(Error(
|
|
189
|
+
message=f"Expected at least {at_least} matches, got {len(ret)}",
|
|
190
|
+
this=self,
|
|
191
|
+
state=input.down(index)
|
|
192
|
+
))
|
|
193
|
+
return Right((ManyResult(tuple(ret)), input))
|
|
194
|
+
return self.__class__(many_run, name=f"many({self.name})") # type: ignore
|
|
268
195
|
|
|
269
|
-
def gen_or_else(self,
|
|
270
|
-
other: Algebra[GenResult[T], GenState[T]]) -> Algebra[OrResult[GenResult[T]], GenState[T]]:
|
|
271
|
-
def gen_or_else_run(input: GenState[T],
|
|
272
|
-
use_cache:bool
|
|
273
|
-
)->Either[Any, Tuple[OrResult[GenResult[T]], GenState[T]]]:
|
|
274
|
-
forked_input = input.fork(tag="or_else")
|
|
275
|
-
match forked_input.rng("or_else").choice((self, other)).run(forked_input.freeform(), use_cache):
|
|
276
|
-
case Right((value, next_input)):
|
|
277
|
-
return Right((OrResult(value), next_input))
|
|
278
|
-
case Left(error):
|
|
279
|
-
return Left(error)
|
|
280
|
-
raise TypeError(f"Unexpected result type from {self}")
|
|
281
|
-
return self.__class__(gen_or_else_run, name=f"free_or({self.name} | {other.name})") # type: ignore
|
|
282
196
|
|
|
283
|
-
|
|
284
197
|
def or_else(self, # type: ignore
|
|
285
198
|
other: Algebra[GenResult[T], GenState[T]]
|
|
286
199
|
) -> Algebra[OrResult[GenResult[T]], GenState[T]]:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
200
|
+
def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[OrResult[GenResult[T]], GenState[T]]]:
|
|
201
|
+
if input.pruned:
|
|
202
|
+
forked_input = input.fork(tag="or_else")
|
|
203
|
+
match forked_input.rng("or_else").choice((self, other)).run(forked_input, use_cache):
|
|
204
|
+
case Right((value, next_input)):
|
|
205
|
+
return Right((OrResult(value), next_input))
|
|
206
|
+
case Left(error):
|
|
207
|
+
return Left(error)
|
|
208
|
+
else:
|
|
209
|
+
match self.run(input.down(0), use_cache):
|
|
210
|
+
case Right((value, next_input)):
|
|
211
|
+
return Right((OrResult(value), next_input))
|
|
212
|
+
case Left(error):
|
|
213
|
+
match other.run(input.down(0), use_cache):
|
|
214
|
+
case Right((value, next_input)):
|
|
215
|
+
return Right((OrResult(value), next_input))
|
|
216
|
+
case Left(error):
|
|
217
|
+
return Left(error)
|
|
218
|
+
raise ValueError("or_else should always return a value or an error.")
|
|
219
|
+
return self.__class__(or_else_run, name=f"or_else({self.name} | {other.name})") # type: ignore
|
|
290
220
|
|
|
291
221
|
@classmethod
|
|
292
222
|
def token(cls,
|
|
@@ -298,20 +228,15 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
298
228
|
gen = TokenGen(token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
|
|
299
229
|
lazy_self: Algebra[GenResult[T], GenState[T]]
|
|
300
230
|
def token_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[GenResult[Token], GenState[T]]]:
|
|
301
|
-
if input.
|
|
302
|
-
return
|
|
303
|
-
message=f"{input.__class__.__name__} has ended, cannot run token.",
|
|
304
|
-
state=input))
|
|
305
|
-
elif input.is_freeform:
|
|
306
|
-
return Right((gen.gen(), input.advance(GenState.only_terminal)))
|
|
231
|
+
if input.pruned:
|
|
232
|
+
return Right((gen.gen(), input))
|
|
307
233
|
else:
|
|
308
|
-
input = input.leftmost()
|
|
309
234
|
current = input.focus
|
|
310
235
|
if not isinstance(current, Token) or not gen.is_valid(current):
|
|
311
236
|
return Left(Error(None,
|
|
312
237
|
message=f"Expected a Token, but got {type(current)}.",
|
|
313
238
|
state=input))
|
|
314
|
-
return Right((current, input
|
|
239
|
+
return Right((current, input))
|
|
315
240
|
lazy_self = cls(token_run, name=cls.__name__ + f'.token({token_type or text or regex})') # type: ignore
|
|
316
241
|
return lazy_self
|
|
317
242
|
|
|
@@ -322,6 +247,6 @@ def generate(gen: Algebra[Any, Any], data: Optional[AST[Any]] = None, seed: int
|
|
|
322
247
|
result = gen.run(state, use_cache=False)
|
|
323
248
|
if isinstance(result, Right):
|
|
324
249
|
return AST(result.value[0])
|
|
325
|
-
assert isinstance(result, Left), "
|
|
250
|
+
assert isinstance(result, Left), "Generator must return Either[Any, Tuple[Any, Any]]"
|
|
326
251
|
return result.value
|
|
327
252
|
|
syncraft/parser.py
CHANGED
|
@@ -2,329 +2,23 @@ from __future__ import annotations
|
|
|
2
2
|
import re
|
|
3
3
|
from sqlglot import tokenize, TokenType, Parser as GlotParser, exp
|
|
4
4
|
from typing import (
|
|
5
|
-
Optional, List, Any,
|
|
6
|
-
|
|
5
|
+
Optional, List, Any, Tuple,
|
|
6
|
+
Generic, Callable
|
|
7
7
|
)
|
|
8
8
|
from syncraft.algebra import (
|
|
9
|
-
Either, Left, Right, Error, Insptectable, Algebra
|
|
10
|
-
Lens
|
|
9
|
+
Either, Left, Right, Error, Insptectable, Algebra
|
|
11
10
|
)
|
|
12
|
-
from dataclasses import dataclass, field, replace
|
|
11
|
+
from dataclasses import dataclass, field, replace
|
|
13
12
|
from enum import Enum
|
|
14
|
-
from functools import reduce
|
|
13
|
+
from functools import reduce
|
|
15
14
|
from syncraft.dsl import DSL
|
|
16
15
|
|
|
16
|
+
from syncraft.ast import Token, TokenSpec, AST, T
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
@runtime_checkable
|
|
23
|
-
class TokenProtocol(Protocol):
|
|
24
|
-
@property
|
|
25
|
-
def token_type(self) -> TokenType: ...
|
|
26
|
-
@property
|
|
27
|
-
def text(self) -> str: ...
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@dataclass(frozen=True)
|
|
31
|
-
class Token:
|
|
32
|
-
token_type: TokenType
|
|
33
|
-
text: str
|
|
34
|
-
def __str__(self) -> str:
|
|
35
|
-
return f"{self.token_type.name}({self.text})"
|
|
36
|
-
|
|
37
|
-
def __repr__(self) -> str:
|
|
38
|
-
return self.__str__()
|
|
39
|
-
|
|
40
|
-
@dataclass(frozen=True)
|
|
41
|
-
class TokenSpec:
|
|
42
|
-
token_type: Optional[TokenType] = None
|
|
43
|
-
text: Optional[str] = None
|
|
44
|
-
case_sensitive: bool = False
|
|
45
|
-
regex: Optional[re.Pattern[str]] = None
|
|
46
|
-
|
|
47
|
-
def is_valid(self, token: TokenProtocol) -> bool:
|
|
48
|
-
type_match = self.token_type is None or token.token_type == self.token_type
|
|
49
|
-
value_match = self.text is None or (token.text.strip() == self.text.strip() if self.case_sensitive else
|
|
50
|
-
token.text.strip().upper() == self.text.strip().upper())
|
|
51
|
-
value_match = value_match or (self.regex is not None and self.regex.fullmatch(token.text) is not None)
|
|
52
|
-
return type_match and value_match
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
T = TypeVar('T', bound=TokenProtocol)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
ParseResult = Union[
|
|
61
|
-
ThenResult['ParseResult[T]', 'ParseResult[T]'],
|
|
62
|
-
NamedResult['ParseResult[T]', Any],
|
|
63
|
-
ManyResult['ParseResult[T]'],
|
|
64
|
-
OrResult['ParseResult[T]'],
|
|
65
|
-
Tuple[T, ...],
|
|
66
|
-
T,
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@dataclass(frozen=True)
|
|
72
|
-
class Crumb(Generic[T]):
|
|
73
|
-
entrance: str
|
|
74
|
-
id: int = field(init=False)
|
|
75
|
-
def __post_init__(self)-> None:
|
|
76
|
-
object.__setattr__(self, 'id', id(self))
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@dataclass(frozen=True)
|
|
81
|
-
class LeftCrumb(Crumb[T]):
|
|
82
|
-
right: ParseResult[T]
|
|
83
|
-
kind: ThenKind
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@dataclass(frozen=True)
|
|
87
|
-
class OrCrumb(Crumb[T]):
|
|
88
|
-
pass
|
|
89
|
-
@dataclass(frozen=True)
|
|
90
|
-
class RightCrumb(Crumb[T]):
|
|
91
|
-
left: ParseResult[T]
|
|
92
|
-
kind: ThenKind
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
@dataclass(frozen=True)
|
|
96
|
-
class NamedCrumb(Crumb[T]):
|
|
97
|
-
name: str
|
|
98
|
-
forward_map: Callable[[Any], Any] | None
|
|
99
|
-
backward_map: Callable[[Any], Any] | None
|
|
100
|
-
aggregator: Callable[..., Any] | None
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@dataclass(frozen=True)
|
|
104
|
-
class ManyCrumb(Crumb[T]):
|
|
105
|
-
before: Tuple[ParseResult[T], ...]
|
|
106
|
-
after: Tuple[ParseResult[T], ...]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
@dataclass(frozen=True)
|
|
110
|
-
class NamedRecord:
|
|
111
|
-
lens: Lens[Any, Any]
|
|
112
|
-
value: Any
|
|
113
|
-
|
|
114
|
-
@dataclass(frozen=True)
|
|
115
|
-
class Walker:
|
|
116
|
-
lens: Optional[Lens[Any, Any]] = None
|
|
117
|
-
def get(self, root: ParseResult[Any]) -> Dict[str, NamedRecord]:
|
|
118
|
-
match root:
|
|
119
|
-
case ManyResult(value=children):
|
|
120
|
-
new_named: Dict[str, NamedRecord] = {}
|
|
121
|
-
for i, child in enumerate(children):
|
|
122
|
-
new_walker = replace(self, lens=(self.lens / ManyResult.lens(i)) if self.lens else ManyResult.lens(i))
|
|
123
|
-
new_named |= new_walker.get(child)
|
|
124
|
-
return new_named
|
|
125
|
-
case OrResult(value=value):
|
|
126
|
-
new_walker = replace(self, lens=(self.lens / OrResult.lens()) if self.lens else OrResult.lens())
|
|
127
|
-
return new_walker.get(value)
|
|
128
|
-
case ThenResult(left=left,
|
|
129
|
-
right=right,
|
|
130
|
-
kind=kind):
|
|
131
|
-
new_walker = replace(self, lens=(self.lens / ThenResult.lens(kind)) if self.lens else ThenResult.lens(kind))
|
|
132
|
-
return new_walker.get(left) | new_walker.get(right)
|
|
133
|
-
case NamedResult(name=name,
|
|
134
|
-
value=value,
|
|
135
|
-
forward_map=forward_map,
|
|
136
|
-
backward_map=backward_map,
|
|
137
|
-
aggregator=aggregator):
|
|
138
|
-
this_lens = (self.lens / NamedResult.lens()) if self.lens else NamedResult.lens()
|
|
139
|
-
if callable(forward_map) and callable(backward_map):
|
|
140
|
-
this_lens = this_lens.bimap(forward_map, backward_map)
|
|
141
|
-
elif callable(forward_map):
|
|
142
|
-
this_lens = this_lens.bimap(forward_map, lambda _: value)
|
|
143
|
-
elif callable(backward_map):
|
|
144
|
-
raise ValueError("backward_map provided without forward_map")
|
|
145
|
-
new_walker = replace(self, lens=this_lens)
|
|
146
|
-
child_named = new_walker.get(value)
|
|
147
|
-
if aggregator is not None:
|
|
148
|
-
return child_named | {name: NamedRecord(lens=this_lens,
|
|
149
|
-
value=aggregator(child_named))}
|
|
150
|
-
else:
|
|
151
|
-
return child_named
|
|
152
|
-
return {}
|
|
153
|
-
|
|
154
|
-
def set(self, root: ParseResult[Any], updated_values: Dict[str, Any]) -> ParseResult[Any]:
|
|
155
|
-
named_records = self.get(root)
|
|
156
|
-
def apply_update(name: str, value: Any, root: ParseResult[Any]) -> ParseResult[Any]:
|
|
157
|
-
if name not in named_records:
|
|
158
|
-
# Skip unknown names safely
|
|
159
|
-
return root
|
|
160
|
-
record = named_records[name]
|
|
161
|
-
target_named: NamedResult[Any, Any] = record.lens.get(root)
|
|
162
|
-
assert isinstance(target_named, NamedResult)
|
|
163
|
-
|
|
164
|
-
if target_named.aggregator is not None:
|
|
165
|
-
# Break apart dataclass/dict into child fields
|
|
166
|
-
if isinstance(value, dict):
|
|
167
|
-
child_updates = value
|
|
168
|
-
elif is_dataclass(value) and not isinstance(value, type):
|
|
169
|
-
child_updates = asdict(value)
|
|
170
|
-
else:
|
|
171
|
-
raise TypeError(f"Unsupported aggregator value for '{name}': {type(value)}")
|
|
172
|
-
|
|
173
|
-
# Recursively apply each child update
|
|
174
|
-
for child_name, child_value in child_updates.items():
|
|
175
|
-
root = apply_update(child_name, child_value, root)
|
|
176
|
-
return root
|
|
177
|
-
|
|
178
|
-
else:
|
|
179
|
-
# Leaf: just replace the value
|
|
180
|
-
updated_named = replace(target_named, value=value)
|
|
181
|
-
return record.lens.set(root, updated_named)
|
|
182
|
-
|
|
183
|
-
for name, value in updated_values.items():
|
|
184
|
-
root = apply_update(name, value, root)
|
|
185
|
-
|
|
186
|
-
return root
|
|
187
|
-
|
|
188
|
-
@dataclass(frozen=True)
|
|
189
|
-
class AST(Generic[T]):
|
|
190
|
-
focus: ParseResult[T]
|
|
191
|
-
breadcrumbs: Tuple[Crumb[T], ...] = field(default_factory=tuple)
|
|
192
|
-
closed: frozenset[Crumb[T]] = field(default_factory=frozenset)
|
|
193
|
-
|
|
194
|
-
@cached_property
|
|
195
|
-
def is_pruned(self) -> bool:
|
|
196
|
-
for crumb in self.breadcrumbs:
|
|
197
|
-
match crumb:
|
|
198
|
-
case LeftCrumb(kind=ThenKind.RIGHT):
|
|
199
|
-
return True # you're in a left child of a then_right
|
|
200
|
-
case RightCrumb(kind=ThenKind.LEFT):
|
|
201
|
-
return True # you're in a right child of a then_left
|
|
202
|
-
return False
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def up(self) -> Optional[AST[T]]:
|
|
206
|
-
if not self.breadcrumbs:
|
|
207
|
-
return None
|
|
208
|
-
*rest, last = self.breadcrumbs
|
|
209
|
-
|
|
210
|
-
match last:
|
|
211
|
-
case LeftCrumb(right=right, kind=kind):
|
|
212
|
-
parent: ParseResult[T] = ThenResult(kind=kind, left=self.focus, right=right)
|
|
213
|
-
case RightCrumb(left=left, kind=kind):
|
|
214
|
-
parent = ThenResult(kind=kind, left=left, right=self.focus)
|
|
215
|
-
case NamedCrumb(name=name, forward_map=forward_map, backward_map=backward_map, aggregator=aggregator):
|
|
216
|
-
parent = NamedResult(name=name, value=self.focus, forward_map=forward_map, backward_map=backward_map, aggregator=aggregator)
|
|
217
|
-
case ManyCrumb(before=before, after=after):
|
|
218
|
-
parent = ManyResult(value=before + (self.focus,) + after)
|
|
219
|
-
case OrCrumb():
|
|
220
|
-
parent = OrResult(value=self.focus)
|
|
221
|
-
case _:
|
|
222
|
-
raise ValueError(f"Unexpected crumb type: {last}")
|
|
223
|
-
return AST(focus=parent, breadcrumbs=tuple(rest), closed=frozenset(self.closed | {last}))
|
|
224
|
-
|
|
225
|
-
def down_left(self) -> Optional[AST[T]]:
|
|
226
|
-
match self.focus:
|
|
227
|
-
case ThenResult(left=left, right=_, kind=kind):
|
|
228
|
-
return AST(focus=left,
|
|
229
|
-
breadcrumbs=self.breadcrumbs + (LeftCrumb("\u2199", right=self.focus.right, kind=kind),),
|
|
230
|
-
closed=self.closed)
|
|
231
|
-
case NamedResult(name=name,
|
|
232
|
-
value=inner,
|
|
233
|
-
|
|
234
|
-
forward_map=forward_map,
|
|
235
|
-
backward_map=backward_map,
|
|
236
|
-
aggregator=aggregator):
|
|
237
|
-
return AST(focus=inner,
|
|
238
|
-
breadcrumbs=self.breadcrumbs + (NamedCrumb("\u2199",
|
|
239
|
-
name=name,
|
|
240
|
-
|
|
241
|
-
forward_map=forward_map,
|
|
242
|
-
backward_map=backward_map,
|
|
243
|
-
aggregator=aggregator),),
|
|
244
|
-
closed=self.closed)
|
|
245
|
-
case ManyResult(value=()):
|
|
246
|
-
return None
|
|
247
|
-
case ManyResult(value=(head, *tail)):
|
|
248
|
-
return AST(focus=head,
|
|
249
|
-
breadcrumbs=self.breadcrumbs + (ManyCrumb("\u2199", before=(), after=tuple(tail)),),
|
|
250
|
-
closed=self.closed)
|
|
251
|
-
case OrResult(value=value):
|
|
252
|
-
return AST(focus=value,
|
|
253
|
-
breadcrumbs=self.breadcrumbs + (OrCrumb("\u2199"),),
|
|
254
|
-
closed=self.closed)
|
|
255
|
-
case _:
|
|
256
|
-
return None
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def right(self) -> Optional[AST[T]]:
|
|
262
|
-
if not self.breadcrumbs:
|
|
263
|
-
return None
|
|
264
|
-
*rest, last = self.breadcrumbs
|
|
265
|
-
match last:
|
|
266
|
-
case ManyCrumb(before=before, after=(next_, *after)):
|
|
267
|
-
# If inside a ManyResult, and there are elements in after, return the next sibling and update before to include current focus
|
|
268
|
-
new_last = ManyCrumb("\u2192", before=before + (self.focus,), after=tuple(after))
|
|
269
|
-
return AST(focus=next_,
|
|
270
|
-
breadcrumbs=tuple(rest) + (new_last,),
|
|
271
|
-
# don't add the ManyCrumb(last) to closed, because we only close one of its children
|
|
272
|
-
# and the whole ManyCrumb can not be considered closed
|
|
273
|
-
# so we only add the current focus to closed if it is a Crumb.
|
|
274
|
-
# if the client code hold MnayCrumb(last) as a scope, it should check
|
|
275
|
-
# if the scope is in closed, and update the scope to the new ManyCrumb
|
|
276
|
-
closed=frozenset(self.closed | {self.focus}) if isinstance(self.focus, Crumb) else frozenset(self.closed)
|
|
277
|
-
)
|
|
278
|
-
case LeftCrumb(right=right, kind=kind):
|
|
279
|
-
return AST(focus=right,
|
|
280
|
-
breadcrumbs=tuple(rest) + (RightCrumb("\u2192", self.focus, kind),),
|
|
281
|
-
closed=frozenset(self.closed | {last}))
|
|
282
|
-
case _:
|
|
283
|
-
return None
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def replace(self, new_focus: ParseResult[T]) -> AST[T]:
|
|
287
|
-
focus = new_focus
|
|
288
|
-
for crumb in reversed(self.breadcrumbs):
|
|
289
|
-
match crumb:
|
|
290
|
-
case LeftCrumb(right=right, kind=kind):
|
|
291
|
-
focus = ThenResult(left=focus, right=right, kind=kind)
|
|
292
|
-
case RightCrumb(left=left, kind=kind):
|
|
293
|
-
focus = ThenResult(left=left, right=focus, kind=kind)
|
|
294
|
-
case NamedCrumb(name=name,
|
|
295
|
-
|
|
296
|
-
forward_map=forward_map,
|
|
297
|
-
backward_map=backward_map,
|
|
298
|
-
aggregator=aggregator):
|
|
299
|
-
focus = NamedResult(name=name,
|
|
300
|
-
value=focus,
|
|
301
|
-
|
|
302
|
-
forward_map=forward_map,
|
|
303
|
-
backward_map=backward_map,
|
|
304
|
-
aggregator=aggregator)
|
|
305
|
-
case ManyCrumb(before=before, after=after):
|
|
306
|
-
focus = ManyResult(value=before + (focus,) + after)
|
|
307
|
-
case OrCrumb():
|
|
308
|
-
focus = OrResult(value=focus)
|
|
309
|
-
return AST(focus=focus)
|
|
310
|
-
|
|
311
|
-
@cached_property
|
|
312
|
-
def root(self) -> AST[T]:
|
|
313
|
-
z = self
|
|
314
|
-
while z.breadcrumbs:
|
|
315
|
-
z = z.up() # type: ignore
|
|
316
|
-
assert z is not None, "Zipper should not be None when breadcrumbs are present"
|
|
317
|
-
return z
|
|
318
|
-
|
|
319
|
-
@cached_property
|
|
320
|
-
def leftmost(self) -> AST[T]:
|
|
321
|
-
z = self
|
|
322
|
-
while True:
|
|
323
|
-
next_z = z.down_left()
|
|
324
|
-
if next_z is None:
|
|
325
|
-
return z
|
|
326
|
-
z = next_z
|
|
327
|
-
|
|
328
22
|
|
|
329
23
|
|
|
330
24
|
|
|
@@ -386,7 +80,7 @@ class ParserState(Generic[T], Insptectable):
|
|
|
386
80
|
class Parser(Algebra[Tuple[T,...] | T, ParserState[T]]):
|
|
387
81
|
@classmethod
|
|
388
82
|
def token(cls,
|
|
389
|
-
token_type: Optional[
|
|
83
|
+
token_type: Optional[Enum] = None,
|
|
390
84
|
text: Optional[str] = None,
|
|
391
85
|
case_sensitive: bool = False,
|
|
392
86
|
regex: Optional[re.Pattern[str]] = None
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syncraft
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.12
|
|
4
4
|
Summary: Parser combinator library
|
|
5
5
|
Author-email: Michael Afmokt <michael@esacca.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Keywords: parser,combinator,sql,sqlite,generator,printer
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: rich>=14.1.0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
syncraft/algebra.py,sha256=jiqdo1GbXVwi_7Uoh1zBLe-TparNaHp8jW2IFt8PzBc,24494
|
|
3
|
+
syncraft/ast.py,sha256=y48DKYO-rSzmrObxsLXq2k_DfAplMnXjJBURY4Gv30U,7582
|
|
4
|
+
syncraft/cmd.py,sha256=DzXgU8QLVOg0YTFKpmOyzbf02LKPphQ4EeKSLzqpJ_s,2012
|
|
5
|
+
syncraft/diagnostic.py,sha256=_kLbl1LlU4Y2PtsLLZFA_OLqeZqq1f2rjT_LRXLnDaI,2813
|
|
6
|
+
syncraft/dsl.py,sha256=T89zkcE6xKvQSP4PJDHaWp9TsygXCTAzgx6r91KysFc,16504
|
|
7
|
+
syncraft/generator.py,sha256=SD9CRWnJrllWlZeiZmyBEkQ_WOVMjWKgrcAGo-27o14,9970
|
|
8
|
+
syncraft/parser.py,sha256=rk3YF8B0G8PG3JKYpeMQsYmHC5JxqrkCMXZAPfaqgME,10210
|
|
9
|
+
syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
syncraft/sqlite3.py,sha256=2JNGDymSJti3mdc7hdeXIF78S6SsSLpKJwe6AzivskQ,34757
|
|
11
|
+
syncraft-0.1.12.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
|
|
12
|
+
syncraft-0.1.12.dist-info/METADATA,sha256=ObEtoVoxfp6uKYMU9ZW_ABAs6T-RH7TRu__Pjt9mwIA,946
|
|
13
|
+
syncraft-0.1.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
syncraft-0.1.12.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
|
|
15
|
+
syncraft-0.1.12.dist-info/RECORD,,
|
syncraft-0.1.10.dist-info/RECORD
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
syncraft/algebra.py,sha256=jiqdo1GbXVwi_7Uoh1zBLe-TparNaHp8jW2IFt8PzBc,24494
|
|
3
|
-
syncraft/cmd.py,sha256=DzXgU8QLVOg0YTFKpmOyzbf02LKPphQ4EeKSLzqpJ_s,2012
|
|
4
|
-
syncraft/diagnostic.py,sha256=_kLbl1LlU4Y2PtsLLZFA_OLqeZqq1f2rjT_LRXLnDaI,2813
|
|
5
|
-
syncraft/dsl.py,sha256=cUBBB8upivOJDfZteTgwppoRg8Z_e5Bo4urDgdUG_QU,16503
|
|
6
|
-
syncraft/generator.py,sha256=19FnFCi7yfq9dR4ePg7mzt06Kw--1mIRENHXDfPUGnk,12279
|
|
7
|
-
syncraft/parser.py,sha256=wDiRJuNLsBsTBv0HZlGUGtkKMbDpIA8sTfsV3tA4BU0,22835
|
|
8
|
-
syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
syncraft/sqlite3.py,sha256=2JNGDymSJti3mdc7hdeXIF78S6SsSLpKJwe6AzivskQ,34757
|
|
10
|
-
syncraft-0.1.10.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
|
|
11
|
-
syncraft-0.1.10.dist-info/METADATA,sha256=cTJNXllZFFRL9Av8u_-iSgHrN92ZKO0HpgOYbKx6_fE,945
|
|
12
|
-
syncraft-0.1.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
syncraft-0.1.10.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
|
|
14
|
-
syncraft-0.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|