syncraft 0.2.2__py3-none-any.whl → 0.2.3__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/__init__.py +59 -0
- syncraft/algebra.py +230 -25
- syncraft/ast.py +101 -4
- syncraft/constraint.py +41 -0
- syncraft/finder.py +71 -14
- syncraft/generator.py +181 -4
- syncraft/parser.py +162 -0
- syncraft/syntax.py +339 -105
- syncraft-0.2.3.dist-info/METADATA +113 -0
- syncraft-0.2.3.dist-info/RECORD +16 -0
- syncraft-0.2.2.dist-info/METADATA +0 -34
- syncraft-0.2.2.dist-info/RECORD +0 -16
- {syncraft-0.2.2.dist-info → syncraft-0.2.3.dist-info}/WHEEL +0 -0
- {syncraft-0.2.2.dist-info → syncraft-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {syncraft-0.2.2.dist-info → syncraft-0.2.3.dist-info}/top_level.txt +0 -0
syncraft/syntax.py
CHANGED
|
@@ -56,9 +56,7 @@ class Description:
|
|
|
56
56
|
left = self.parameter[0].to_string(interested) if interested(self.parameter[0]) else '...'
|
|
57
57
|
right = self.parameter[1].to_string(interested) if interested(self.parameter[1]) else '...'
|
|
58
58
|
if self.parameter[1].meta.newline is not None:
|
|
59
|
-
|
|
60
|
-
rarrow = '\u2794'
|
|
61
|
-
new = '\u2570' #'\u2936'
|
|
59
|
+
new = '\u2570' # '\u2936'
|
|
62
60
|
return f"{left}\n{new} \"{self.parameter[1].meta.newline}\" {self.name} {right}"
|
|
63
61
|
return f"{left} {self.name} {right}"
|
|
64
62
|
elif self.fixity == 'prefix':
|
|
@@ -80,10 +78,26 @@ class Description:
|
|
|
80
78
|
|
|
81
79
|
@dataclass(frozen=True)
|
|
82
80
|
class Syntax(Generic[A, S]):
|
|
81
|
+
"""
|
|
82
|
+
The core signature of Syntax is take an Algebra Class and return an Algebra Instance.
|
|
83
|
+
"""
|
|
83
84
|
alg: Callable[[Type[Algebra[Any, Any]]], Algebra[A, S]]
|
|
84
85
|
meta: Description = field(default_factory=Description, repr=False)
|
|
85
86
|
|
|
86
|
-
def algebra(self, name: str | MethodType | FunctionType, *args: Any, **kwargs: Any)-> Syntax[A, S]:
|
|
87
|
+
def algebra(self, name: str | MethodType | FunctionType, *args: Any, **kwargs: Any) -> Syntax[A, S]:
|
|
88
|
+
"""Calling method of underlying algebra.
|
|
89
|
+
|
|
90
|
+
Allows calling Algebra instance methods (e.g., cut) by name, if the method
|
|
91
|
+
is not exposed by Syntax.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
name: Method name (string), bound method, or function to invoke.
|
|
95
|
+
*args: Positional arguments passed to the method.
|
|
96
|
+
**kwargs: Keyword arguments passed to the method.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
A new Syntax reflecting the transformed algebra.
|
|
100
|
+
"""
|
|
87
101
|
def algebra_run(cls: Type[Algebra[Any, S]]) -> Algebra[Any, S]:
|
|
88
102
|
a = self.alg(cls)
|
|
89
103
|
if isinstance(name, str):
|
|
@@ -91,10 +105,8 @@ class Syntax(Generic[A, S]):
|
|
|
91
105
|
if attr is None:
|
|
92
106
|
return a
|
|
93
107
|
if isinstance(attr, (staticmethod, classmethod)):
|
|
94
|
-
# These are descriptors: unwrap then call
|
|
95
108
|
attr = attr.__get__(None, cls)
|
|
96
109
|
elif isinstance(attr, FunctionType):
|
|
97
|
-
# Unbound function (e.g., static method not wrapped)
|
|
98
110
|
attr = MethodType(attr, a)
|
|
99
111
|
else:
|
|
100
112
|
return a
|
|
@@ -108,142 +120,316 @@ class Syntax(Generic[A, S]):
|
|
|
108
120
|
else:
|
|
109
121
|
return a
|
|
110
122
|
return self.__class__(alg=algebra_run, meta=self.meta)
|
|
111
|
-
|
|
112
123
|
|
|
113
|
-
def as_(self, typ: Type[B])->B:
|
|
114
|
-
return cast(typ, self)
|
|
115
|
-
|
|
124
|
+
def as_(self, typ: Type[B]) -> B:
|
|
125
|
+
return cast(typ, self) # type: ignore
|
|
126
|
+
|
|
116
127
|
def __call__(self, alg: Type[Algebra[Any, Any]]) -> Algebra[A, S]:
|
|
117
128
|
return self.alg(alg)
|
|
118
|
-
|
|
129
|
+
|
|
119
130
|
def to_string(self, interested: Callable[[Any], bool]) -> Optional[str]:
|
|
120
131
|
return self.meta.to_string(interested)
|
|
121
132
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return self.describe(newline=info)
|
|
133
|
+
def describe(
|
|
134
|
+
self,
|
|
135
|
+
*,
|
|
136
|
+
newline: Optional[str] = None,
|
|
137
|
+
name: Optional[str] = None,
|
|
138
|
+
fixity: Optional[Literal['infix', 'prefix', 'postfix']] = None,
|
|
139
|
+
parameter: Optional[Tuple[Syntax[Any, S], ...]] = None,
|
|
140
|
+
) -> Syntax[A, S]:
|
|
141
|
+
return self.__class__(
|
|
142
|
+
alg=self.alg,
|
|
143
|
+
meta=self.meta.update(
|
|
144
|
+
name=name, newline=newline, fixity=fixity, parameter=parameter
|
|
145
|
+
),
|
|
146
|
+
)
|
|
137
147
|
|
|
138
|
-
def
|
|
139
|
-
return self.describe(
|
|
148
|
+
def newline(self, info: str = '') -> Syntax[A, S]:
|
|
149
|
+
return self.describe(newline=info)
|
|
140
150
|
|
|
141
|
-
######################################################## value transformation ########################################################
|
|
151
|
+
######################################################## value transformation ########################################################
|
|
142
152
|
def map(self, f: Callable[[A], B]) -> Syntax[B, S]:
|
|
143
|
-
|
|
144
|
-
|
|
153
|
+
"""Map the produced value while preserving state and metadata.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
f: Function mapping value A to B.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Syntax yielding B with the same resulting state.
|
|
160
|
+
"""
|
|
161
|
+
return self.__class__(lambda cls: self.alg(cls).map(f), meta=self.meta) # type: ignore
|
|
162
|
+
|
|
145
163
|
def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Syntax[B, S]:
|
|
146
|
-
|
|
164
|
+
"""Bidirectionally map values with an inverse, keeping round-trip info.
|
|
165
|
+
|
|
166
|
+
Applies f to the value and adjusts internal state via inverse i so
|
|
167
|
+
generation/parsing stay in sync.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
f: Forward mapping A -> B.
|
|
171
|
+
i: Inverse mapping B -> A applied to the state.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Syntax yielding B with state alignment preserved.
|
|
175
|
+
"""
|
|
176
|
+
return self.__class__(lambda cls: self.alg(cls).bimap(f, i), meta=self.meta) # type: ignore
|
|
147
177
|
|
|
148
178
|
def map_all(self, f: Callable[[A, S], Tuple[B, S]]) -> Syntax[B, S]:
|
|
149
|
-
|
|
150
|
-
|
|
179
|
+
"""Map both value and state on success.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
f: Function mapping (value, state) to (new_value, new_state).
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Syntax yielding transformed value and state.
|
|
186
|
+
"""
|
|
187
|
+
return self.__class__(lambda cls: self.alg(cls).map_all(f), meta=self.meta) # type: ignore
|
|
188
|
+
|
|
151
189
|
def map_error(self, f: Callable[[Optional[Any]], Any]) -> Syntax[A, S]:
|
|
190
|
+
"""Transform the error payload when this syntax fails.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
f: Function applied to the error payload of Left.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Syntax that preserves successes and maps failures.
|
|
197
|
+
"""
|
|
152
198
|
return self.__class__(lambda cls: self.alg(cls).map_error(f), meta=self.meta)
|
|
153
|
-
|
|
154
|
-
def
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
199
|
+
|
|
200
|
+
def map_state(self, f: Callable[[S], S]) -> Syntax[A, S]:
|
|
201
|
+
"""Map the input state before running this syntax.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
f: S -> S function applied to the state prior to running.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Syntax that runs with f(state).
|
|
208
|
+
"""
|
|
209
|
+
return self.__class__(lambda cls: self.alg(cls).map_state(f), meta=self.meta)
|
|
159
210
|
|
|
160
211
|
def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
|
|
161
|
-
|
|
212
|
+
"""Chain computations where the next step depends on the value.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
f: Function mapping value to the next algebra to run.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Syntax yielding the result of the chained computation.
|
|
219
|
+
"""
|
|
220
|
+
return self.__class__(lambda cls: self.alg(cls).flat_map(f)) # type: ignore
|
|
162
221
|
|
|
163
222
|
def many(self, *, at_least: int = 1, at_most: Optional[int] = None) -> Syntax[Many[A], S]:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
################################################ facility combinators ############################################################
|
|
223
|
+
"""Repeat this syntax and collect results into Many.
|
|
224
|
+
|
|
225
|
+
Repeats greedily until failure or no progress. Enforces bounds.
|
|
169
226
|
|
|
227
|
+
Args:
|
|
228
|
+
at_least: Minimum number of matches (default 1).
|
|
229
|
+
at_most: Optional maximum number of matches.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Syntax producing Many of values.
|
|
233
|
+
"""
|
|
234
|
+
return self.__class__(
|
|
235
|
+
lambda cls: self.alg(cls).many(at_least=at_least, at_most=at_most) # type: ignore
|
|
236
|
+
).describe(
|
|
237
|
+
name='*', fixity='prefix', parameter=(self,)
|
|
238
|
+
) # type: ignore
|
|
239
|
+
|
|
240
|
+
############################################################### facility combinators ############################################################
|
|
170
241
|
def between(self, left: Syntax[B, S], right: Syntax[C, S]) -> Syntax[Then[B, Then[A, C]], S]:
|
|
242
|
+
"""Parse left, then this syntax, then right; keep all.
|
|
243
|
+
|
|
244
|
+
Equivalent to left >> self // right.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
left: Opening syntax.
|
|
248
|
+
right: Closing syntax.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Syntax producing nested Then with all parts.
|
|
252
|
+
"""
|
|
171
253
|
return left >> self // right
|
|
172
254
|
|
|
173
|
-
def sep_by(self,
|
|
174
|
-
|
|
175
|
-
|
|
255
|
+
def sep_by(self, sep: Syntax[B, S]) -> Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S]:
|
|
256
|
+
"""Parse one or more items separated by sep.
|
|
257
|
+
|
|
258
|
+
Returns a structure where the first item is separated from the rest,
|
|
259
|
+
which are collected in a Many of Then pairs.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
sep: Separator syntax between items.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Syntax describing a non-empty, separator-delimited list.
|
|
266
|
+
"""
|
|
267
|
+
ret: Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S] = (
|
|
268
|
+
self + (sep >> self).many().optional()
|
|
269
|
+
)
|
|
270
|
+
|
|
176
271
|
def f(a: Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]]) -> Many[A]:
|
|
177
272
|
match a:
|
|
178
|
-
case Then(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
273
|
+
case Then(
|
|
274
|
+
kind=ThenKind.BOTH,
|
|
275
|
+
left=left,
|
|
276
|
+
right=Choice(kind=ChoiceKind.RIGHT, value=Nothing()),
|
|
277
|
+
):
|
|
278
|
+
return Many(value=(left,))
|
|
279
|
+
case Then(
|
|
280
|
+
kind=ThenKind.BOTH,
|
|
281
|
+
left=left,
|
|
282
|
+
right=Choice(kind=ChoiceKind.LEFT, value=Many(value=bs)),
|
|
283
|
+
):
|
|
284
|
+
return Many(value=(left,) + tuple([b.right for b in bs]))
|
|
182
285
|
case _:
|
|
183
286
|
raise ValueError(f"Bad data shape {a}")
|
|
184
|
-
|
|
185
|
-
def i(a: Many[A]) -> Then[A, Choice[Many[Then[B|None, A]], Optional[Nothing]]]:
|
|
287
|
+
|
|
288
|
+
def i(a: Many[A]) -> Then[A, Choice[Many[Then[B | None, A]], Optional[Nothing]]]:
|
|
186
289
|
if not isinstance(a, Many) or len(a.value) < 1:
|
|
187
|
-
raise ValueError(
|
|
290
|
+
raise ValueError(
|
|
291
|
+
f"sep_by inverse expect Many with at least one element, got {a}"
|
|
292
|
+
)
|
|
188
293
|
if len(a.value) == 1:
|
|
189
|
-
return Then(
|
|
294
|
+
return Then(
|
|
295
|
+
kind=ThenKind.BOTH,
|
|
296
|
+
left=a.value[0],
|
|
297
|
+
right=Choice(kind=ChoiceKind.RIGHT, value=Nothing()),
|
|
298
|
+
)
|
|
190
299
|
else:
|
|
191
|
-
v: List[Then[B|None, A]] = [
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
300
|
+
v: List[Then[B | None, A]] = [
|
|
301
|
+
Then(kind=ThenKind.RIGHT, right=x, left=None) for x in a.value[1:]
|
|
302
|
+
]
|
|
303
|
+
return Then(
|
|
304
|
+
kind=ThenKind.BOTH,
|
|
305
|
+
left=a.value[0],
|
|
306
|
+
right=Choice(kind=ChoiceKind.LEFT, value=Many(value=tuple(v))),
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
ret = ret.bimap(f, i) # type: ignore
|
|
310
|
+
return ret.describe(name='sep_by', fixity='prefix', parameter=(self, sep))
|
|
311
|
+
|
|
312
|
+
def parens(
|
|
313
|
+
self,
|
|
314
|
+
sep: Syntax[C, S],
|
|
315
|
+
open: Syntax[B, S],
|
|
316
|
+
close: Syntax[D, S],
|
|
317
|
+
) -> Syntax[Then[B, Then[Then[A, Choice[Many[Then[C, A]], Optional[Nothing]]], D]], S]:
|
|
318
|
+
"""Parse a parenthesized, separator-delimited list.
|
|
319
|
+
|
|
320
|
+
Shorthand for self.sep_by(sep).between(open, close).
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
sep: Separator between elements.
|
|
324
|
+
open: Opening delimiter.
|
|
325
|
+
close: Closing delimiter.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Syntax producing all three parts with the list nested inside.
|
|
329
|
+
"""
|
|
206
330
|
return self.sep_by(sep=sep).between(left=open, right=close)
|
|
207
|
-
|
|
331
|
+
|
|
208
332
|
def optional(self) -> Syntax[Choice[A, Optional[Nothing]], S]:
|
|
209
|
-
|
|
333
|
+
"""Make this syntax optional.
|
|
334
|
+
|
|
335
|
+
Returns a Choice of the value or Nothing when absent.
|
|
210
336
|
|
|
337
|
+
Returns:
|
|
338
|
+
Syntax producing Choice of value or Nothing.
|
|
339
|
+
"""
|
|
340
|
+
return (self | success(Nothing())).describe(
|
|
341
|
+
name='~', fixity='prefix', parameter=(self,)
|
|
342
|
+
)
|
|
211
343
|
|
|
212
344
|
def cut(self) -> Syntax[A, S]:
|
|
213
|
-
|
|
345
|
+
"""Commit this branch: on failure, prevent trying alternatives.
|
|
214
346
|
|
|
347
|
+
Wraps the underlying algebra's cut.
|
|
215
348
|
|
|
216
|
-
|
|
349
|
+
Returns:
|
|
350
|
+
Syntax that marks downstream failures as committed.
|
|
351
|
+
"""
|
|
352
|
+
return self.__class__(lambda cls: self.alg(cls).cut())
|
|
353
|
+
|
|
354
|
+
###################################################### operator overloading #############################################
|
|
217
355
|
def __floordiv__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
356
|
+
"""Then-left: run both and prefer the left in the result kind.
|
|
357
|
+
|
|
358
|
+
Returns Then(kind=LEFT) with both left and right values.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
other: Syntax to run after this one.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
Syntax producing Then(left, right, kind=LEFT).
|
|
365
|
+
"""
|
|
218
366
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
219
|
-
ret: Syntax[Then[A, B], S] = self.__class__(
|
|
220
|
-
|
|
221
|
-
|
|
367
|
+
ret: Syntax[Then[A, B], S] = self.__class__(
|
|
368
|
+
lambda cls: self.alg(cls).then_left(other.alg(cls)) # type: ignore
|
|
369
|
+
) # type: ignore
|
|
370
|
+
return ret.describe(name=ThenKind.LEFT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
371
|
+
|
|
222
372
|
def __rfloordiv__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
223
373
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
224
374
|
return other.__floordiv__(self)
|
|
225
|
-
|
|
375
|
+
|
|
226
376
|
def __add__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
377
|
+
"""Then-both: run both and keep both values.
|
|
378
|
+
|
|
379
|
+
Returns Then(kind=BOTH).
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
other: Syntax to run after this one.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
Syntax producing Then(left, right, kind=BOTH).
|
|
386
|
+
"""
|
|
227
387
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
228
|
-
ret: Syntax[Then[A, B], S] = self.__class__(
|
|
229
|
-
|
|
388
|
+
ret: Syntax[Then[A, B], S] = self.__class__(
|
|
389
|
+
lambda cls: self.alg(cls).then_both(other.alg(cls)) # type: ignore
|
|
390
|
+
) # type: ignore
|
|
391
|
+
return ret.describe(name=ThenKind.BOTH.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
230
392
|
|
|
231
393
|
def __radd__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
232
394
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
233
395
|
return other.__add__(self)
|
|
234
396
|
|
|
235
397
|
def __rshift__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
398
|
+
"""Then-right: run both and prefer the right in the result kind.
|
|
399
|
+
|
|
400
|
+
Returns Then(kind=RIGHT).
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
other: Syntax to run after this one.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Syntax producing Then(left, right, kind=RIGHT).
|
|
407
|
+
"""
|
|
236
408
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
237
|
-
ret: Syntax[Then[A, B], S] = self.__class__(
|
|
238
|
-
|
|
409
|
+
ret: Syntax[Then[A, B], S] = self.__class__(
|
|
410
|
+
lambda cls: self.alg(cls).then_right(other.alg(cls)) # type: ignore
|
|
411
|
+
) # type: ignore
|
|
412
|
+
return ret.describe(name=ThenKind.RIGHT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
239
413
|
|
|
240
414
|
def __rrshift__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
241
415
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
242
|
-
return other.__rshift__(self)
|
|
243
|
-
|
|
416
|
+
return other.__rshift__(self)
|
|
417
|
+
|
|
244
418
|
def __or__(self, other: Syntax[B, S]) -> Syntax[Choice[A, B], S]:
|
|
419
|
+
"""Alternative: try this syntax; if it fails uncommitted, try the other.
|
|
420
|
+
|
|
421
|
+
Returns a Choice indicating which branch succeeded.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
other: Alternative syntax to try on failure.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Syntax producing Choice.LEFT or Choice.RIGHT.
|
|
428
|
+
"""
|
|
245
429
|
other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
|
|
246
|
-
ret: Syntax[Choice[A, B], S] = self.__class__(
|
|
430
|
+
ret: Syntax[Choice[A, B], S] = self.__class__(
|
|
431
|
+
lambda cls: self.alg(cls).or_else(other.alg(cls)) # type: ignore
|
|
432
|
+
) # type: ignore
|
|
247
433
|
return ret.describe(name='|', fixity='infix', parameter=(self, other))
|
|
248
434
|
|
|
249
435
|
def __ror__(self, other: Syntax[B, S]) -> Syntax[Choice[B, A], S]:
|
|
@@ -251,59 +437,101 @@ class Syntax(Generic[A, S]):
|
|
|
251
437
|
return other.__or__(self)
|
|
252
438
|
|
|
253
439
|
def __invert__(self) -> Syntax[Choice[A, Optional[Nothing]], S]:
|
|
440
|
+
"""Syntactic sugar for optional() (tilde operator)."""
|
|
254
441
|
return self.optional()
|
|
255
442
|
|
|
256
|
-
|
|
257
|
-
######################################################################## data processing combinators #########################################################
|
|
443
|
+
######################################################################## data processing combinators #########################################################
|
|
258
444
|
def bind(self, name: Optional[str] = None) -> Syntax[A, S]:
|
|
445
|
+
"""Bind the produced value to the name.
|
|
446
|
+
|
|
447
|
+
If name is None and the value is Marked, the name of Marked is used.
|
|
448
|
+
If name is None and the value if Collect, the name of the collector is used.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
name: Optional binding name; must be a valid identifier if provided.
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
Syntax that writes the value into the state's binding table.
|
|
455
|
+
"""
|
|
259
456
|
if name:
|
|
260
457
|
assert valid_name(name), f"Invalid mark name: {name}"
|
|
261
|
-
|
|
458
|
+
|
|
459
|
+
def bind_v(v: Any, s: S) -> Tuple[Any, S]:
|
|
262
460
|
if name:
|
|
263
|
-
return v, s.bind(name, v)
|
|
461
|
+
return v, s.bind(name, v)
|
|
264
462
|
elif isinstance(v, Marked):
|
|
265
463
|
return v.value, s.bind(v.name, v.value)
|
|
464
|
+
elif isinstance(v, Collect) and isinstance(v.collector, type):
|
|
465
|
+
return v.value, s.bind(v.collector.__name__, v.value)
|
|
266
466
|
else:
|
|
267
467
|
return v, s
|
|
468
|
+
|
|
268
469
|
return self.map_all(bind_v).describe(name=f'bind({name})', fixity='postfix', parameter=(self,))
|
|
269
470
|
|
|
270
|
-
def to(self, f: Collector[E])-> Syntax[Collect[A, E], S]:
|
|
471
|
+
def to(self, f: Collector[E]) -> Syntax[Collect[A, E], S]:
|
|
472
|
+
"""Attach a collector to the produced value.
|
|
473
|
+
A collector can be a dataclass, and the Marked nodes will be
|
|
474
|
+
mapped to the fields of the dataclass.
|
|
475
|
+
|
|
476
|
+
Wraps the value in Collect or updates an existing one.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
f: Collector invoked during generation/printing.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
Syntax producing Collect(value, collector=f).
|
|
483
|
+
"""
|
|
271
484
|
def to_f(v: A) -> Collect[A, E]:
|
|
272
485
|
if isinstance(v, Collect):
|
|
273
486
|
return replace(v, collector=f)
|
|
274
487
|
else:
|
|
275
488
|
return Collect(collector=f, value=v)
|
|
489
|
+
|
|
276
490
|
def ito_f(c: Collect[A, E]) -> A:
|
|
277
491
|
return c.value if isinstance(c, Collect) else c
|
|
278
|
-
return self.bimap(to_f, ito_f).describe(name=f'to({f})', fixity='postfix', parameter=(self,))
|
|
279
492
|
|
|
493
|
+
return self.bimap(to_f, ito_f).describe(name=f'to({f})', fixity='postfix', parameter=(self,))
|
|
280
494
|
|
|
281
495
|
def mark(self, name: str) -> Syntax[Marked[A], S]:
|
|
496
|
+
"""Mark the produced value with a name.
|
|
497
|
+
|
|
498
|
+
Useful for later bind operations.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
name: Identifier to attach to the value.
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
Syntax producing Marked(name, value).
|
|
505
|
+
"""
|
|
282
506
|
assert valid_name(name), f"Invalid mark name: {name}"
|
|
507
|
+
|
|
283
508
|
def mark_s(value: A) -> Marked[A]:
|
|
284
509
|
if isinstance(value, Marked):
|
|
285
|
-
return replace(value, name=name)
|
|
510
|
+
return replace(value, name=name)
|
|
286
511
|
else:
|
|
287
512
|
return Marked(name=name, value=value)
|
|
288
|
-
def imark_s(m : Marked[A]) -> A:
|
|
289
|
-
return m.value if isinstance(m, Marked) else m
|
|
290
|
-
|
|
291
|
-
return self.bimap(mark_s, imark_s).describe(name=f'mark("{name}")', fixity='postfix', parameter=(self,))
|
|
292
513
|
|
|
514
|
+
def imark_s(m: Marked[A]) -> A:
|
|
515
|
+
return m.value if isinstance(m, Marked) else m
|
|
293
516
|
|
|
517
|
+
return self.bimap(mark_s, imark_s).describe(name=f'mark("{name}")', fixity='postfix', parameter=(self,))
|
|
294
518
|
|
|
295
519
|
def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> Syntax[A, S]:
|
|
296
|
-
def dump_error_run(err: Any)->Any:
|
|
520
|
+
def dump_error_run(err: Any) -> Any:
|
|
297
521
|
if isinstance(err, Error) and formatter is not None:
|
|
298
|
-
formatter(err)
|
|
522
|
+
formatter(err)
|
|
299
523
|
return err
|
|
300
|
-
return self.__class__(lambda cls: self.alg(cls).map_error(dump_error_run))
|
|
301
524
|
|
|
525
|
+
return self.__class__(lambda cls: self.alg(cls).map_error(dump_error_run))
|
|
302
526
|
|
|
303
|
-
def debug(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
527
|
+
def debug(
|
|
528
|
+
self,
|
|
529
|
+
label: str,
|
|
530
|
+
formatter: Optional[
|
|
531
|
+
Callable[[Algebra[Any, S], S, Either[Any, Tuple[Any, S]]], None]
|
|
532
|
+
] = None,
|
|
533
|
+
) -> Syntax[A, S]:
|
|
534
|
+
return self.__class__(lambda cls: self.alg(cls).debug(label, formatter), meta=self.meta)
|
|
307
535
|
|
|
308
536
|
|
|
309
537
|
|
|
@@ -317,9 +545,15 @@ def success(value: Any) -> Syntax[Any, Any]:
|
|
|
317
545
|
return Syntax(lambda alg: alg.success(value)).describe(name=f'success({value})', fixity='prefix')
|
|
318
546
|
|
|
319
547
|
def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
548
|
+
"""
|
|
549
|
+
A shorthand for writing a chain of syntax combined with '|'.
|
|
550
|
+
"""
|
|
320
551
|
return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(Nothing())
|
|
321
552
|
|
|
322
553
|
def run(syntax: Syntax[A, S], alg: Type[Algebra[A, S]], use_cache:bool, *args: Any, **kwargs: Any) -> Tuple[Any, FrozenDict[str, Tuple[Any, ...]]] | Tuple[Any, None]:
|
|
554
|
+
"""
|
|
555
|
+
Run the syntax over the given algebra, and return the result and bind.
|
|
556
|
+
"""
|
|
323
557
|
parser = syntax(alg)
|
|
324
558
|
input: Optional[S] = alg.state(*args, **kwargs)
|
|
325
559
|
if input:
|