syncraft 0.2.5__py3-none-any.whl → 0.2.7__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 +30 -9
- syncraft/algebra.py +143 -214
- syncraft/ast.py +62 -7
- syncraft/cache.py +113 -0
- syncraft/constraint.py +184 -134
- syncraft/dev.py +9 -0
- syncraft/finder.py +17 -12
- syncraft/generator.py +80 -78
- syncraft/lexer.py +131 -0
- syncraft/parser.py +75 -224
- syncraft/syntax.py +187 -100
- syncraft/utils.py +214 -0
- syncraft/walker.py +147 -0
- syncraft-0.2.7.dist-info/METADATA +56 -0
- syncraft-0.2.7.dist-info/RECORD +20 -0
- syncraft/diagnostic.py +0 -70
- syncraft-0.2.5.dist-info/METADATA +0 -113
- syncraft-0.2.5.dist-info/RECORD +0 -16
- {syncraft-0.2.5.dist-info → syncraft-0.2.7.dist-info}/WHEEL +0 -0
- {syncraft-0.2.5.dist-info → syncraft-0.2.7.dist-info}/licenses/LICENSE +0 -0
- {syncraft-0.2.5.dist-info → syncraft-0.2.7.dist-info}/top_level.txt +0 -0
syncraft/syntax.py
CHANGED
|
@@ -6,13 +6,15 @@ 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,
|
|
10
|
-
from syncraft.
|
|
11
|
-
from syncraft.
|
|
9
|
+
from syncraft.algebra import Algebra, Error, Right, Left, Incomplete
|
|
10
|
+
from syncraft.cache import Cache
|
|
11
|
+
from syncraft.constraint import Bindable
|
|
12
|
+
from syncraft.ast import Then, ThenKind, Marked, Choice, Many, ChoiceKind, Nothing, Collect, E, Collector, SyncraftError
|
|
12
13
|
from types import MethodType, FunctionType
|
|
13
14
|
import keyword
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
import re
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from rich import print
|
|
16
18
|
|
|
17
19
|
def valid_name(name: str) -> bool:
|
|
18
20
|
return (name.isidentifier()
|
|
@@ -32,56 +34,27 @@ S = TypeVar('S', bound=Bindable) # State type
|
|
|
32
34
|
@dataclass(frozen=True)
|
|
33
35
|
class Description:
|
|
34
36
|
name: Optional[str] = None
|
|
35
|
-
newline: Optional[str] = None
|
|
36
37
|
fixity: Literal['infix', 'prefix', 'postfix'] = 'infix'
|
|
37
38
|
parameter: Tuple[Any, ...] = field(default_factory=tuple)
|
|
38
39
|
|
|
39
40
|
def update(self,
|
|
40
41
|
*,
|
|
41
|
-
newline: Optional[str] = None,
|
|
42
42
|
name: Optional[str] = None,
|
|
43
43
|
fixity: Optional[Literal['infix', 'prefix', 'postfix']] = None,
|
|
44
44
|
parameter: Optional[Tuple[Any, ...]] = None) -> 'Description':
|
|
45
45
|
return Description(
|
|
46
46
|
name=name if name is not None else self.name,
|
|
47
|
-
newline= newline if newline is not None else self.newline,
|
|
48
47
|
fixity=fixity if fixity is not None else self.fixity,
|
|
49
48
|
parameter=parameter if parameter is not None else self.parameter
|
|
50
49
|
)
|
|
51
50
|
|
|
52
|
-
def to_string(self, interested: Callable[[Any], bool]) -> Optional[str]:
|
|
53
|
-
if self.name is not None:
|
|
54
|
-
if self.fixity == 'infix':
|
|
55
|
-
assert len(self.parameter) == 2, "Expected exactly two parameters for infix operator"
|
|
56
|
-
left = self.parameter[0].to_string(interested) if interested(self.parameter[0]) else '...'
|
|
57
|
-
right = self.parameter[1].to_string(interested) if interested(self.parameter[1]) else '...'
|
|
58
|
-
if self.parameter[1].meta.newline is not None:
|
|
59
|
-
new = '\u2570' # '\u2936'
|
|
60
|
-
return f"{left}\n{new} \"{self.parameter[1].meta.newline}\" {self.name} {right}"
|
|
61
|
-
return f"{left} {self.name} {right}"
|
|
62
|
-
elif self.fixity == 'prefix':
|
|
63
|
-
if len(self.parameter) == 0:
|
|
64
|
-
return self.name
|
|
65
|
-
tmp = [x.to_string(interested) if interested(x) else '...' for x in self.parameter]
|
|
66
|
-
return f"{self.name}({','.join(str(x) for x in tmp)})"
|
|
67
|
-
elif self.fixity == 'postfix':
|
|
68
|
-
if len(self.parameter) == 0:
|
|
69
|
-
return self.name
|
|
70
|
-
tmp = [x.to_string(interested) if interested(x) else '...' for x in self.parameter]
|
|
71
|
-
return f"({','.join(str(x) for x in tmp)}).{self.name}"
|
|
72
|
-
else:
|
|
73
|
-
return f"Invalid fixity: {self.fixity}"
|
|
74
|
-
return None
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
51
|
|
|
79
52
|
@dataclass(frozen=True)
|
|
80
53
|
class Syntax(Generic[A, S]):
|
|
81
54
|
"""
|
|
82
55
|
The core signature of Syntax is take an Algebra Class and return an Algebra Instance.
|
|
83
56
|
"""
|
|
84
|
-
alg: Callable[[Type[Algebra[Any, Any]]], Algebra[A, S]]
|
|
57
|
+
alg: Callable[[Type[Algebra[Any, Any]], Cache[Any, Any]], Algebra[A, S]]
|
|
85
58
|
meta: Description = field(default_factory=Description, repr=False)
|
|
86
59
|
|
|
87
60
|
def algebra(self, name: str | MethodType | FunctionType, *args: Any, **kwargs: Any) -> Syntax[A, S]:
|
|
@@ -98,8 +71,8 @@ class Syntax(Generic[A, S]):
|
|
|
98
71
|
Returns:
|
|
99
72
|
A new Syntax reflecting the transformed algebra.
|
|
100
73
|
"""
|
|
101
|
-
def algebra_run(cls: Type[Algebra[Any, S]]) -> Algebra[Any, S]:
|
|
102
|
-
a = self
|
|
74
|
+
def algebra_run(cls: Type[Algebra[Any, S]], cache: Cache[Any, Any]) -> Algebra[Any, S]:
|
|
75
|
+
a = self(cls, cache)
|
|
103
76
|
if isinstance(name, str):
|
|
104
77
|
attr = getattr(a, name, None) or getattr(cls, name, None)
|
|
105
78
|
if attr is None:
|
|
@@ -124,16 +97,14 @@ class Syntax(Generic[A, S]):
|
|
|
124
97
|
def as_(self, typ: Type[B]) -> B:
|
|
125
98
|
return cast(typ, self) # type: ignore
|
|
126
99
|
|
|
127
|
-
def __call__(self, alg: Type[Algebra[Any, Any]]) -> Algebra[A, S]:
|
|
128
|
-
return self.alg(alg)
|
|
100
|
+
def __call__(self, alg: Type[Algebra[Any, Any]], cache: Cache[Any, Any]) -> Algebra[A, S]:
|
|
101
|
+
return self.alg(alg, cache)
|
|
102
|
+
|
|
129
103
|
|
|
130
|
-
def to_string(self, interested: Callable[[Any], bool]) -> Optional[str]:
|
|
131
|
-
return self.meta.to_string(interested)
|
|
132
104
|
|
|
133
105
|
def describe(
|
|
134
106
|
self,
|
|
135
107
|
*,
|
|
136
|
-
newline: Optional[str] = None,
|
|
137
108
|
name: Optional[str] = None,
|
|
138
109
|
fixity: Optional[Literal['infix', 'prefix', 'postfix']] = None,
|
|
139
110
|
parameter: Optional[Tuple[Syntax[Any, S], ...]] = None,
|
|
@@ -141,12 +112,10 @@ class Syntax(Generic[A, S]):
|
|
|
141
112
|
return self.__class__(
|
|
142
113
|
alg=self.alg,
|
|
143
114
|
meta=self.meta.update(
|
|
144
|
-
name=name,
|
|
115
|
+
name=name, fixity=fixity, parameter=parameter
|
|
145
116
|
),
|
|
146
117
|
)
|
|
147
118
|
|
|
148
|
-
def newline(self, info: str = '') -> Syntax[A, S]:
|
|
149
|
-
return self.describe(newline=info)
|
|
150
119
|
|
|
151
120
|
######################################################## value transformation ########################################################
|
|
152
121
|
def map(self, f: Callable[[A], B]) -> Syntax[B, S]:
|
|
@@ -158,7 +127,7 @@ class Syntax(Generic[A, S]):
|
|
|
158
127
|
Returns:
|
|
159
128
|
Syntax yielding B with the same resulting state.
|
|
160
129
|
"""
|
|
161
|
-
return self.__class__(lambda cls: self
|
|
130
|
+
return self.__class__(lambda cls, cache: self(cls, cache).map(f), meta=self.meta) # type: ignore
|
|
162
131
|
|
|
163
132
|
def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Syntax[B, S]:
|
|
164
133
|
"""Bidirectionally map values with an inverse, keeping round-trip info.
|
|
@@ -173,7 +142,7 @@ class Syntax(Generic[A, S]):
|
|
|
173
142
|
Returns:
|
|
174
143
|
Syntax yielding B with state alignment preserved.
|
|
175
144
|
"""
|
|
176
|
-
return self.__class__(lambda cls: self
|
|
145
|
+
return self.__class__(lambda cls, cache: self(cls, cache).bimap(f, i), meta=self.meta) # type: ignore
|
|
177
146
|
|
|
178
147
|
def map_all(self, f: Callable[[A, S], Tuple[B, S]]) -> Syntax[B, S]:
|
|
179
148
|
"""Map both value and state on success.
|
|
@@ -184,7 +153,7 @@ class Syntax(Generic[A, S]):
|
|
|
184
153
|
Returns:
|
|
185
154
|
Syntax yielding transformed value and state.
|
|
186
155
|
"""
|
|
187
|
-
return self.__class__(lambda cls: self
|
|
156
|
+
return self.__class__(lambda cls, cache: self(cls, cache).map_all(f), meta=self.meta) # type: ignore
|
|
188
157
|
|
|
189
158
|
def map_error(self, f: Callable[[Optional[Any]], Any]) -> Syntax[A, S]:
|
|
190
159
|
"""Transform the error payload when this syntax fails.
|
|
@@ -195,7 +164,7 @@ class Syntax(Generic[A, S]):
|
|
|
195
164
|
Returns:
|
|
196
165
|
Syntax that preserves successes and maps failures.
|
|
197
166
|
"""
|
|
198
|
-
return self.__class__(lambda cls: self
|
|
167
|
+
return self.__class__(lambda cls, cache: self(cls, cache).map_error(f), meta=self.meta)
|
|
199
168
|
|
|
200
169
|
def map_state(self, f: Callable[[S], S]) -> Syntax[A, S]:
|
|
201
170
|
"""Map the input state before running this syntax.
|
|
@@ -206,7 +175,7 @@ class Syntax(Generic[A, S]):
|
|
|
206
175
|
Returns:
|
|
207
176
|
Syntax that runs with f(state).
|
|
208
177
|
"""
|
|
209
|
-
return self.__class__(lambda cls: self
|
|
178
|
+
return self.__class__(lambda cls, cache: self(cls, cache).map_state(f), meta=self.meta)
|
|
210
179
|
|
|
211
180
|
def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
|
|
212
181
|
"""Chain computations where the next step depends on the value.
|
|
@@ -217,7 +186,7 @@ class Syntax(Generic[A, S]):
|
|
|
217
186
|
Returns:
|
|
218
187
|
Syntax yielding the result of the chained computation.
|
|
219
188
|
"""
|
|
220
|
-
return self.__class__(lambda cls: self
|
|
189
|
+
return self.__class__(lambda cls, cache: self(cls, cache).flat_map(f)) # type: ignore
|
|
221
190
|
|
|
222
191
|
def many(self, *, at_least: int = 1, at_most: Optional[int] = None) -> Syntax[Many[A], S]:
|
|
223
192
|
"""Repeat this syntax and collect results into Many.
|
|
@@ -232,7 +201,7 @@ class Syntax(Generic[A, S]):
|
|
|
232
201
|
Syntax producing Many of values.
|
|
233
202
|
"""
|
|
234
203
|
return self.__class__(
|
|
235
|
-
lambda cls: self
|
|
204
|
+
lambda cls, cache: self(cls, cache).many(at_least=at_least, at_most=at_most) # type: ignore
|
|
236
205
|
).describe(
|
|
237
206
|
name='*', fixity='prefix', parameter=(self,)
|
|
238
207
|
) # type: ignore
|
|
@@ -283,13 +252,11 @@ class Syntax(Generic[A, S]):
|
|
|
283
252
|
):
|
|
284
253
|
return Many(value=(left,) + tuple([b.right for b in bs]))
|
|
285
254
|
case _:
|
|
286
|
-
raise
|
|
255
|
+
raise SyncraftError(f"Bad data shape {a}", offending=a, expect="Then(BOTH) with Choice on the right")
|
|
287
256
|
|
|
288
257
|
def i(a: Many[A]) -> Then[A, Choice[Many[Then[B | None, A]], Optional[Nothing]]]:
|
|
289
258
|
if not isinstance(a, Many) or len(a.value) < 1:
|
|
290
|
-
raise
|
|
291
|
-
f"sep_by inverse expect Many with at least one element, got {a}"
|
|
292
|
-
)
|
|
259
|
+
raise SyncraftError(f"sep_by inverse expect Many with at least one element, got {a}", offending=a, expect="Many with at least one element")
|
|
293
260
|
if len(a.value) == 1:
|
|
294
261
|
return Then(
|
|
295
262
|
kind=ThenKind.BOTH,
|
|
@@ -349,7 +316,7 @@ class Syntax(Generic[A, S]):
|
|
|
349
316
|
Returns:
|
|
350
317
|
Syntax that marks downstream failures as committed.
|
|
351
318
|
"""
|
|
352
|
-
return self.__class__(lambda cls: self
|
|
319
|
+
return self.__class__(lambda cls, cache: self(cls, cache).cut())
|
|
353
320
|
|
|
354
321
|
###################################################### operator overloading #############################################
|
|
355
322
|
def __floordiv__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
@@ -363,14 +330,14 @@ class Syntax(Generic[A, S]):
|
|
|
363
330
|
Returns:
|
|
364
331
|
Syntax producing Then(left, right, kind=LEFT).
|
|
365
332
|
"""
|
|
366
|
-
other = other if isinstance(other, Syntax) else
|
|
333
|
+
other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
|
|
367
334
|
ret: Syntax[Then[A, B], S] = self.__class__(
|
|
368
|
-
lambda cls: self
|
|
335
|
+
lambda cls, cache: self(cls, cache).then_left(other(cls, cache)) # type: ignore
|
|
369
336
|
) # type: ignore
|
|
370
337
|
return ret.describe(name=ThenKind.LEFT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
371
338
|
|
|
372
339
|
def __rfloordiv__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
373
|
-
other = other if isinstance(other, Syntax) else
|
|
340
|
+
other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
|
|
374
341
|
return other.__floordiv__(self)
|
|
375
342
|
|
|
376
343
|
def __add__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
@@ -384,14 +351,14 @@ class Syntax(Generic[A, S]):
|
|
|
384
351
|
Returns:
|
|
385
352
|
Syntax producing Then(left, right, kind=BOTH).
|
|
386
353
|
"""
|
|
387
|
-
other = other if isinstance(other, Syntax) else
|
|
354
|
+
other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
|
|
388
355
|
ret: Syntax[Then[A, B], S] = self.__class__(
|
|
389
|
-
lambda cls: self
|
|
356
|
+
lambda cls, cache: self(cls, cache).then_both(other(cls, cache)) # type: ignore
|
|
390
357
|
) # type: ignore
|
|
391
358
|
return ret.describe(name=ThenKind.BOTH.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
392
359
|
|
|
393
360
|
def __radd__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
394
|
-
other = other if isinstance(other, Syntax) else
|
|
361
|
+
other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
|
|
395
362
|
return other.__add__(self)
|
|
396
363
|
|
|
397
364
|
def __rshift__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
@@ -405,14 +372,14 @@ class Syntax(Generic[A, S]):
|
|
|
405
372
|
Returns:
|
|
406
373
|
Syntax producing Then(left, right, kind=RIGHT).
|
|
407
374
|
"""
|
|
408
|
-
other = other if isinstance(other, Syntax) else
|
|
375
|
+
other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
|
|
409
376
|
ret: Syntax[Then[A, B], S] = self.__class__(
|
|
410
|
-
lambda cls: self
|
|
377
|
+
lambda cls, cache: self(cls, cache).then_right(other(cls, cache)) # type: ignore
|
|
411
378
|
) # type: ignore
|
|
412
379
|
return ret.describe(name=ThenKind.RIGHT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
|
|
413
380
|
|
|
414
381
|
def __rrshift__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
|
|
415
|
-
other = other if isinstance(other, Syntax) else
|
|
382
|
+
other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
|
|
416
383
|
return other.__rshift__(self)
|
|
417
384
|
|
|
418
385
|
def __or__(self, other: Syntax[B, S]) -> Syntax[Choice[A, B], S]:
|
|
@@ -426,14 +393,14 @@ class Syntax(Generic[A, S]):
|
|
|
426
393
|
Returns:
|
|
427
394
|
Syntax producing Choice.LEFT or Choice.RIGHT.
|
|
428
395
|
"""
|
|
429
|
-
other = other if isinstance(other, Syntax) else
|
|
396
|
+
other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
|
|
430
397
|
ret: Syntax[Choice[A, B], S] = self.__class__(
|
|
431
|
-
lambda cls: self
|
|
432
|
-
)
|
|
398
|
+
lambda cls, cache: self(cls, cache).or_else(other(cls, cache)) # type: ignore
|
|
399
|
+
)
|
|
433
400
|
return ret.describe(name='|', fixity='infix', parameter=(self, other))
|
|
434
401
|
|
|
435
402
|
def __ror__(self, other: Syntax[B, S]) -> Syntax[Choice[B, A], S]:
|
|
436
|
-
other = other if isinstance(other, Syntax) else
|
|
403
|
+
other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
|
|
437
404
|
return other.__or__(self)
|
|
438
405
|
|
|
439
406
|
def __invert__(self) -> Syntax[Choice[A, Optional[Nothing]], S]:
|
|
@@ -515,34 +482,46 @@ class Syntax(Generic[A, S]):
|
|
|
515
482
|
return m.value if isinstance(m, Marked) else m
|
|
516
483
|
|
|
517
484
|
return self.bimap(mark_s, imark_s).describe(name=f'mark("{name}")', fixity='postfix', parameter=(self,))
|
|
485
|
+
|
|
486
|
+
def when(f: Callable[[], bool], then: Syntax[A, S], otherwise: Syntax[B, S]) -> Syntax[A | B, S]:
|
|
487
|
+
"""
|
|
488
|
+
Conditionally selects between two syntax branches based on a predicate function.
|
|
518
489
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return err
|
|
524
|
-
|
|
525
|
-
return self.__class__(lambda cls: self.alg(cls).map_error(dump_error_run))
|
|
490
|
+
Args:
|
|
491
|
+
f: A callable returning a boolean to choose the branch.
|
|
492
|
+
then: Syntax to use if f() is True.
|
|
493
|
+
otherwise: Syntax to use if f() is False.
|
|
526
494
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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)
|
|
495
|
+
Returns:
|
|
496
|
+
A Syntax object representing the chosen branch.
|
|
497
|
+
"""
|
|
498
|
+
return lazy(lambda: then if f() else otherwise).describe(name='when(?)', fixity='postfix', parameter=(then, otherwise)) # type: ignore
|
|
535
499
|
|
|
500
|
+
def fail(error: Any) -> Syntax[Any, Any]:
|
|
501
|
+
"""
|
|
502
|
+
Creates a syntax node that always fails with the given error.
|
|
536
503
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return Syntax(lambda cls: cls.lazy(lambda: thunk()(cls))).describe(name='lazy(?)', fixity='postfix')
|
|
504
|
+
Args:
|
|
505
|
+
error: The error to raise or propagate.
|
|
540
506
|
|
|
541
|
-
|
|
542
|
-
|
|
507
|
+
Returns:
|
|
508
|
+
A Syntax object that always fails.
|
|
509
|
+
"""
|
|
510
|
+
return Syntax(lambda alg, cache: alg.fail(error, cache=cache)).describe(name=f'fail({error})', fixity='prefix')
|
|
543
511
|
|
|
544
512
|
def success(value: Any) -> Syntax[Any, Any]:
|
|
545
|
-
|
|
513
|
+
"""
|
|
514
|
+
Creates a syntax node that always succeeds with the given value.
|
|
515
|
+
|
|
516
|
+
Args:
|
|
517
|
+
value: The value to return on success.
|
|
518
|
+
|
|
519
|
+
Returns:
|
|
520
|
+
A Syntax object that always succeeds.
|
|
521
|
+
"""
|
|
522
|
+
return Syntax(lambda alg, cache: alg.success(value, cache=cache)).describe(name=f'success({value})', fixity='prefix')
|
|
523
|
+
|
|
524
|
+
|
|
546
525
|
|
|
547
526
|
def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
548
527
|
"""
|
|
@@ -550,18 +529,126 @@ def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
|
550
529
|
"""
|
|
551
530
|
return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(Nothing())
|
|
552
531
|
|
|
553
|
-
def run(
|
|
532
|
+
def run(*,
|
|
533
|
+
syntax: Syntax[A, S],
|
|
534
|
+
alg: Type[Algebra[A, S]],
|
|
535
|
+
use_cache:bool,
|
|
536
|
+
**kwargs: Any) -> Tuple[Any, None | S]:
|
|
554
537
|
"""
|
|
555
538
|
Run the syntax over the given algebra, and return the result and bind.
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
*args, **kwargs: the arguments passed to alg.state to construct the state object of the algebra.
|
|
556
542
|
"""
|
|
557
|
-
parser = syntax(alg)
|
|
558
|
-
input: Optional[S] = alg.state(
|
|
543
|
+
parser = syntax(alg, Cache())
|
|
544
|
+
input: Optional[S] = alg.state(**kwargs)
|
|
559
545
|
if input:
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
546
|
+
gen = parser.run(input, use_cache=use_cache)
|
|
547
|
+
try:
|
|
548
|
+
result = next(gen)
|
|
549
|
+
while isinstance(result, Incomplete):
|
|
550
|
+
old_input = result.state
|
|
551
|
+
result = gen.send(old_input)
|
|
552
|
+
return Error(this=result, message="Algebra yield data that is not Incomplete"), None
|
|
553
|
+
except StopIteration as e:
|
|
554
|
+
result = e.value
|
|
555
|
+
if isinstance(result, Right):
|
|
556
|
+
return result.value[0], result.value[1]
|
|
557
|
+
elif isinstance(result, Left):
|
|
558
|
+
return result.value, None
|
|
559
|
+
else:
|
|
560
|
+
return Error(this=result, message="Algebra returned data that is not Left or Right"), None
|
|
561
|
+
return Error(this=None, message="Algebra failed to create initial state"), None
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
|
|
565
|
+
syntax: Optional[Syntax[A, S]] = None
|
|
566
|
+
algebra: Optional[Algebra[A, S]] = None
|
|
567
|
+
def syntax_lazy_run(cls: Type[Algebra[Any, S]], cache: Cache) -> Algebra[A, S]:
|
|
568
|
+
nonlocal syntax, algebra
|
|
569
|
+
# print('==' * 20, 'Syntax.lazy.syntax_lazy_run', '==' * 20)
|
|
570
|
+
# print('thunk', thunk, id(thunk))
|
|
571
|
+
# print('syntax', syntax, id(syntax))
|
|
572
|
+
# print('algebra', algebra, id(algebra))
|
|
573
|
+
if syntax is None:
|
|
574
|
+
syntax = thunk()
|
|
575
|
+
def algebra_lazy_f():
|
|
576
|
+
if syntax is None:
|
|
577
|
+
raise SyncraftError("Lazy thunk did not resolve to a Syntax", offending=thunk, expect="a Syntax")
|
|
578
|
+
return syntax(cls, cache)
|
|
579
|
+
if algebra is None:
|
|
580
|
+
algebra = cls.lazy(algebra_lazy_f, cache=cache)
|
|
581
|
+
return algebra
|
|
582
|
+
return Syntax(syntax_lazy_run).describe(name='lazy(?)', fixity='postfix')
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
# def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
|
|
586
|
+
# resolved: Optional[Syntax[A, S]] = None
|
|
587
|
+
|
|
588
|
+
# def run_lazy(cls: Type[Algebra[Any, S]], cache: Cache) -> Algebra[A, S]:
|
|
589
|
+
# nonlocal resolved
|
|
590
|
+
# if resolved is None:
|
|
591
|
+
# resolved = thunk()
|
|
592
|
+
# return resolved(cls, cache) # reuse the same Algebra instance
|
|
593
|
+
# return Syntax(run_lazy)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def token(*,
|
|
599
|
+
text: Optional[str] = None,
|
|
600
|
+
token_type: Optional[Enum] = None,
|
|
601
|
+
case_sensitive: bool = False,
|
|
602
|
+
regex: Optional[re.Pattern[str]] = None
|
|
603
|
+
) -> Syntax[Any, Any]:
|
|
604
|
+
"""Build a ``Syntax`` that matches a single token.
|
|
605
|
+
|
|
606
|
+
Convenience wrapper around ``Parser.token``. You can match by
|
|
607
|
+
type, exact text, or regex.
|
|
608
|
+
|
|
609
|
+
Args:
|
|
610
|
+
token_type: Expected token enum type.
|
|
611
|
+
text: Exact token text to match.
|
|
612
|
+
case_sensitive: Whether text matching respects case.
|
|
613
|
+
regex: Pattern to match token text.
|
|
614
|
+
|
|
615
|
+
Returns:
|
|
616
|
+
Syntax[Any, Any]: A syntax that matches one token.
|
|
617
|
+
"""
|
|
618
|
+
token_type_txt = token_type.name if token_type is not None else None
|
|
619
|
+
token_value_txt = text if text is not None else None
|
|
620
|
+
msg = 'token(' + ','.join([x for x in [token_type_txt, token_value_txt, str(regex)] if x is not None]) + ')'
|
|
621
|
+
return Syntax(
|
|
622
|
+
lambda cls, cache: cls.factory('token', token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex, cache=cache)
|
|
623
|
+
).describe(name=msg, fixity='prefix')
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def literal(lit: str) -> Syntax[Any, Any]:
|
|
628
|
+
"""Match an exact literal string (case-sensitive)."""
|
|
629
|
+
return token(token_type=None, text=lit, case_sensitive=True)
|
|
630
|
+
|
|
631
|
+
def regex(regex: re.Pattern[str] | str) -> Syntax[Any, Any]:
|
|
632
|
+
"""Match a token whose text satisfies the given regular expression."""
|
|
633
|
+
if isinstance(regex, str):
|
|
634
|
+
regex = re.compile(regex)
|
|
635
|
+
return token(token_type=None, regex=regex, case_sensitive=True)
|
|
636
|
+
|
|
637
|
+
def lift(value: Any)-> Syntax[Any, Any]:
|
|
638
|
+
"""Lift a Python value into the nearest matching token syntax.
|
|
639
|
+
|
|
640
|
+
- ``str`` -> ``literal``
|
|
641
|
+
- ``re.Pattern`` -> ``token`` with regex
|
|
642
|
+
- ``Enum`` -> ``token`` with type
|
|
643
|
+
- otherwise -> succeed with the value
|
|
644
|
+
"""
|
|
645
|
+
if isinstance(value, str):
|
|
646
|
+
return literal(value)
|
|
647
|
+
elif isinstance(value, re.Pattern):
|
|
648
|
+
return token(regex=value)
|
|
649
|
+
elif isinstance(value, Enum):
|
|
650
|
+
return token(token_type=value)
|
|
565
651
|
else:
|
|
566
|
-
return
|
|
652
|
+
return Syntax(lambda cls, cache: cls.success(value, cache=cache))
|
|
653
|
+
|
|
567
654
|
|