syncraft 0.1.19__py3-none-any.whl → 0.1.21__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/algebra.py +162 -224
- syncraft/ast.py +13 -10
- syncraft/finder.py +149 -0
- syncraft/generator.py +16 -47
- syncraft/parser.py +2 -16
- syncraft/syntax.py +32 -11
- {syncraft-0.1.19.dist-info → syncraft-0.1.21.dist-info}/METADATA +5 -4
- syncraft-0.1.21.dist-info/RECORD +16 -0
- syncraft-0.1.19.dist-info/RECORD +0 -15
- {syncraft-0.1.19.dist-info → syncraft-0.1.21.dist-info}/WHEEL +0 -0
- {syncraft-0.1.19.dist-info → syncraft-0.1.21.dist-info}/licenses/LICENSE +0 -0
- {syncraft-0.1.19.dist-info → syncraft-0.1.21.dist-info}/top_level.txt +0 -0
syncraft/algebra.py
CHANGED
|
@@ -1,163 +1,136 @@
|
|
|
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
|
-
|
|
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
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def when(condition: Callable[..., bool],
|
|
45
|
+
then: Bimap[S, A, B],
|
|
46
|
+
otherwise: Optional[Bimap[S, A, B]] = None) -> Callable[..., Bimap[S, A, B]]:
|
|
47
|
+
def _when(*args:Any, **kwargs:Any) -> Bimap[S, A, B]:
|
|
48
|
+
return then if condition(*args, **kwargs) else (otherwise or Bimap.identity())
|
|
49
|
+
return _when
|
|
99
50
|
|
|
100
|
-
def bimap(self, ff: Callable[[A], B], bf: Callable[[B], A]) -> Lens[S, B]:
|
|
101
|
-
def getf(data: S) -> B:
|
|
102
|
-
return ff(self.get(data))
|
|
103
|
-
|
|
104
|
-
def setf(data: S, value: B) -> S:
|
|
105
|
-
return self.set(data, bf(value))
|
|
106
|
-
|
|
107
|
-
return Lens(get=getf, set=setf)
|
|
108
51
|
|
|
109
|
-
def __truediv__(self, other: Lens[A, B]) -> Lens[S, B]:
|
|
110
|
-
def get_composed(obj: S) -> B:
|
|
111
|
-
return other.get(self.get(obj))
|
|
112
|
-
def set_composed(obj: S, value: B) -> S:
|
|
113
|
-
return self.set(obj, other.set(self.get(obj), value))
|
|
114
|
-
return Lens(get=get_composed, set=set_composed)
|
|
115
52
|
|
|
116
|
-
def __rtruediv__(self, other: Lens[B, S])->Lens[B, A]:
|
|
117
|
-
return other.__truediv__(self)
|
|
118
|
-
|
|
119
53
|
class StructuralResult:
|
|
120
|
-
def bimap(self,
|
|
121
|
-
|
|
122
|
-
|
|
54
|
+
def bimap(self,
|
|
55
|
+
before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
|
|
56
|
+
after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
|
|
57
|
+
) -> Bimap[Any, Any, Any]:
|
|
58
|
+
return Bimap.identity()
|
|
123
59
|
|
|
124
60
|
@dataclass(frozen=True)
|
|
125
61
|
class NamedResult(Generic[A], StructuralResult):
|
|
126
62
|
name: str
|
|
127
63
|
value: A
|
|
128
|
-
def bimap(self,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
64
|
+
def bimap(self,
|
|
65
|
+
before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
|
|
66
|
+
after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
|
|
67
|
+
) -> Bimap[Any, NamedResult[A], NamedResult[Any]]:
|
|
68
|
+
inner_b = self.value.bimap(before, after) if isinstance(self.value, StructuralResult) else before(self.value) >> after(self.value)
|
|
69
|
+
def fwd(s: S, a: NamedResult[A])-> Tuple[S, NamedResult[Any]]:
|
|
70
|
+
assert a == self, f"Expected {self}, got {a}"
|
|
71
|
+
inner_s, inner_v = inner_b.forward(s, a.value)
|
|
72
|
+
return (inner_s, replace(a, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, inner_v)
|
|
73
|
+
|
|
74
|
+
def inv(s: S, a: NamedResult[Any]) -> Tuple[S, NamedResult[A]]:
|
|
75
|
+
assert isinstance(a, NamedResult), f"Expected NamedResult, got {type(a)}"
|
|
76
|
+
inner_s, inner_v = inner_b.inverse(s, a.value)
|
|
77
|
+
return (inner_s, replace(self, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, replace(self, value=inner_v.value))
|
|
78
|
+
ret: Bimap[Any, Any, Any] = Bimap(
|
|
79
|
+
forward=fwd,
|
|
80
|
+
inverse=inv
|
|
81
|
+
)
|
|
82
|
+
return before(self) >> ret >> after(self)
|
|
138
83
|
@dataclass(eq=True, frozen=True)
|
|
139
84
|
class ManyResult(Generic[A], StructuralResult):
|
|
140
85
|
value: Tuple[A, ...]
|
|
141
|
-
def bimap(self,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
86
|
+
def bimap(self,
|
|
87
|
+
before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
|
|
88
|
+
after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
|
|
89
|
+
) -> Bimap[Any, ManyResult[A], List[A]]:
|
|
90
|
+
# We don't allow zero length ManyResult e.g. many(at_least >= 1) at the syntax level
|
|
91
|
+
# so inner_b has at least 1 element
|
|
92
|
+
assert len(self.value) > 0, "ManyResult must have at least one element"
|
|
93
|
+
inner_b = [v.bimap(before, after) if isinstance(v, StructuralResult) else before(v) >> after(v) for v in self.value]
|
|
94
|
+
def fwd(s: Any, a: ManyResult[A]) -> Tuple[Any, List[A]]:
|
|
95
|
+
assert a == self, f"Expected {self}, got {a}"
|
|
96
|
+
return s, [inner_b[i].forward(s, v)[1] for i, v in enumerate(a.value)]
|
|
97
|
+
|
|
98
|
+
def inv(s: Any, a: List[A]) -> Tuple[Any, ManyResult[A]]:
|
|
99
|
+
assert isinstance(a, list), f"Expected list, got {type(a)}"
|
|
100
|
+
ret = [inner_b[i].inverse(s, v)[1] for i, v in enumerate(a)]
|
|
101
|
+
if len(ret) <= len(inner_b):
|
|
102
|
+
return s, ManyResult(value=tuple(ret))
|
|
103
|
+
else:
|
|
104
|
+
extra = [inner_b[-1].inverse(s, v)[1] for v in a[len(inner_b):]]
|
|
105
|
+
return s, ManyResult(value=tuple(ret + extra))
|
|
151
106
|
|
|
107
|
+
ret = Bimap(
|
|
108
|
+
forward=fwd,
|
|
109
|
+
inverse=inv
|
|
110
|
+
)
|
|
111
|
+
return before(self) >> ret >> after(self)
|
|
152
112
|
|
|
153
113
|
@dataclass(eq=True, frozen=True)
|
|
154
114
|
class OrResult(Generic[A], StructuralResult):
|
|
155
115
|
value: A
|
|
156
|
-
def bimap(self,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
116
|
+
def bimap(self,
|
|
117
|
+
before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
|
|
118
|
+
after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
|
|
119
|
+
) -> Bimap[Any, OrResult[A], Any]:
|
|
120
|
+
inner_b = self.value.bimap(before, after) if isinstance(self.value, StructuralResult) else before(self.value) >> after(self.value)
|
|
121
|
+
def fwd(s: Any, a: OrResult[A]) -> Tuple[Any, Any]:
|
|
122
|
+
assert a == self, f"Expected {self}, got {a}"
|
|
123
|
+
return inner_b.forward(s, a.value)
|
|
124
|
+
|
|
125
|
+
def inv(s: Any, a: Any) -> Tuple[Any, OrResult[A]]:
|
|
126
|
+
inner_s, inner_v = inner_b.inverse(s, a)
|
|
127
|
+
return inner_s, OrResult(value=inner_v)
|
|
128
|
+
|
|
129
|
+
ret = Bimap(
|
|
130
|
+
forward=fwd,
|
|
131
|
+
inverse=inv
|
|
132
|
+
)
|
|
133
|
+
return before(self) >> ret >> after(self)
|
|
161
134
|
class ThenKind(Enum):
|
|
162
135
|
BOTH = '+'
|
|
163
136
|
LEFT = '//'
|
|
@@ -168,49 +141,64 @@ class ThenResult(Generic[A, B], StructuralResult):
|
|
|
168
141
|
kind: ThenKind
|
|
169
142
|
left: A
|
|
170
143
|
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
|
-
if
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
144
|
+
def arity(self)->int:
|
|
145
|
+
if self.kind == ThenKind.LEFT:
|
|
146
|
+
return self.left.arity() if isinstance(self.left, ThenResult) else 1
|
|
147
|
+
elif self.kind == ThenKind.RIGHT:
|
|
148
|
+
return self.right.arity() if isinstance(self.right, ThenResult) else 1
|
|
149
|
+
elif self.kind == ThenKind.BOTH:
|
|
150
|
+
left_arity = self.left.arity() if isinstance(self.left, ThenResult) else 1
|
|
151
|
+
right_arity = self.right.arity() if isinstance(self.right, ThenResult) else 1
|
|
152
|
+
return left_arity + right_arity
|
|
153
|
+
else:
|
|
154
|
+
return 1
|
|
155
|
+
|
|
156
|
+
def bimap(self,
|
|
157
|
+
before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
|
|
158
|
+
after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
|
|
159
|
+
) -> Bimap[Any, ThenResult[A, B], Tuple[Any, ...] | Any]:
|
|
160
|
+
kind = self.kind
|
|
161
|
+
lb = self.left.bimap(before, after) if isinstance(self.left, StructuralResult) else before(self.left) >> after(self.left)
|
|
162
|
+
rb = self.right.bimap(before, after) if isinstance(self.right, StructuralResult) else before(self.right) >> after(self.right)
|
|
163
|
+
left_size = self.left.arity() if isinstance(self.left, ThenResult) else 1
|
|
164
|
+
right_size = self.right.arity() if isinstance(self.right, ThenResult) else 1
|
|
165
|
+
def fwd(s : S, a : ThenResult[A, B]) -> Tuple[S, Tuple[Any, ...] | Any]:
|
|
166
|
+
assert a == self, f"Expected {self}, got {a}"
|
|
167
|
+
match kind:
|
|
168
|
+
case ThenKind.LEFT:
|
|
169
|
+
return lb.forward(s, a.left)
|
|
170
|
+
case ThenKind.RIGHT:
|
|
171
|
+
return rb.forward(s, a.right)
|
|
172
|
+
case ThenKind.BOTH:
|
|
173
|
+
s1, left_v = lb.forward(s, a.left)
|
|
174
|
+
s2, right_v = rb.forward(s1, a.right)
|
|
175
|
+
left_v = (left_v,) if not isinstance(a.left, ThenResult) else left_v
|
|
176
|
+
right_v = (right_v,) if not isinstance(a.right, ThenResult) else right_v
|
|
177
|
+
return s2, left_v + right_v
|
|
178
|
+
|
|
179
|
+
def inv(s: S, b: Tuple[Any, ...] | Any) -> Tuple[S, ThenResult[A, B]]:
|
|
180
|
+
match kind:
|
|
181
|
+
case ThenKind.LEFT:
|
|
182
|
+
s1, lv = lb.inverse(s, b)
|
|
183
|
+
return s1, replace(self, left=lv)
|
|
184
|
+
case ThenKind.RIGHT:
|
|
185
|
+
s1, rv = rb.inverse(s, b)
|
|
186
|
+
return s1, replace(self, right=rv)
|
|
187
|
+
case ThenKind.BOTH:
|
|
188
|
+
lraw = b[:left_size]
|
|
189
|
+
rraw = b[left_size:left_size + right_size]
|
|
190
|
+
lraw = lraw[0] if left_size == 1 else lraw
|
|
191
|
+
rraw = rraw[0] if right_size == 1 else rraw
|
|
192
|
+
s1, lv = lb.inverse(s, lraw)
|
|
193
|
+
s2, rv = rb.inverse(s1, rraw)
|
|
194
|
+
return s2, replace(self, left=lv, right=rv)
|
|
195
|
+
|
|
196
|
+
ret: Bimap[Any, Any, Any] = Bimap(
|
|
197
|
+
forward=fwd,
|
|
198
|
+
inverse=inv
|
|
199
|
+
)
|
|
200
|
+
return before(self) >> ret >> after(self)
|
|
201
|
+
|
|
214
202
|
InProgress = object() # Marker for in-progress state, used to prevent re-entrance in recursive calls
|
|
215
203
|
L = TypeVar('L') # Left type for combined results
|
|
216
204
|
R = TypeVar('R') # Right type for combined results
|
|
@@ -233,7 +221,7 @@ class Right(Either[L, R]):
|
|
|
233
221
|
|
|
234
222
|
|
|
235
223
|
@dataclass(frozen=True)
|
|
236
|
-
class Error
|
|
224
|
+
class Error:
|
|
237
225
|
this: Any
|
|
238
226
|
message: Optional[str] = None
|
|
239
227
|
error: Optional[Any] = None
|
|
@@ -254,59 +242,9 @@ class Error(Insptectable):
|
|
|
254
242
|
state=state,
|
|
255
243
|
previous=self
|
|
256
244
|
)
|
|
257
|
-
|
|
258
245
|
|
|
259
246
|
|
|
260
247
|
|
|
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
|
-
|
|
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
248
|
@dataclass(frozen=True)
|
|
311
249
|
class Algebra(ABC, Generic[A, S]):
|
|
312
250
|
######################################################## shared among all subclasses ########################################################
|
syncraft/ast.py
CHANGED
|
@@ -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)
|
syncraft/finder.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
Any, Tuple, Optional, Callable,
|
|
5
|
+
List, Generator as YieldGen
|
|
6
|
+
)
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from syncraft.algebra import (
|
|
9
|
+
Algebra, Either, Left, Right, Error,
|
|
10
|
+
OrResult, ManyResult, ThenResult, NamedResult
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from syncraft.ast import T, ParseResult, Token, AST
|
|
14
|
+
from syncraft.generator import GenState, TokenGen, B
|
|
15
|
+
from sqlglot import TokenType
|
|
16
|
+
from syncraft.syntax import Syntax
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class Finder(Algebra[ParseResult[T], GenState[T]]):
|
|
21
|
+
def flat_map(self, f: Callable[[ParseResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
|
|
22
|
+
def flat_map_run(original: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
|
|
23
|
+
wrapper = original.wrapper()
|
|
24
|
+
input = original if not original.is_named else original.down(0) # If the input is named, we need to go down to the first child
|
|
25
|
+
try:
|
|
26
|
+
lft = input.left()
|
|
27
|
+
match self.run(lft, use_cache=use_cache):
|
|
28
|
+
case Left(error):
|
|
29
|
+
return Left(error)
|
|
30
|
+
case Right((value, next_input)):
|
|
31
|
+
r = input.right()
|
|
32
|
+
match f(value).run(r, use_cache):
|
|
33
|
+
case Left(e):
|
|
34
|
+
return Left(e)
|
|
35
|
+
case Right((result, next_input)):
|
|
36
|
+
return Right((wrapper(result), next_input))
|
|
37
|
+
raise ValueError("flat_map should always return a value or an error.")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
return Left(Error(
|
|
40
|
+
message=str(e),
|
|
41
|
+
this=self,
|
|
42
|
+
state=original,
|
|
43
|
+
error=e
|
|
44
|
+
))
|
|
45
|
+
return Finder(run_f = flat_map_run, name=self.name) # type: ignore
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[ParseResult[T]], GenState[T]]:
|
|
49
|
+
assert at_least > 0, "at_least must be greater than 0"
|
|
50
|
+
assert at_most is None or at_least <= at_most, "at_least must be less than or equal to at_most"
|
|
51
|
+
def many_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ManyResult[ParseResult[T]], GenState[T]]]:
|
|
52
|
+
wrapper = input.wrapper()
|
|
53
|
+
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
54
|
+
ret = []
|
|
55
|
+
for index in range(input.how_many):
|
|
56
|
+
match self.run(input.down(index), use_cache):
|
|
57
|
+
case Right((value, next_input)):
|
|
58
|
+
ret.append(value)
|
|
59
|
+
if at_most is not None and len(ret) > at_most:
|
|
60
|
+
return Left(Error(
|
|
61
|
+
message=f"Expected at most {at_most} matches, got {len(ret)}",
|
|
62
|
+
this=self,
|
|
63
|
+
state=input.down(index)
|
|
64
|
+
))
|
|
65
|
+
case Left(_):
|
|
66
|
+
pass
|
|
67
|
+
if len(ret) < at_least:
|
|
68
|
+
return Left(Error(
|
|
69
|
+
message=f"Expected at least {at_least} matches, got {len(ret)}",
|
|
70
|
+
this=self,
|
|
71
|
+
state=input.down(index)
|
|
72
|
+
))
|
|
73
|
+
return Right((wrapper(ManyResult(tuple(ret))), input))
|
|
74
|
+
return self.__class__(many_run, name=f"many({self.name})") # type: ignore
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def or_else(self, # type: ignore
|
|
78
|
+
other: Algebra[ParseResult[T], GenState[T]]
|
|
79
|
+
) -> Algebra[OrResult[ParseResult[T]], GenState[T]]:
|
|
80
|
+
def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[OrResult[ParseResult[T]], GenState[T]]]:
|
|
81
|
+
wrapper = input.wrapper()
|
|
82
|
+
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
83
|
+
match self.run(input.down(0), use_cache):
|
|
84
|
+
case Right((value, next_input)):
|
|
85
|
+
return Right((wrapper(OrResult(value)), next_input))
|
|
86
|
+
case Left(error):
|
|
87
|
+
match other.run(input.down(0), use_cache):
|
|
88
|
+
case Right((value, next_input)):
|
|
89
|
+
return Right((wrapper(OrResult(value)), next_input))
|
|
90
|
+
case Left(error):
|
|
91
|
+
return Left(error)
|
|
92
|
+
raise ValueError("or_else should always return a value or an error.")
|
|
93
|
+
return self.__class__(or_else_run, name=f"or_else({self.name} | {other.name})") # type: ignore
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def token(cls,
|
|
97
|
+
token_type: Optional[TokenType] = None,
|
|
98
|
+
text: Optional[str] = None,
|
|
99
|
+
case_sensitive: bool = False,
|
|
100
|
+
regex: Optional[re.Pattern[str]] = None
|
|
101
|
+
)-> Algebra[ParseResult[T], GenState[T]]:
|
|
102
|
+
gen = TokenGen(token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
|
|
103
|
+
lazy_self: Algebra[ParseResult[T], GenState[T]]
|
|
104
|
+
def token_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ParseResult[Token], GenState[T]]]:
|
|
105
|
+
wrapper = input.wrapper()
|
|
106
|
+
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
107
|
+
current = input.focus
|
|
108
|
+
if not isinstance(current, Token) or not gen.is_valid(current):
|
|
109
|
+
return Left(Error(None,
|
|
110
|
+
message=f"Expected a Token, but got {type(current)}.",
|
|
111
|
+
state=input))
|
|
112
|
+
return Right((wrapper(current), input))
|
|
113
|
+
lazy_self = cls(token_run, name=cls.__name__ + f'.token({token_type or text or regex})') # type: ignore
|
|
114
|
+
return lazy_self
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def anything(cls)->Algebra[ParseResult[T], GenState[T]]:
|
|
118
|
+
def anything_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ParseResult[T], GenState[T]]]:
|
|
119
|
+
wrapper = input.wrapper()
|
|
120
|
+
return Right((wrapper(input.focus), input))
|
|
121
|
+
return cls(anything_run, name=cls.__name__ + '.anything()')
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
anything = Syntax(lambda cls: cls.factory('anything')).describe(name="anything", fixity='infix')
|
|
126
|
+
|
|
127
|
+
def matches(syntax: Syntax[Any, Any], data: AST[Any])-> bool:
|
|
128
|
+
gen = syntax(Finder)
|
|
129
|
+
state = GenState.from_ast(data)
|
|
130
|
+
result = gen.run(state, use_cache=True)
|
|
131
|
+
return isinstance(result, Right)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def find(syntax: Syntax[Any, Any], data: AST[Any]) -> YieldGen[AST[Any], None, None]:
|
|
135
|
+
if matches(syntax, data):
|
|
136
|
+
yield data
|
|
137
|
+
match data.focus:
|
|
138
|
+
case ThenResult(left = left, right=right):
|
|
139
|
+
yield from find(syntax, AST(left))
|
|
140
|
+
yield from find(syntax, AST(right))
|
|
141
|
+
case ManyResult(value = value):
|
|
142
|
+
for e in value:
|
|
143
|
+
yield from find(syntax, AST(e))
|
|
144
|
+
case NamedResult(value=value):
|
|
145
|
+
yield from find(syntax, AST(value))
|
|
146
|
+
case OrResult(value=value):
|
|
147
|
+
yield from find(syntax, AST(value))
|
|
148
|
+
case _:
|
|
149
|
+
pass
|
syncraft/generator.py
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from typing import (
|
|
4
|
-
Any, TypeVar, Tuple, Optional, Callable, Generic,
|
|
5
|
-
List,
|
|
4
|
+
Any, TypeVar, Tuple, Optional, Callable, Generic,
|
|
5
|
+
List,
|
|
6
6
|
)
|
|
7
7
|
from functools import cached_property
|
|
8
8
|
from dataclasses import dataclass, replace
|
|
9
9
|
from syncraft.algebra import (
|
|
10
|
-
Algebra,
|
|
11
|
-
OrResult, ManyResult
|
|
10
|
+
Algebra, Either, Left, Right, Error,
|
|
11
|
+
OrResult, ManyResult
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
-
from syncraft.ast import
|
|
14
|
+
from syncraft.ast import T, ParseResult, AST, Token, TokenSpec
|
|
15
15
|
from syncraft.syntax import Syntax
|
|
16
16
|
from sqlglot import TokenType
|
|
17
17
|
import re
|
|
@@ -20,18 +20,10 @@ from functools import lru_cache
|
|
|
20
20
|
import random
|
|
21
21
|
|
|
22
22
|
B = TypeVar('B')
|
|
23
|
-
T = TypeVar('T', bound=TokenProtocol)
|
|
24
23
|
|
|
25
|
-
GenResult = Union[
|
|
26
|
-
ThenResult['GenResult[T]', 'GenResult[T]'],
|
|
27
|
-
ManyResult['GenResult[T]'],
|
|
28
|
-
OrResult['GenResult[T]'],
|
|
29
|
-
|
|
30
|
-
T
|
|
31
|
-
]
|
|
32
24
|
|
|
33
25
|
@dataclass(frozen=True)
|
|
34
|
-
class GenState(Generic[T]
|
|
26
|
+
class GenState(Generic[T]):
|
|
35
27
|
ast: Optional[AST[T]]
|
|
36
28
|
seed: int
|
|
37
29
|
|
|
@@ -150,8 +142,8 @@ class TokenGen(TokenSpec):
|
|
|
150
142
|
|
|
151
143
|
|
|
152
144
|
@dataclass(frozen=True)
|
|
153
|
-
class Generator(Algebra[
|
|
154
|
-
def flat_map(self, f: Callable[[
|
|
145
|
+
class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
146
|
+
def flat_map(self, f: Callable[[ParseResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
|
|
155
147
|
def flat_map_run(original: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
|
|
156
148
|
wrapper = original.wrapper()
|
|
157
149
|
input = original if not original.is_named else original.down(0) # If the input is named, we need to go down to the first child
|
|
@@ -178,10 +170,10 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
178
170
|
return Generator(run_f = flat_map_run, name=self.name) # type: ignore
|
|
179
171
|
|
|
180
172
|
|
|
181
|
-
def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[
|
|
173
|
+
def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[ParseResult[T]], GenState[T]]:
|
|
182
174
|
assert at_least > 0, "at_least must be greater than 0"
|
|
183
175
|
assert at_most is None or at_least <= at_most, "at_least must be less than or equal to at_most"
|
|
184
|
-
def many_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ManyResult[
|
|
176
|
+
def many_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ManyResult[ParseResult[T]], GenState[T]]]:
|
|
185
177
|
wrapper = input.wrapper()
|
|
186
178
|
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
187
179
|
if input.pruned:
|
|
@@ -221,9 +213,9 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
221
213
|
|
|
222
214
|
|
|
223
215
|
def or_else(self, # type: ignore
|
|
224
|
-
other: Algebra[
|
|
225
|
-
) -> Algebra[OrResult[
|
|
226
|
-
def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[OrResult[
|
|
216
|
+
other: Algebra[ParseResult[T], GenState[T]]
|
|
217
|
+
) -> Algebra[OrResult[ParseResult[T]], GenState[T]]:
|
|
218
|
+
def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[OrResult[ParseResult[T]], GenState[T]]]:
|
|
227
219
|
wrapper = input.wrapper()
|
|
228
220
|
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
229
221
|
if input.pruned:
|
|
@@ -252,10 +244,10 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
|
|
|
252
244
|
text: Optional[str] = None,
|
|
253
245
|
case_sensitive: bool = False,
|
|
254
246
|
regex: Optional[re.Pattern[str]] = None
|
|
255
|
-
)-> Algebra[
|
|
247
|
+
)-> Algebra[ParseResult[T], GenState[T]]:
|
|
256
248
|
gen = TokenGen(token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
|
|
257
|
-
lazy_self: Algebra[
|
|
258
|
-
def token_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[
|
|
249
|
+
lazy_self: Algebra[ParseResult[T], GenState[T]]
|
|
250
|
+
def token_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ParseResult[Token], GenState[T]]]:
|
|
259
251
|
wrapper = input.wrapper()
|
|
260
252
|
input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
|
|
261
253
|
if input.pruned:
|
|
@@ -282,27 +274,4 @@ def generate(syntax: Syntax[Any, Any], data: Optional[AST[Any]] = None, seed: in
|
|
|
282
274
|
return result.value
|
|
283
275
|
|
|
284
276
|
|
|
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
277
|
|
syncraft/parser.py
CHANGED
|
@@ -6,7 +6,7 @@ from typing import (
|
|
|
6
6
|
Generic, Callable
|
|
7
7
|
)
|
|
8
8
|
from syncraft.algebra import (
|
|
9
|
-
Either, Left, Right, Error,
|
|
9
|
+
Either, Left, Right, Error, Algebra
|
|
10
10
|
)
|
|
11
11
|
from dataclasses import dataclass, field, replace
|
|
12
12
|
from enum import Enum
|
|
@@ -16,15 +16,8 @@ from syncraft.syntax import Syntax
|
|
|
16
16
|
from syncraft.ast import Token, TokenSpec, AST, T
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
19
|
@dataclass(frozen=True)
|
|
27
|
-
class ParserState(Generic[T]
|
|
20
|
+
class ParserState(Generic[T]):
|
|
28
21
|
input: Tuple[T, ...] = field(default_factory=tuple)
|
|
29
22
|
index: int = 0
|
|
30
23
|
|
|
@@ -40,13 +33,6 @@ class ParserState(Generic[T], Insptectable):
|
|
|
40
33
|
def after(self, length: Optional[int] = 5)->str:
|
|
41
34
|
length = min(length, len(self.input) - self.index) if length is not None else len(self.input) - self.index
|
|
42
35
|
return " ".join(token.text for token in self.input[self.index:self.index + length])
|
|
43
|
-
|
|
44
|
-
def to_string(self, interested: Callable[[Any], bool])->str:
|
|
45
|
-
return f"ParserState(\n"\
|
|
46
|
-
f"index={self.index}, \n"\
|
|
47
|
-
f"input({len(self.input)})=[{self.token_sample_string()}, ...]), \n"\
|
|
48
|
-
f"before=({self.before()}), \n"\
|
|
49
|
-
f"after=({self.after()})"
|
|
50
36
|
|
|
51
37
|
|
|
52
38
|
def current(self)->T:
|
syncraft/syntax.py
CHANGED
|
@@ -6,7 +6,7 @@ from typing import (
|
|
|
6
6
|
)
|
|
7
7
|
from dataclasses import dataclass, field, replace
|
|
8
8
|
from functools import reduce
|
|
9
|
-
from syncraft.algebra import Algebra, Error, Either,
|
|
9
|
+
from syncraft.algebra import Algebra, Error, Either, ThenResult, ManyResult, ThenKind, NamedResult
|
|
10
10
|
from types import MethodType, FunctionType
|
|
11
11
|
|
|
12
12
|
|
|
@@ -17,8 +17,35 @@ B = TypeVar('B') # Result type for mapping
|
|
|
17
17
|
C = TypeVar('C') # Result type for else branch
|
|
18
18
|
S = TypeVar('S') # State type
|
|
19
19
|
|
|
20
|
+
|
|
21
|
+
class Variable(Generic[A]):
|
|
22
|
+
def __init__(self, name: Optional[str] = None):
|
|
23
|
+
self.name = name
|
|
24
|
+
self.initialized = False
|
|
25
|
+
self._value = None
|
|
26
|
+
def __call__(self, a: Any) -> Any:
|
|
27
|
+
if self.initialized:
|
|
28
|
+
if self._value != a:
|
|
29
|
+
raise ValueError(f"Variable {self.name or ''} is already initialized")
|
|
30
|
+
else:
|
|
31
|
+
return a
|
|
32
|
+
elif self.name is not None and isinstance(a, NamedResult):
|
|
33
|
+
if a.name == self.name:
|
|
34
|
+
self._value = a.value
|
|
35
|
+
self.initialized = True
|
|
36
|
+
else:
|
|
37
|
+
self._value = a
|
|
38
|
+
self.initialized = True
|
|
39
|
+
return a
|
|
40
|
+
@property
|
|
41
|
+
def value(self) -> A:
|
|
42
|
+
if self.initialized is False or self._value is None:
|
|
43
|
+
raise ValueError(f"Variable {self.name or ''} is not initialized")
|
|
44
|
+
return self._value
|
|
45
|
+
|
|
46
|
+
|
|
20
47
|
@dataclass(frozen=True)
|
|
21
|
-
class Description
|
|
48
|
+
class Description:
|
|
22
49
|
name: Optional[str] = None
|
|
23
50
|
newline: Optional[str] = None
|
|
24
51
|
fixity: Literal['infix', 'prefix', 'postfix'] = 'infix'
|
|
@@ -67,7 +94,7 @@ class Description(Insptectable):
|
|
|
67
94
|
|
|
68
95
|
|
|
69
96
|
@dataclass(frozen=True)
|
|
70
|
-
class Syntax(Generic[A, S]
|
|
97
|
+
class Syntax(Generic[A, S]):
|
|
71
98
|
alg: Callable[[Type[Algebra[Any, Any]]], Algebra[A, S]]
|
|
72
99
|
meta: Description = field(default_factory=Description, repr=False)
|
|
73
100
|
|
|
@@ -98,8 +125,6 @@ class Syntax(Generic[A, S], Insptectable):
|
|
|
98
125
|
return self.__class__(alg=algebra_run, meta=self.meta)
|
|
99
126
|
|
|
100
127
|
|
|
101
|
-
|
|
102
|
-
|
|
103
128
|
def as_(self, typ: Type[B])->B:
|
|
104
129
|
return cast(typ, self) # type: ignore
|
|
105
130
|
|
|
@@ -234,6 +259,8 @@ class Syntax(Generic[A, S], Insptectable):
|
|
|
234
259
|
return NamedResult(name=name, value=value)
|
|
235
260
|
return self.map(bind_f).describe(name=f'bind("{name}")', fixity='postfix', parameter=[self])
|
|
236
261
|
|
|
262
|
+
def assign(self, var: Variable[A]) -> Syntax[A, S]:
|
|
263
|
+
return self.map(var).describe(name=f'assign({var.name or ""})', fixity='postfix', parameter=[self])
|
|
237
264
|
|
|
238
265
|
def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> Syntax[A, S]:
|
|
239
266
|
def dump_error_run(err: Any)->Any:
|
|
@@ -263,12 +290,6 @@ def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
|
263
290
|
return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(None)
|
|
264
291
|
|
|
265
292
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
293
|
def all(*parsers: Syntax[Any, S]) -> Syntax[ThenResult[Any, Any], S]:
|
|
273
294
|
return reduce(lambda a, b: a + b, parsers) if len(parsers) > 0 else success(None)
|
|
274
295
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syncraft
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.21
|
|
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,16 @@
|
|
|
1
|
+
syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
syncraft/algebra.py,sha256=bya-2b5WpCcOMxgJjGqrFaNG8dCjs5NUqnxbJTc4uOM,21908
|
|
3
|
+
syncraft/ast.py,sha256=Hn88_xOqSzrHYtPjNFKREJuE6yISmNI-VVTcU6PJKI0,4737
|
|
4
|
+
syncraft/cmd.py,sha256=DzXgU8QLVOg0YTFKpmOyzbf02LKPphQ4EeKSLzqpJ_s,2012
|
|
5
|
+
syncraft/diagnostic.py,sha256=mnzzceE9HpN2jVa7ztecZCz3sbCD79JxgkdLhJRBsvY,2822
|
|
6
|
+
syncraft/finder.py,sha256=zADOQQdcHu60413ZrRxTSq-E5EAJ9xv0OQodCpoYpo0,7202
|
|
7
|
+
syncraft/generator.py,sha256=Mh9Sx1hNR0EAWcox6MMvVw7aq2s33TU4stGZFZ1N1cw,11287
|
|
8
|
+
syncraft/parser.py,sha256=PSy9s3q_KXQh-Qlg0NxvJixxcgVYi3yCq0ebqlA5WwM,11288
|
|
9
|
+
syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
syncraft/sqlite3.py,sha256=Pq09IHZOwuWg5W82l9D1flzd36QV0TOHQpTJ5U02V8g,34701
|
|
11
|
+
syncraft/syntax.py,sha256=yUp3DmDnkwNEqDAFZH4vesjdnbIUsrwFjWN-Ljy1qN4,14880
|
|
12
|
+
syncraft-0.1.21.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
|
|
13
|
+
syncraft-0.1.21.dist-info/METADATA,sha256=cJyW-BifZOf3RdmFKp1-auJv13aMNjRwk1VDqPG5gR4,1381
|
|
14
|
+
syncraft-0.1.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
syncraft-0.1.21.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
|
|
16
|
+
syncraft-0.1.21.dist-info/RECORD,,
|
syncraft-0.1.19.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
syncraft/algebra.py,sha256=l_ptzSkXbRGSLzu46GXixcNQPdXAi1s5R9nRDI86JkY,22785
|
|
3
|
-
syncraft/ast.py,sha256=9CuHqXRiw_7UMDLd4x2def3kHgTVQ0hnPxyIbBlx1Zc,4640
|
|
4
|
-
syncraft/cmd.py,sha256=DzXgU8QLVOg0YTFKpmOyzbf02LKPphQ4EeKSLzqpJ_s,2012
|
|
5
|
-
syncraft/diagnostic.py,sha256=mnzzceE9HpN2jVa7ztecZCz3sbCD79JxgkdLhJRBsvY,2822
|
|
6
|
-
syncraft/generator.py,sha256=P2NoSBnTn4i3YV-fuIozmzXwRPR7J8sN-Paj3gQXrtg,12383
|
|
7
|
-
syncraft/parser.py,sha256=BzNOiuc9iLt0wSxN79bNC9x0xsMIQcDdIN1Ndp0D3B8,11642
|
|
8
|
-
syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
syncraft/sqlite3.py,sha256=Pq09IHZOwuWg5W82l9D1flzd36QV0TOHQpTJ5U02V8g,34701
|
|
10
|
-
syncraft/syntax.py,sha256=XTXx0Sf2Elbt0GVTYPt-g-1InTPvZeptcpquugf8My0,13884
|
|
11
|
-
syncraft-0.1.19.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
|
|
12
|
-
syncraft-0.1.19.dist-info/METADATA,sha256=c-xyYpD0DMsrsZCHNzqyK7X5e8JneLA84Rm5T7m2kHc,946
|
|
13
|
-
syncraft-0.1.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
-
syncraft-0.1.19.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
|
|
15
|
-
syncraft-0.1.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|