syncraft 0.1.18__tar.gz → 0.1.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of syncraft might be problematic. Click here for more details.
- {syncraft-0.1.18 → syncraft-0.1.20}/PKG-INFO +5 -4
- syncraft-0.1.20/README.md +22 -0
- {syncraft-0.1.18 → syncraft-0.1.20}/pyproject.toml +1 -1
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/algebra.py +145 -227
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/ast.py +13 -10
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/diagnostic.py +2 -2
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/generator.py +33 -5
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/parser.py +21 -27
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/sqlite3.py +7 -7
- syncraft-0.1.18/syncraft/dsl.py → syncraft-0.1.20/syncraft/syntax.py +58 -58
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft.egg-info/PKG-INFO +5 -4
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft.egg-info/SOURCES.txt +1 -1
- {syncraft-0.1.18 → syncraft-0.1.20}/tests/test_bimap.py +167 -48
- {syncraft-0.1.18 → syncraft-0.1.20}/tests/test_parse.py +7 -7
- {syncraft-0.1.18 → syncraft-0.1.20}/tests/test_until.py +4 -4
- syncraft-0.1.18/README.md +0 -21
- {syncraft-0.1.18 → syncraft-0.1.20}/LICENSE +0 -0
- {syncraft-0.1.18 → syncraft-0.1.20}/setup.cfg +0 -0
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/__init__.py +0 -0
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/cmd.py +0 -0
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft/py.typed +0 -0
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.1.18 → syncraft-0.1.20}/syncraft.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syncraft
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.20
|
|
4
4
|
Summary: Parser combinator library
|
|
5
5
|
Author-email: Michael Afmokt <michael@esacca.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -31,6 +31,7 @@ pip install syncraft
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
## TODO
|
|
34
|
-
- [ ]
|
|
35
|
-
- [ ]
|
|
36
|
-
- [ ]
|
|
34
|
+
- [ ] Add a collect method to AST to collect all named entries and pack them into a dict or a custom dataclass. This method will be called as the last step of my current bimap. So it shares the signature of bimap and can combine with the current bimap
|
|
35
|
+
- [ ] Amend all, first, last, and named helper functions to support bimap and named results.
|
|
36
|
+
- [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
|
|
37
|
+
- [ ] Make the library as fast as possible and feasible.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Syncraft
|
|
2
|
+
|
|
3
|
+
Syncraft is a parser/generator combinator library with full round-trip support:
|
|
4
|
+
|
|
5
|
+
- Parse source code into AST or dataclasses
|
|
6
|
+
- Generate source code from dataclasses
|
|
7
|
+
- Bidirectional transformations via lenses
|
|
8
|
+
- Convenience combinators: `all`, `first`, `last`, `named`
|
|
9
|
+
- SQLite syntax support included
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install syncraft
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## TODO
|
|
19
|
+
- [ ] Add a collect method to AST to collect all named entries and pack them into a dict or a custom dataclass. This method will be called as the last step of my current bimap. So it shares the signature of bimap and can combine with the current bimap
|
|
20
|
+
- [ ] Amend all, first, last, and named helper functions to support bimap and named results.
|
|
21
|
+
- [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
|
|
22
|
+
- [ ] Make the library as fast as possible and feasible.
|
|
@@ -1,163 +1,120 @@
|
|
|
1
|
-
"""
|
|
2
|
-
We want to parse a token stream into an AST, and then generate a new token stream from that AST.
|
|
3
|
-
The generation should be a dual to the parsing. By 'dual' we mean that the generation algebra should be
|
|
4
|
-
as close as possible to the parsing algebra. The closest algebra to the parsing algebra is the parsing
|
|
5
|
-
algebra itself.
|
|
6
|
-
|
|
7
|
-
Given:
|
|
8
|
-
AST = Syntax(Parser)(ParserState([Token, ...]))
|
|
9
|
-
AST =?= Syntax(Parser)(GenState(AST))
|
|
10
|
-
|
|
11
|
-
where =?= means the LHS and RHS induce the same text output, e.g. the same token stream
|
|
12
|
-
inspite of the token metadata, token types, and/or potentially different structure of the AST.
|
|
13
|
-
|
|
14
|
-
With the above setting, Generator as a dual to Parser, can reuse most of the parsing combinator, the
|
|
15
|
-
change needed is to introduce randomness in the generation process, e.g. to generate a random variable name, etc.
|
|
16
|
-
|
|
17
|
-
[Token, ...] == Syntax(Generator)(GenState(AST))
|
|
18
|
-
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
|
|
22
1
|
from __future__ import annotations
|
|
23
|
-
|
|
24
2
|
from typing import (
|
|
25
3
|
Optional, List, Any, TypeVar, Generic, Callable, Tuple, cast,
|
|
26
|
-
Dict, Type, ClassVar, Hashable
|
|
27
|
-
Mapping, Iterator
|
|
4
|
+
Dict, Type, ClassVar, Hashable
|
|
28
5
|
)
|
|
29
6
|
|
|
30
7
|
import traceback
|
|
31
|
-
from dataclasses import dataclass,
|
|
32
|
-
from functools import cached_property
|
|
8
|
+
from dataclasses import dataclass, replace
|
|
33
9
|
from weakref import WeakKeyDictionary
|
|
34
|
-
from abc import ABC
|
|
10
|
+
from abc import ABC
|
|
35
11
|
from enum import Enum
|
|
12
|
+
from functools import reduce
|
|
36
13
|
|
|
37
14
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def to_string(self, interested: Callable[[Any], bool]) -> Optional[str]:
|
|
43
|
-
raise NotImplementedError("Subclasses must implement to_string")
|
|
44
|
-
|
|
45
|
-
@cached_property
|
|
46
|
-
def _string(self)->Optional[str]:
|
|
47
|
-
return self.to_string(lambda _: True)
|
|
48
|
-
|
|
49
|
-
def __repr__(self) -> str:
|
|
50
|
-
return self._string or self.__class__.__name__
|
|
51
|
-
def __str__(self) -> str:
|
|
52
|
-
return self._string or self.__class__.__name__
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
A = TypeVar('A') # Result type
|
|
56
|
-
B = TypeVar('B') # Result type for mapping
|
|
57
|
-
|
|
58
|
-
S = TypeVar('S') # State type for the Algebra
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class FrozenDict(Generic[A]):
|
|
63
|
-
def __init__(self, items: Mapping[str, A]):
|
|
64
|
-
for k, v in items.items():
|
|
65
|
-
if not isinstance(k, Hashable) or not isinstance(v, Hashable):
|
|
66
|
-
raise TypeError(f"Metadata key or value not hashable: {k} = {v}")
|
|
67
|
-
self._items = tuple(sorted(items.items()))
|
|
68
|
-
|
|
69
|
-
def __getitem__(self, key: str) -> A:
|
|
70
|
-
return dict(self._items)[key]
|
|
71
|
-
|
|
72
|
-
def __contains__(self, key: str) -> bool:
|
|
73
|
-
return key in dict(self._items)
|
|
74
|
-
|
|
75
|
-
def items(self) -> Iterator[tuple[str, A]]:
|
|
76
|
-
return iter(self._items)
|
|
77
|
-
|
|
78
|
-
def to_dict(self) -> dict[str, A]:
|
|
79
|
-
return dict(self._items)
|
|
80
|
-
|
|
81
|
-
def __hash__(self) -> int:
|
|
82
|
-
return hash(self._items)
|
|
83
|
-
|
|
84
|
-
def __eq__(self, other: object) -> bool:
|
|
85
|
-
return isinstance(other, FrozenDict) and self._items == other._items
|
|
86
|
-
|
|
87
|
-
def __repr__(self) -> str:
|
|
88
|
-
return f"FrozenDict({dict(self._items)})"
|
|
89
|
-
|
|
15
|
+
A = TypeVar('A')
|
|
16
|
+
B = TypeVar('B')
|
|
17
|
+
C = TypeVar('C')
|
|
18
|
+
S = TypeVar('S')
|
|
90
19
|
|
|
91
20
|
|
|
92
21
|
@dataclass(frozen=True)
|
|
93
|
-
class
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
22
|
+
class Bimap(Generic[S, A, B]):
|
|
23
|
+
forward: Callable[[S, A], Tuple[S, B]]
|
|
24
|
+
inverse: Callable[[S, B], Tuple[S, A]]
|
|
25
|
+
def __rshift__(self, other: Bimap[S, B, C]) -> Bimap[S, A, C]:
|
|
26
|
+
def fwd(s: S, a: A) -> Tuple[S, C]:
|
|
27
|
+
s1, b = self.forward(s, a)
|
|
28
|
+
return other.forward(s1, b)
|
|
29
|
+
def inv(s: S, c: C) -> Tuple[S, A]:
|
|
30
|
+
s1, b = other.inverse(s, c)
|
|
31
|
+
return self.inverse(s1, b)
|
|
32
|
+
return Bimap(
|
|
33
|
+
forward=fwd,
|
|
34
|
+
inverse=inv
|
|
35
|
+
)
|
|
36
|
+
@staticmethod
|
|
37
|
+
def identity()->Bimap[S, A, A]:
|
|
38
|
+
return Bimap(
|
|
39
|
+
forward=lambda s, x: (s, x),
|
|
40
|
+
inverse=lambda s, y: (s, y)
|
|
41
|
+
)
|
|
42
|
+
@staticmethod
|
|
43
|
+
def variable(value: A)->Bimap[S, A, A]:
|
|
44
|
+
return Bimap(
|
|
45
|
+
forward=lambda s, _: (s, value),
|
|
46
|
+
inverse=lambda s, y: (s, y)
|
|
47
|
+
)
|
|
48
|
+
@staticmethod
|
|
49
|
+
def const(state: S, value: A)->Bimap[Any, A, A]:
|
|
50
|
+
return Bimap(
|
|
51
|
+
forward=lambda s, _: (state, value),
|
|
52
|
+
inverse=lambda s, y: (state, value)
|
|
53
|
+
)
|
|
54
|
+
@staticmethod
|
|
55
|
+
def combine(*biarrows: Bimap[Any, Any, Any]) -> Bimap[Any, Any, Any]:
|
|
56
|
+
return reduce(lambda a, b: a >> b, biarrows, Bimap.identity())
|
|
57
|
+
|
|
115
58
|
|
|
116
|
-
def __rtruediv__(self, other: Lens[B, S])->Lens[B, A]:
|
|
117
|
-
return other.__truediv__(self)
|
|
118
59
|
|
|
119
60
|
class StructuralResult:
|
|
120
|
-
def bimap(self,
|
|
121
|
-
return (
|
|
122
|
-
|
|
61
|
+
def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, Any, Any]:
|
|
62
|
+
return Bimap.identity()
|
|
123
63
|
|
|
124
64
|
@dataclass(frozen=True)
|
|
125
65
|
class NamedResult(Generic[A], StructuralResult):
|
|
126
66
|
name: str
|
|
127
67
|
value: A
|
|
128
|
-
def bimap(self,
|
|
129
|
-
|
|
130
|
-
def
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
68
|
+
def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, NamedResult[A], NamedResult[Any]]:
|
|
69
|
+
inner_b = self.value.bimap(arr) if isinstance(self.value, StructuralResult) else arr
|
|
70
|
+
def fwd(s: S, a: NamedResult[A])-> Tuple[S, NamedResult[Any]]:
|
|
71
|
+
assert a == self, f"Expected {self}, got {a}"
|
|
72
|
+
inner_s, inner_v = inner_b.forward(s, a.value)
|
|
73
|
+
return (inner_s, replace(a, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, inner_v)
|
|
74
|
+
|
|
75
|
+
def inv(s: S, a: NamedResult[Any]) -> Tuple[S, NamedResult[A]]:
|
|
76
|
+
assert isinstance(a, NamedResult), f"Expected NamedResult, got {type(a)}"
|
|
77
|
+
inner_s, inner_v = inner_b.inverse(s, a.value)
|
|
78
|
+
return (inner_s, replace(self, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, replace(self, value=inner_v.value))
|
|
79
|
+
|
|
80
|
+
return Bimap(
|
|
81
|
+
forward=fwd,
|
|
82
|
+
inverse=inv
|
|
83
|
+
)
|
|
138
84
|
@dataclass(eq=True, frozen=True)
|
|
139
85
|
class ManyResult(Generic[A], StructuralResult):
|
|
140
86
|
value: Tuple[A, ...]
|
|
141
|
-
def bimap(self,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
87
|
+
def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, ManyResult[A], List[A]]:
|
|
88
|
+
inner_b = [v.bimap(arr) if isinstance(v, StructuralResult) else arr for v in self.value]
|
|
89
|
+
def fwd(s: Any, a: ManyResult[A]) -> Tuple[Any, List[A]]:
|
|
90
|
+
assert a == self, f"Expected {self}, got {a}"
|
|
91
|
+
return s, [inner_b[i].forward(s, v)[1] for i, v in enumerate(a.value)]
|
|
92
|
+
|
|
93
|
+
def inv(s: Any, a: List[A]) -> Tuple[Any, ManyResult[A]]:
|
|
94
|
+
assert isinstance(a, list), f"Expected list, got {type(a)}"
|
|
95
|
+
assert len(a) == len(inner_b), f"Expected {len(inner_b)} elements, got {len(a)}"
|
|
96
|
+
return s, ManyResult(value=tuple(inner_b[i].inverse(s, v)[1] for i, v in enumerate(a)))
|
|
97
|
+
return Bimap(
|
|
98
|
+
forward=fwd,
|
|
99
|
+
inverse=inv
|
|
100
|
+
)
|
|
153
101
|
@dataclass(eq=True, frozen=True)
|
|
154
102
|
class OrResult(Generic[A], StructuralResult):
|
|
155
103
|
value: A
|
|
156
|
-
def bimap(self,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
104
|
+
def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, OrResult[A], Any]:
|
|
105
|
+
inner_b = self.value.bimap(arr) if isinstance(self.value, StructuralResult) else arr
|
|
106
|
+
def fwd(s: Any, a: OrResult[A]) -> Tuple[Any, Any]:
|
|
107
|
+
assert a == self, f"Expected {self}, got {a}"
|
|
108
|
+
return inner_b.forward(s, a.value)
|
|
109
|
+
|
|
110
|
+
def inv(s: Any, a: Any) -> Tuple[Any, OrResult[A]]:
|
|
111
|
+
inner_s, inner_v = inner_b.inverse(s, a)
|
|
112
|
+
return inner_s, OrResult(value=inner_v)
|
|
113
|
+
|
|
114
|
+
return Bimap(
|
|
115
|
+
forward=fwd,
|
|
116
|
+
inverse=inv
|
|
117
|
+
)
|
|
161
118
|
class ThenKind(Enum):
|
|
162
119
|
BOTH = '+'
|
|
163
120
|
LEFT = '//'
|
|
@@ -168,49 +125,60 @@ class ThenResult(Generic[A, B], StructuralResult):
|
|
|
168
125
|
kind: ThenKind
|
|
169
126
|
left: A
|
|
170
127
|
right: B
|
|
171
|
-
def
|
|
172
|
-
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
128
|
+
def arity(self)->int:
|
|
129
|
+
if self.kind == ThenKind.LEFT:
|
|
130
|
+
return self.left.arity() if isinstance(self.left, ThenResult) else 1
|
|
131
|
+
elif self.kind == ThenKind.RIGHT:
|
|
132
|
+
return self.right.arity() if isinstance(self.right, ThenResult) else 1
|
|
133
|
+
elif self.kind == ThenKind.BOTH:
|
|
134
|
+
left_arity = self.left.arity() if isinstance(self.left, ThenResult) else 1
|
|
135
|
+
right_arity = self.right.arity() if isinstance(self.right, ThenResult) else 1
|
|
136
|
+
return left_arity + right_arity
|
|
137
|
+
else:
|
|
138
|
+
return 1
|
|
139
|
+
|
|
140
|
+
def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, ThenResult[A, B], Tuple[Any, ...] | Any]:
|
|
141
|
+
kind = self.kind
|
|
142
|
+
lb = self.left.bimap(arr) if isinstance(self.left, StructuralResult) else arr
|
|
143
|
+
rb = self.right.bimap(arr) if isinstance(self.right, StructuralResult) else arr
|
|
144
|
+
left_size = self.left.arity() if isinstance(self.left, ThenResult) else 1
|
|
145
|
+
right_size = self.right.arity() if isinstance(self.right, ThenResult) else 1
|
|
146
|
+
def fwd(s : S, a : ThenResult[A, B]) -> Tuple[S, Tuple[Any, ...] | Any]:
|
|
147
|
+
assert a == self, f"Expected {self}, got {a}"
|
|
148
|
+
match kind:
|
|
149
|
+
case ThenKind.LEFT:
|
|
150
|
+
return lb.forward(s, a.left)
|
|
151
|
+
case ThenKind.RIGHT:
|
|
152
|
+
return rb.forward(s, a.right)
|
|
153
|
+
case ThenKind.BOTH:
|
|
154
|
+
s1, left_v = lb.forward(s, a.left)
|
|
155
|
+
s2, right_v = rb.forward(s1, a.right)
|
|
156
|
+
left_v = (left_v,) if not isinstance(a.left, ThenResult) else left_v
|
|
157
|
+
right_v = (right_v,) if not isinstance(a.right, ThenResult) else right_v
|
|
158
|
+
return s2, left_v + right_v
|
|
159
|
+
|
|
160
|
+
def inv(s: S, b: Tuple[Any, ...] | Any) -> Tuple[S, ThenResult[A, B]]:
|
|
161
|
+
match kind:
|
|
162
|
+
case ThenKind.LEFT:
|
|
163
|
+
s1, lv = lb.inverse(s, b)
|
|
164
|
+
return s1, replace(self, left=lv)
|
|
165
|
+
case ThenKind.RIGHT:
|
|
166
|
+
s1, rv = rb.inverse(s, b)
|
|
167
|
+
return s1, replace(self, right=rv)
|
|
168
|
+
case ThenKind.BOTH:
|
|
169
|
+
lraw = b[:left_size]
|
|
170
|
+
rraw = b[left_size:left_size + right_size]
|
|
171
|
+
lraw = lraw[0] if left_size == 1 else lraw
|
|
172
|
+
rraw = rraw[0] if right_size == 1 else rraw
|
|
173
|
+
s1, lv = lb.inverse(s, lraw)
|
|
174
|
+
s2, rv = rb.inverse(s1, rraw)
|
|
175
|
+
return s2, replace(self, left=lv, right=rv)
|
|
176
|
+
|
|
177
|
+
return Bimap(
|
|
178
|
+
forward=fwd,
|
|
179
|
+
inverse=inv
|
|
180
|
+
)
|
|
181
|
+
|
|
214
182
|
InProgress = object() # Marker for in-progress state, used to prevent re-entrance in recursive calls
|
|
215
183
|
L = TypeVar('L') # Left type for combined results
|
|
216
184
|
R = TypeVar('R') # Right type for combined results
|
|
@@ -233,7 +201,7 @@ class Right(Either[L, R]):
|
|
|
233
201
|
|
|
234
202
|
|
|
235
203
|
@dataclass(frozen=True)
|
|
236
|
-
class Error
|
|
204
|
+
class Error:
|
|
237
205
|
this: Any
|
|
238
206
|
message: Optional[str] = None
|
|
239
207
|
error: Optional[Any] = None
|
|
@@ -254,59 +222,9 @@ class Error(Insptectable):
|
|
|
254
222
|
state=state,
|
|
255
223
|
previous=self
|
|
256
224
|
)
|
|
257
|
-
|
|
258
|
-
|
|
259
225
|
|
|
260
226
|
|
|
261
|
-
def to_list(self, interested: Callable[[Any], bool]) -> List[Dict[str, str]]:
|
|
262
|
-
|
|
263
|
-
def to_dict() -> Dict[str, str]:
|
|
264
|
-
data: Dict[str, str] = {}
|
|
265
|
-
for f in fields(self):
|
|
266
|
-
value = getattr(self, f.name)
|
|
267
|
-
if isinstance(value, Error):
|
|
268
|
-
# self.previous
|
|
269
|
-
pass
|
|
270
|
-
elif isinstance(value, Insptectable):
|
|
271
|
-
# self.this
|
|
272
|
-
def inst(x: Any) -> bool:
|
|
273
|
-
return x in self.algebras
|
|
274
|
-
s = value.to_string(inst)
|
|
275
|
-
data[f.name] = s if s is not None else repr(value)
|
|
276
|
-
elif value is not None:
|
|
277
|
-
# self.committed, self.message, self.expect, self.exception
|
|
278
|
-
data[f.name] = repr(value)
|
|
279
|
-
return data
|
|
280
|
-
ret = []
|
|
281
|
-
tmp : None | Error = self
|
|
282
|
-
while tmp is not None:
|
|
283
|
-
ret.append(to_dict())
|
|
284
|
-
tmp = tmp.previous
|
|
285
|
-
return ret
|
|
286
|
-
|
|
287
|
-
def find(self, predicate: Callable[[Error], bool]) -> Optional[Error]:
|
|
288
|
-
if predicate(self):
|
|
289
|
-
return self
|
|
290
|
-
if self.previous is not None:
|
|
291
|
-
return self.previous.find(predicate)
|
|
292
|
-
return None
|
|
293
|
-
|
|
294
|
-
def to_string(self, interested: Callable[[Any], bool])->str:
|
|
295
|
-
lst = self.to_list(interested)
|
|
296
|
-
root, leaf = lst[0], lst[-1]
|
|
297
|
-
root_fields = ',\n '.join([f"{key}: {value}" for key, value in root.items() if value is not None])
|
|
298
|
-
leaf_fields = ',\n '.join([f"{key}: {value}" for key, value in leaf.items() if value is not None])
|
|
299
|
-
|
|
300
|
-
return f"{self.__class__.__name__}: ROOT\n"\
|
|
301
|
-
f" {root_fields}\n"\
|
|
302
|
-
f"\u25cf \u25cf \u25cf LEAF\n"\
|
|
303
|
-
f" {leaf_fields}\n"
|
|
304
227
|
|
|
305
|
-
|
|
306
|
-
@cached_property
|
|
307
|
-
def algebras(self) -> List[Any]:
|
|
308
|
-
return [self.this] + (self.previous.algebras if self.previous is not None else [])
|
|
309
|
-
|
|
310
228
|
@dataclass(frozen=True)
|
|
311
229
|
class Algebra(ABC, Generic[A, S]):
|
|
312
230
|
######################################################## shared among all subclasses ########################################################
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
import re
|
|
5
5
|
from typing import (
|
|
6
|
-
Optional, Any, TypeVar, Tuple, runtime_checkable,
|
|
6
|
+
Optional, Any, TypeVar, Tuple, runtime_checkable,
|
|
7
7
|
Protocol, Generic, Callable, Union, cast
|
|
8
8
|
)
|
|
9
9
|
from syncraft.algebra import (
|
|
10
|
-
OrResult,ThenResult, ManyResult, ThenKind,NamedResult, StructuralResult
|
|
11
|
-
Lens
|
|
10
|
+
OrResult,ThenResult, ManyResult, ThenKind,NamedResult, StructuralResult
|
|
12
11
|
)
|
|
13
12
|
from dataclasses import dataclass, replace, is_dataclass, asdict
|
|
14
13
|
from enum import Enum
|
|
@@ -67,13 +66,17 @@ class AST(Generic[T]):
|
|
|
67
66
|
pruned: bool = False
|
|
68
67
|
parent: Optional[AST[T]] = None
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
def bimap(self)->Tuple[Any, Callable[[Any], AST[T]]]:
|
|
70
|
+
if isinstance(self.focus, StructuralResult):
|
|
71
|
+
b = self.focus.bimap()
|
|
72
|
+
s, v = b.forward(None, self.focus)
|
|
73
|
+
def inverse(data: Any) -> AST[T]:
|
|
74
|
+
s1, v1 = b.inverse(None, data)
|
|
75
|
+
return replace(self, focus=v1)
|
|
76
|
+
return v, inverse
|
|
77
|
+
else:
|
|
78
|
+
return self.focus, lambda x: replace(self, focus=x)
|
|
79
|
+
|
|
77
80
|
def wrapper(self)-> Callable[[Any], Any]:
|
|
78
81
|
if isinstance(self.focus, NamedResult):
|
|
79
82
|
focus = cast(NamedResult[Any], self.focus)
|
|
@@ -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
|
-
Algebra, ThenResult, Either, Left, Right, Error,
|
|
11
|
-
OrResult, ManyResult
|
|
10
|
+
Algebra, ThenResult, Either, Left, Right, Error,
|
|
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
|
|
@@ -29,7 +31,7 @@ GenResult = Union[
|
|
|
29
31
|
]
|
|
30
32
|
|
|
31
33
|
@dataclass(frozen=True)
|
|
32
|
-
class GenState(Generic[T]
|
|
34
|
+
class GenState(Generic[T]):
|
|
33
35
|
ast: Optional[AST[T]]
|
|
34
36
|
seed: int
|
|
35
37
|
|
|
@@ -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
|
+
|