syncraft 0.2.9__tar.gz → 0.2.10__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.2.9/syncraft.egg-info → syncraft-0.2.10}/PKG-INFO +1 -1
- {syncraft-0.2.9 → syncraft-0.2.10}/pyproject.toml +1 -1
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/algebra.py +11 -4
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/ast.py +45 -7
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/syntax.py +19 -10
- syncraft-0.2.10/syncraft/walker.py +189 -0
- {syncraft-0.2.9 → syncraft-0.2.10/syncraft.egg-info}/PKG-INFO +1 -1
- syncraft-0.2.9/syncraft/walker.py +0 -147
- {syncraft-0.2.9 → syncraft-0.2.10}/LICENSE +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/README.md +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/setup.cfg +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/__init__.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/cache.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/constraint.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/dev.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/finder.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/generator.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/lexer.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/parser.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/py.typed +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/sqlite3.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft/utils.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft.egg-info/SOURCES.txt +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/syncraft.egg-info/top_level.txt +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/tests/test_bimap.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/tests/test_constraint.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/tests/test_find.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/tests/test_lazy.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/tests/test_parse.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/tests/test_to.py +0 -0
- {syncraft-0.2.9 → syncraft-0.2.10}/tests/test_walk.py +0 -0
|
@@ -8,6 +8,7 @@ from dataclasses import dataclass, replace
|
|
|
8
8
|
from syncraft.ast import ThenKind, Then, Choice, Many, ChoiceKind, shallow_dict, SyncraftError
|
|
9
9
|
from syncraft.cache import Cache
|
|
10
10
|
from syncraft.constraint import Bindable
|
|
11
|
+
from functools import cached_property
|
|
11
12
|
from rich import print
|
|
12
13
|
|
|
13
14
|
|
|
@@ -76,16 +77,22 @@ class Error:
|
|
|
76
77
|
class Algebra(Generic[A, S]):
|
|
77
78
|
######################################################## shared among all subclasses ########################################################
|
|
78
79
|
run_f: Callable[[S, bool], Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]]
|
|
79
|
-
name:
|
|
80
|
+
name: str
|
|
80
81
|
cache: Cache[Either[Any, Tuple[A, S]]]
|
|
82
|
+
|
|
81
83
|
|
|
82
84
|
@classmethod
|
|
83
85
|
def state(cls, **kwargs:Any)->Optional[S]:
|
|
84
86
|
return None
|
|
85
87
|
|
|
86
|
-
def named(self, name:
|
|
88
|
+
def named(self, name: str) -> Algebra[A, S]:
|
|
87
89
|
return replace(self, name=name)
|
|
88
|
-
|
|
90
|
+
|
|
91
|
+
@cached_property
|
|
92
|
+
def hashable(self)->Hashable:
|
|
93
|
+
return frozenset({'name': self.name, 'run_f': self.run_f})
|
|
94
|
+
|
|
95
|
+
|
|
89
96
|
def __call__(self, input: S, use_cache: bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]:
|
|
90
97
|
return self.run(input, use_cache=use_cache)
|
|
91
98
|
|
|
@@ -366,7 +373,7 @@ class Algebra(Generic[A, S]):
|
|
|
366
373
|
def map_all_run_f(input:S, use_cache:bool) -> Generator[Incomplete[S], S, Either[Any, Tuple[B, S]]]:
|
|
367
374
|
yield from ()
|
|
368
375
|
return Right(f(a, input))
|
|
369
|
-
return self.__class__(map_all_run_f, name=self.name, cache=self.cache)
|
|
376
|
+
return self.__class__(map_all_run_f, name=self.name, cache=self.cache) # type: ignore
|
|
370
377
|
return self.flat_map(map_all_f)
|
|
371
378
|
|
|
372
379
|
|
|
@@ -5,11 +5,11 @@ import re
|
|
|
5
5
|
from typing import (
|
|
6
6
|
Optional, Any, TypeVar, Tuple, runtime_checkable, cast,
|
|
7
7
|
Generic, Callable, Union, Protocol, Type, List, ClassVar,
|
|
8
|
-
Dict
|
|
8
|
+
Dict, Hashable
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
from dataclasses import dataclass, replace, is_dataclass, fields
|
|
12
|
+
from dataclasses import dataclass, replace, is_dataclass, fields, field
|
|
13
13
|
from enum import Enum
|
|
14
14
|
|
|
15
15
|
|
|
@@ -480,26 +480,51 @@ T = TypeVar('T', bound=TokenProtocol)
|
|
|
480
480
|
|
|
481
481
|
@dataclass(frozen=True)
|
|
482
482
|
class SyntaxSpec:
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
left: A
|
|
487
|
-
right: B
|
|
483
|
+
id: int = field(init=False)
|
|
484
|
+
def __post_init__(self) -> None:
|
|
485
|
+
object.__setattr__(self, 'id', id(self))
|
|
488
486
|
|
|
489
487
|
@dataclass(frozen=True)
|
|
488
|
+
class RefSpec(SyntaxSpec, Generic[A]):
|
|
489
|
+
ref: int | A
|
|
490
|
+
referent: str
|
|
491
|
+
@dataclass(frozen=True)
|
|
490
492
|
class LazySpec(SyntaxSpec, Generic[A]):
|
|
493
|
+
name: str
|
|
491
494
|
value: None | A
|
|
492
495
|
@dataclass(frozen=True)
|
|
493
496
|
class ThenSpec(SyntaxSpec, Generic[A, B]):
|
|
497
|
+
name: str
|
|
494
498
|
kind: ThenKind
|
|
495
499
|
left: A
|
|
496
500
|
right: B
|
|
501
|
+
# def __repr__(self) -> str:
|
|
502
|
+
# return f"ThenSpec(id={id(self)}, kind={self.kind}, left={self.left}, right={self.right})"
|
|
503
|
+
# def __str__(self) -> str:
|
|
504
|
+
# return self.__repr__()
|
|
505
|
+
|
|
506
|
+
@dataclass(frozen=True)
|
|
507
|
+
class ChoiceSpec(SyntaxSpec, Generic[A, B]):
|
|
508
|
+
name: str
|
|
509
|
+
left: A
|
|
510
|
+
right: B
|
|
511
|
+
|
|
512
|
+
# def __repr__(self) -> str:
|
|
513
|
+
# return f"ChoiceSpec(id={id(self)}, left={self.left}, right={self.right})"
|
|
514
|
+
# def __str__(self) -> str:
|
|
515
|
+
# return self.__repr__()
|
|
497
516
|
|
|
498
517
|
@dataclass(frozen=True)
|
|
499
518
|
class ManySpec(SyntaxSpec, Generic[A]):
|
|
519
|
+
name: str
|
|
500
520
|
value: A
|
|
501
521
|
at_least: int
|
|
502
522
|
at_most: Optional[int]
|
|
523
|
+
# def __repr__(self) -> str:
|
|
524
|
+
# return f"ManySpec(id={id(self)}, value={self.value}, at_least={self.at_least}, at_most={self.at_most})"
|
|
525
|
+
# def __str__(self) -> str:
|
|
526
|
+
# return self.__repr__()
|
|
527
|
+
|
|
503
528
|
|
|
504
529
|
|
|
505
530
|
@dataclass(frozen=True)
|
|
@@ -509,6 +534,19 @@ class TokenSpec(SyntaxSpec):
|
|
|
509
534
|
case_sensitive: bool = False
|
|
510
535
|
regex: Optional[re.Pattern[str]] = None
|
|
511
536
|
|
|
537
|
+
def __repr__(self) -> str:
|
|
538
|
+
parts = []
|
|
539
|
+
if self.token_type is not None:
|
|
540
|
+
parts.append(f"type={self.token_type.name}")
|
|
541
|
+
if self.text is not None:
|
|
542
|
+
parts.append(f"text={'`'+self.text+'`' if self.case_sensitive else self.text}")
|
|
543
|
+
if self.regex is not None:
|
|
544
|
+
parts.append(f"regex=/{self.regex.pattern}/")
|
|
545
|
+
return f"TokenSpec(id={id(self)}, " + ", ".join(parts) + ")"
|
|
546
|
+
|
|
547
|
+
def __str__(self) -> str:
|
|
548
|
+
return self.__repr__()
|
|
549
|
+
|
|
512
550
|
@classmethod
|
|
513
551
|
def create(cls,
|
|
514
552
|
*,
|
|
@@ -107,7 +107,7 @@ class Syntax(Generic[A, S]):
|
|
|
107
107
|
*,
|
|
108
108
|
name: Optional[str] = None,
|
|
109
109
|
fixity: Optional[Literal['infix', 'prefix', 'postfix']] = None,
|
|
110
|
-
parameter: Optional[Tuple[Syntax[Any, S], ...]] = None,
|
|
110
|
+
parameter: Optional[Tuple[Syntax[Any, S] | Any, ...]] = None,
|
|
111
111
|
) -> Syntax[A, S]:
|
|
112
112
|
return self.__class__(
|
|
113
113
|
alg=self.alg,
|
|
@@ -116,7 +116,16 @@ class Syntax(Generic[A, S]):
|
|
|
116
116
|
),
|
|
117
117
|
)
|
|
118
118
|
|
|
119
|
+
def named(self, name: str) -> Syntax[A, S]:
|
|
120
|
+
"""Assign a name to this syntax node for better debugging output.
|
|
119
121
|
|
|
122
|
+
Args:
|
|
123
|
+
name: Name to assign; must be a valid identifier.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Syntax with the given name.
|
|
127
|
+
"""
|
|
128
|
+
return self.__class__(lambda cls, cache: self(cls, cache).named(name), meta=self.meta)
|
|
120
129
|
######################################################## value transformation ########################################################
|
|
121
130
|
def map(self, f: Callable[[A], B]) -> Syntax[B, S]:
|
|
122
131
|
"""Map the produced value while preserving state and metadata.
|
|
@@ -219,7 +228,7 @@ class Syntax(Generic[A, S]):
|
|
|
219
228
|
Returns:
|
|
220
229
|
Syntax producing nested Then with all parts.
|
|
221
230
|
"""
|
|
222
|
-
return left >> self // right
|
|
231
|
+
return (left >> self // right).describe(name='between', fixity='postfix', parameter=(self, left, right))
|
|
223
232
|
|
|
224
233
|
def sep_by(self, sep: Syntax[B, S]) -> Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S]:
|
|
225
234
|
"""Parse one or more items separated by sep.
|
|
@@ -316,7 +325,7 @@ class Syntax(Generic[A, S]):
|
|
|
316
325
|
Returns:
|
|
317
326
|
Syntax that marks downstream failures as committed.
|
|
318
327
|
"""
|
|
319
|
-
return self.__class__(lambda cls, cache: self(cls, cache).cut())
|
|
328
|
+
return self.__class__(lambda cls, cache: self(cls, cache).cut()).describe(name='cut', fixity='postfix', parameter=(self,))
|
|
320
329
|
|
|
321
330
|
###################################################### operator overloading #############################################
|
|
322
331
|
def __floordiv__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
|
|
@@ -433,7 +442,7 @@ class Syntax(Generic[A, S]):
|
|
|
433
442
|
else:
|
|
434
443
|
return v, s
|
|
435
444
|
|
|
436
|
-
return self.map_all(bind_v).describe(name=
|
|
445
|
+
return self.map_all(bind_v).describe(name='bind', fixity='infix', parameter=(self, name))
|
|
437
446
|
|
|
438
447
|
def to(self, f: Collector[E]) -> Syntax[Collect[A, E], S]:
|
|
439
448
|
"""Attach a collector to the produced value.
|
|
@@ -457,7 +466,7 @@ class Syntax(Generic[A, S]):
|
|
|
457
466
|
def ito_f(c: Collect[A, E]) -> A:
|
|
458
467
|
return c.value if isinstance(c, Collect) else c
|
|
459
468
|
|
|
460
|
-
return self.bimap(to_f, ito_f).describe(name=
|
|
469
|
+
return self.bimap(to_f, ito_f).describe(name='to', fixity='infix', parameter=(self, f))
|
|
461
470
|
|
|
462
471
|
def mark(self, name: str) -> Syntax[Marked[A], S]:
|
|
463
472
|
"""Mark the produced value with a name.
|
|
@@ -481,7 +490,7 @@ class Syntax(Generic[A, S]):
|
|
|
481
490
|
def imark_s(m: Marked[A]) -> A:
|
|
482
491
|
return m.value if isinstance(m, Marked) else m
|
|
483
492
|
|
|
484
|
-
return self.bimap(mark_s, imark_s).describe(name=
|
|
493
|
+
return self.bimap(mark_s, imark_s).describe(name='mark', fixity='infix', parameter=(self, name))
|
|
485
494
|
|
|
486
495
|
def when(f: Callable[[], bool], then: Syntax[A, S], otherwise: Syntax[B, S]) -> Syntax[A | B, S]:
|
|
487
496
|
"""
|
|
@@ -495,7 +504,7 @@ def when(f: Callable[[], bool], then: Syntax[A, S], otherwise: Syntax[B, S]) ->
|
|
|
495
504
|
Returns:
|
|
496
505
|
A Syntax object representing the chosen branch.
|
|
497
506
|
"""
|
|
498
|
-
return lazy(lambda: then if f() else otherwise).describe(name='when
|
|
507
|
+
return lazy(lambda: then if f() else otherwise).describe(name='when', fixity='postfix', parameter=(then, otherwise)) # type: ignore
|
|
499
508
|
|
|
500
509
|
def fail(error: Any) -> Syntax[Any, Any]:
|
|
501
510
|
"""
|
|
@@ -507,7 +516,7 @@ def fail(error: Any) -> Syntax[Any, Any]:
|
|
|
507
516
|
Returns:
|
|
508
517
|
A Syntax object that always fails.
|
|
509
518
|
"""
|
|
510
|
-
return Syntax(lambda alg, cache: alg.fail(error, cache=cache)).describe(name=
|
|
519
|
+
return Syntax(lambda alg, cache: alg.fail(error, cache=cache)).describe(name='fail', fixity='prefix', parameter=(error,))
|
|
511
520
|
|
|
512
521
|
def success(value: Any) -> Syntax[Any, Any]:
|
|
513
522
|
"""
|
|
@@ -519,7 +528,7 @@ def success(value: Any) -> Syntax[Any, Any]:
|
|
|
519
528
|
Returns:
|
|
520
529
|
A Syntax object that always succeeds.
|
|
521
530
|
"""
|
|
522
|
-
return Syntax(lambda alg, cache: alg.success(value, cache=cache)).describe(name=
|
|
531
|
+
return Syntax(lambda alg, cache: alg.success(value, cache=cache)).describe(name='success', fixity='prefix', parameter=(value,))
|
|
523
532
|
|
|
524
533
|
|
|
525
534
|
|
|
@@ -581,7 +590,7 @@ def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
|
|
|
581
590
|
algebra = cls.lazy(algebra_lazy_f, cache=cache)
|
|
582
591
|
previous_cls = cls
|
|
583
592
|
return algebra
|
|
584
|
-
return Syntax(syntax_lazy_run).describe(name='lazy
|
|
593
|
+
return Syntax(syntax_lazy_run).describe(name='lazy', fixity='prefix', parameter=(lambda: syntax,))
|
|
585
594
|
|
|
586
595
|
|
|
587
596
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
Any, Tuple, Generator as PyGenerator, TypeVar, Generic, Optional, Callable, Hashable
|
|
5
|
+
)
|
|
6
|
+
from dataclasses import dataclass, replace, field
|
|
7
|
+
from syncraft.algebra import (
|
|
8
|
+
Algebra, Either, Right, Incomplete, Left, SyncraftError
|
|
9
|
+
)
|
|
10
|
+
from syncraft.ast import TokenSpec, ThenSpec, ManySpec, ChoiceSpec, LazySpec, ThenKind, RefSpec
|
|
11
|
+
from syncraft.parser import TokenType
|
|
12
|
+
from syncraft.constraint import Bindable, FrozenDict
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from syncraft.syntax import Syntax
|
|
16
|
+
from syncraft.cache import Cache, RecursionError
|
|
17
|
+
from rich import print
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
S = TypeVar('S', bound=Bindable)
|
|
21
|
+
A = TypeVar('A')
|
|
22
|
+
B = TypeVar('B')
|
|
23
|
+
SS = TypeVar('SS', bound=Hashable)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class WalkerState(Bindable):
|
|
28
|
+
visited: FrozenDict = field(default_factory=FrozenDict)
|
|
29
|
+
def visit(self, algebra: Algebra, data: Hashable) -> WalkerState:
|
|
30
|
+
return replace(self, visited=self.visited | FrozenDict({algebra.hashable: data}))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class Walker(Algebra[Any, WalkerState]):
|
|
36
|
+
@classmethod
|
|
37
|
+
def state(cls, **kwargs: Any)->WalkerState:
|
|
38
|
+
return WalkerState()
|
|
39
|
+
|
|
40
|
+
def map(self, f: Callable[[Any], Any]) -> Algebra[Any, WalkerState]:
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
def map_state(self, f: Callable[[WalkerState], WalkerState]) -> Algebra[Any, WalkerState]:
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def bimap(self, f: Callable[[Any], Any], g: Callable[[Any], Any]) -> Algebra[Any, WalkerState]:
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def map_all(self, f: Callable[[Any, WalkerState], Tuple[Any, WalkerState]]) -> Algebra[Any, WalkerState]:
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def flat_map(self, f: Callable[[Any], Algebra[Any, WalkerState]]) -> Algebra[Any, WalkerState]:
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def run(self,
|
|
56
|
+
input: WalkerState,
|
|
57
|
+
use_cache: bool = True
|
|
58
|
+
) -> PyGenerator[Incomplete[WalkerState], WalkerState, Either[Any, Tuple[Any, WalkerState]]]:
|
|
59
|
+
try:
|
|
60
|
+
if self.hashable in input.visited:
|
|
61
|
+
ref = input.visited[self.hashable]
|
|
62
|
+
return Right((RefSpec(ref=id(ref), referent= '' if not hasattr(ref, 'name') else ref.name), input))
|
|
63
|
+
return (yield from self.cache.gen(self.run_f, input, use_cache))
|
|
64
|
+
except RecursionError as e:
|
|
65
|
+
if self.hashable in input.visited:
|
|
66
|
+
ref = input.visited[self.hashable]
|
|
67
|
+
return Right((RefSpec(ref=id(ref), referent= '' if not hasattr(ref, 'name') else ref.name), input))
|
|
68
|
+
else:
|
|
69
|
+
return Right((RefSpec(ref=e.offending, referent=''), input))
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def token(cls,
|
|
73
|
+
*,
|
|
74
|
+
cache: Cache,
|
|
75
|
+
token_type: Optional[TokenType] = None,
|
|
76
|
+
text: Optional[str] = None,
|
|
77
|
+
case_sensitive: bool = False,
|
|
78
|
+
regex: Optional[re.Pattern[str]] = None
|
|
79
|
+
)-> Algebra[Any, WalkerState]:
|
|
80
|
+
name = f'Token({token_type or text or (regex.pattern if regex else "")})'
|
|
81
|
+
this: None | Algebra[Any, WalkerState] = None
|
|
82
|
+
def token_run(input: WalkerState, use_cache:bool) -> PyGenerator[Incomplete[WalkerState], WalkerState, Either[Any, Tuple[Any, WalkerState]]]:
|
|
83
|
+
yield from ()
|
|
84
|
+
data = TokenSpec(token_type=token_type, text=text, regex=regex, case_sensitive=case_sensitive)
|
|
85
|
+
return Right((data, input.visit(this, data) if this is not None else input))
|
|
86
|
+
this = cls(token_run, name=name, cache=cache)
|
|
87
|
+
return this
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def lazy(cls,
|
|
91
|
+
thunk: Callable[[], Algebra[Any, WalkerState]],
|
|
92
|
+
cache: Cache) -> Algebra[Any, WalkerState]:
|
|
93
|
+
this: None | Algebra[Any, WalkerState] = None
|
|
94
|
+
def algebra_lazy_run(input: WalkerState, use_cache:bool) -> PyGenerator[Incomplete[WalkerState], WalkerState, Either[Any, Tuple[Any, WalkerState]]]:
|
|
95
|
+
alg = thunk()
|
|
96
|
+
thunk_result = yield from alg.run(input, use_cache)
|
|
97
|
+
match thunk_result:
|
|
98
|
+
case Right((value, from_thunk)):
|
|
99
|
+
data: LazySpec[Any] = LazySpec(name=alg.name, value=value)
|
|
100
|
+
from_thunk = from_thunk.visit(alg, value)
|
|
101
|
+
return Right((data, from_thunk.visit(this, data) if this is not None else from_thunk))
|
|
102
|
+
raise SyncraftError("flat_map should always return a value or an error.", offending=thunk_result, expect=(Left, Right))
|
|
103
|
+
this = cls(algebra_lazy_run, name='lazy(?)', cache=cache)
|
|
104
|
+
return this
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def then_all(self, other: Algebra[Any, WalkerState], kind: ThenKind) -> Algebra[Any, WalkerState]:
|
|
108
|
+
name = f"{self.name} {kind.value} {other.name}"
|
|
109
|
+
this: None | Algebra[Any, WalkerState] = None
|
|
110
|
+
def then_run(input: WalkerState, use_cache:bool) -> PyGenerator[Incomplete[WalkerState], WalkerState, Either[Any, Tuple[Any, WalkerState]]]:
|
|
111
|
+
self_result = yield from self.run(input, use_cache=use_cache)
|
|
112
|
+
match self_result:
|
|
113
|
+
case Right((value, from_left)):
|
|
114
|
+
other_result = yield from other.run(from_left.visit(self, value), use_cache)
|
|
115
|
+
match other_result:
|
|
116
|
+
case Right((result, from_right)):
|
|
117
|
+
data = ThenSpec(name=name, kind=kind, left=value, right=result)
|
|
118
|
+
from_right = from_right.visit(other, result)
|
|
119
|
+
return Right((data, from_right.visit(this, data) if this is not None else from_right))
|
|
120
|
+
raise SyncraftError("flat_map should always return a value or an error.", offending=self_result, expect=(Left, Right))
|
|
121
|
+
this = self.__class__(then_run, name=name, cache=self.cache | other.cache)
|
|
122
|
+
return this
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def then_both(self, other: Algebra[Any, WalkerState]) -> Algebra[Any, WalkerState]:
|
|
127
|
+
return self.then_all(other, ThenKind.BOTH)
|
|
128
|
+
|
|
129
|
+
def then_left(self, other: Algebra[Any, WalkerState]) -> Algebra[Any, WalkerState]:
|
|
130
|
+
return self.then_all(other, ThenKind.LEFT)
|
|
131
|
+
|
|
132
|
+
def then_right(self, other: Algebra[Any, WalkerState]) -> Algebra[Any, WalkerState]:
|
|
133
|
+
return self.then_all(other, ThenKind.RIGHT)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[Any, WalkerState]:
|
|
137
|
+
if at_least <=0 or (at_most is not None and at_most < at_least):
|
|
138
|
+
raise SyncraftError(f"Invalid arguments for many: at_least={at_least}, at_most={at_most}", offending=(at_least, at_most), expect="at_least>0 and (at_most is None or at_most>=at_least)")
|
|
139
|
+
this: None | Algebra[Any, WalkerState] = None
|
|
140
|
+
def many_run(input: WalkerState, use_cache:bool) -> PyGenerator[Incomplete[WalkerState], WalkerState, Either[Any, Tuple[Any, WalkerState]]]:
|
|
141
|
+
self_result = yield from self.run(input, use_cache)
|
|
142
|
+
match self_result:
|
|
143
|
+
case Right((value, from_self)):
|
|
144
|
+
data = ManySpec(name=f"*({self.name})", value=value, at_least=at_least, at_most=at_most)
|
|
145
|
+
from_self = from_self.visit(self, value)
|
|
146
|
+
return Right((data, from_self.visit(this, data) if this is not None else from_self))
|
|
147
|
+
raise SyncraftError("many should always return a value or an error.", offending=self_result, expect=(Left, Right))
|
|
148
|
+
this = self.__class__(many_run, name=self.name, cache=self.cache)
|
|
149
|
+
return this
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def or_else(self, other: Algebra[Any, WalkerState]) -> Algebra[Any, WalkerState]:
|
|
153
|
+
pattern = re.compile(r'\s')
|
|
154
|
+
self_name = self.name.strip()
|
|
155
|
+
self_name = f"({self_name})" if bool(pattern.search(self_name)) else self_name
|
|
156
|
+
other_name = other.name.strip()
|
|
157
|
+
other_name = f"({other_name})" if bool(pattern.search(other_name)) else other_name
|
|
158
|
+
name = f"{self_name} | {other_name}"
|
|
159
|
+
this: None | Algebra[Any, WalkerState] = None
|
|
160
|
+
def or_else_run(input: WalkerState, use_cache:bool) -> PyGenerator[Incomplete[WalkerState], WalkerState, Either[Any, Tuple[Any, WalkerState]]]:
|
|
161
|
+
self_result = yield from self.run(input, use_cache=use_cache)
|
|
162
|
+
match self_result:
|
|
163
|
+
case Right((value, from_left)):
|
|
164
|
+
other_result = yield from other.run(from_left.visit(self, value), use_cache)
|
|
165
|
+
match other_result:
|
|
166
|
+
case Right((result, from_right)):
|
|
167
|
+
data = ChoiceSpec(name=name, left=value, right=result)
|
|
168
|
+
from_right = from_right.visit(other, result)
|
|
169
|
+
return Right((data, from_right.visit(this, data) if this is not None else from_right))
|
|
170
|
+
raise SyncraftError("", offending=self)
|
|
171
|
+
this = self.__class__(or_else_run, name=name, cache=self.cache | other.cache)
|
|
172
|
+
return this
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def walk(syntax: Syntax[Any, Any], reducer: Optional[Callable[[Any, Any], SS]] = None, init: Optional[SS] = None) -> Any:
|
|
177
|
+
from syncraft.syntax import run
|
|
178
|
+
v, s = run(syntax=syntax,
|
|
179
|
+
alg=Walker,
|
|
180
|
+
use_cache=True,
|
|
181
|
+
reducer=reducer or (lambda a, s: s),
|
|
182
|
+
init=init)
|
|
183
|
+
if reducer is None:
|
|
184
|
+
return v
|
|
185
|
+
else:
|
|
186
|
+
if s is not None:
|
|
187
|
+
return s.acc
|
|
188
|
+
else:
|
|
189
|
+
return None
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import (
|
|
4
|
-
Any, Tuple, Generator as PyGenerator, TypeVar, Generic, Optional, Callable, Hashable
|
|
5
|
-
)
|
|
6
|
-
from dataclasses import dataclass, replace, field
|
|
7
|
-
from syncraft.algebra import (
|
|
8
|
-
Algebra, Either, Right, Incomplete, Left, SyncraftError
|
|
9
|
-
)
|
|
10
|
-
from syncraft.ast import TokenSpec, ThenSpec, ManySpec, ChoiceSpec, LazySpec, ThenKind
|
|
11
|
-
from syncraft.parser import TokenType
|
|
12
|
-
from syncraft.constraint import Bindable, FrozenDict
|
|
13
|
-
|
|
14
|
-
import re
|
|
15
|
-
from syncraft.syntax import Syntax
|
|
16
|
-
from syncraft.cache import Cache, RecursionError
|
|
17
|
-
from rich import print
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
S = TypeVar('S', bound=Bindable)
|
|
21
|
-
A = TypeVar('A')
|
|
22
|
-
B = TypeVar('B')
|
|
23
|
-
SS = TypeVar('SS', bound=Hashable)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@dataclass(frozen=True)
|
|
27
|
-
class WalkerState(Bindable, Generic[SS]):
|
|
28
|
-
reducer: Optional[Callable[[Any, SS], SS]] = None
|
|
29
|
-
acc: Optional[SS] = None
|
|
30
|
-
|
|
31
|
-
def reduce(self, value: Any) -> WalkerState[SS]:
|
|
32
|
-
if self.reducer:
|
|
33
|
-
new_acc = self.reducer(value, self.acc) if self.acc is not None else value
|
|
34
|
-
return replace(self, acc=new_acc)
|
|
35
|
-
else:
|
|
36
|
-
return replace(self, acc=value)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@dataclass(frozen=True)
|
|
40
|
-
class Walker(Algebra[SS, WalkerState[SS]]):
|
|
41
|
-
@classmethod
|
|
42
|
-
def state(cls, reducer: Callable[[Any, SS], SS], init: SS )->WalkerState[SS]: # type: ignore
|
|
43
|
-
assert callable(reducer), f"reducer must be a Reducer or None, got {type(reducer)}"
|
|
44
|
-
return WalkerState(reducer=reducer, acc=init)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@classmethod
|
|
49
|
-
def token(cls,
|
|
50
|
-
*,
|
|
51
|
-
cache: Cache,
|
|
52
|
-
token_type: Optional[TokenType] = None,
|
|
53
|
-
text: Optional[str] = None,
|
|
54
|
-
case_sensitive: bool = False,
|
|
55
|
-
regex: Optional[re.Pattern[str]] = None
|
|
56
|
-
)-> Algebra[Any, WalkerState[SS]]:
|
|
57
|
-
def token_run(input: WalkerState[SS], use_cache:bool) -> PyGenerator[Incomplete[WalkerState[SS]], WalkerState[SS], Either[Any, Tuple[Any, WalkerState[SS]]]]:
|
|
58
|
-
yield from ()
|
|
59
|
-
data = TokenSpec(token_type=token_type, text=text, regex=regex, case_sensitive=case_sensitive)
|
|
60
|
-
return Right((data, input.reduce(data)))
|
|
61
|
-
return cls(token_run, name=cls.__name__ + f'.token({token_type or text or regex})', cache=cache)
|
|
62
|
-
|
|
63
|
-
@classmethod
|
|
64
|
-
def lazy(cls,
|
|
65
|
-
thunk: Callable[[], Algebra[Any, WalkerState[SS]]],
|
|
66
|
-
cache: Cache) -> Algebra[Any, WalkerState[SS]]:
|
|
67
|
-
def algebra_lazy_run(input: WalkerState[SS], use_cache:bool) -> PyGenerator[Incomplete[WalkerState[SS]], WalkerState[SS], Either[Any, Tuple[Any, WalkerState[SS]]]]:
|
|
68
|
-
alg = thunk()
|
|
69
|
-
# print('--' * 20, "Walker.lazy.algebra_lazy_run", '--' * 20)
|
|
70
|
-
# print('thunk', thunk, id(thunk))
|
|
71
|
-
# print('input', input, id(input))
|
|
72
|
-
# print('alg', alg, id(alg))
|
|
73
|
-
try:
|
|
74
|
-
thunk_result = yield from alg.run(input, use_cache)
|
|
75
|
-
match thunk_result:
|
|
76
|
-
case Right((value, from_thunk)):
|
|
77
|
-
data: LazySpec[Any] = LazySpec(value=value)
|
|
78
|
-
return Right((data, from_thunk.reduce(data)))
|
|
79
|
-
raise SyncraftError("flat_map should always return a value or an error.", offending=thunk_result, expect=(Left, Right))
|
|
80
|
-
except RecursionError as e:
|
|
81
|
-
return Right((LazySpec(value=None), input))
|
|
82
|
-
return cls(algebra_lazy_run, name=cls.__name__ + '.lazy', cache=cache)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def then_both(self, other: Algebra[Any, WalkerState[SS]]) -> Algebra[Any, WalkerState[SS]]:
|
|
87
|
-
def then_run(input: WalkerState[SS], use_cache:bool) -> PyGenerator[Incomplete[WalkerState[SS]], WalkerState[SS], Either[Any, Tuple[Any, WalkerState[SS]]]]:
|
|
88
|
-
self_result = yield from self.run(input, use_cache=use_cache)
|
|
89
|
-
match self_result:
|
|
90
|
-
case Right((value, from_left)):
|
|
91
|
-
other_result = yield from other.run(from_left, use_cache)
|
|
92
|
-
match other_result:
|
|
93
|
-
case Right((result, from_right)):
|
|
94
|
-
data = ThenSpec(kind=ThenKind.BOTH, left=value, right=result)
|
|
95
|
-
return Right((data, from_right.reduce(data)))
|
|
96
|
-
raise SyncraftError("flat_map should always return a value or an error.", offending=self_result, expect=(Left, Right))
|
|
97
|
-
return self.__class__(then_run, name=self.name, cache=self.cache | other.cache)
|
|
98
|
-
|
|
99
|
-
def then_left(self, other: Algebra[Any, WalkerState[SS]]) -> Algebra[Any, WalkerState[SS]]:
|
|
100
|
-
return self.then_both(other).map(lambda t: replace(t, kind=ThenKind.LEFT))
|
|
101
|
-
|
|
102
|
-
def then_right(self, other: Algebra[Any, WalkerState[SS]]) -> Algebra[Any, WalkerState[SS]]:
|
|
103
|
-
return self.then_both(other).map(lambda t: replace(t, kind=ThenKind.RIGHT))
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[Any, WalkerState[SS]]:
|
|
107
|
-
if at_least <=0 or (at_most is not None and at_most < at_least):
|
|
108
|
-
raise SyncraftError(f"Invalid arguments for many: at_least={at_least}, at_most={at_most}", offending=(at_least, at_most), expect="at_least>0 and (at_most is None or at_most>=at_least)")
|
|
109
|
-
def many_run(input: WalkerState[SS], use_cache:bool) -> PyGenerator[Incomplete[WalkerState[SS]], WalkerState[SS], Either[Any, Tuple[Any, WalkerState[SS]]]]:
|
|
110
|
-
self_result = yield from self.run(input, use_cache)
|
|
111
|
-
match self_result:
|
|
112
|
-
case Right((value, from_self)):
|
|
113
|
-
data = ManySpec(value=value, at_least=at_least, at_most=at_most)
|
|
114
|
-
return Right((data, from_self.reduce(data)))
|
|
115
|
-
raise SyncraftError("many should always return a value or an error.", offending=self_result, expect=(Left, Right))
|
|
116
|
-
return self.__class__(many_run, name=f"many({self.name})", cache=self.cache)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def or_else(self, other: Algebra[Any, WalkerState[SS]]) -> Algebra[Any, WalkerState[SS]]:
|
|
120
|
-
def or_else_run(input: WalkerState[SS], use_cache:bool) -> PyGenerator[Incomplete[WalkerState[SS]], WalkerState[SS], Either[Any, Tuple[Any, WalkerState[SS]]]]:
|
|
121
|
-
self_result = yield from self.run(input, use_cache=use_cache)
|
|
122
|
-
match self_result:
|
|
123
|
-
case Right((value, from_left)):
|
|
124
|
-
other_result = yield from other.run(from_left, use_cache)
|
|
125
|
-
match other_result:
|
|
126
|
-
case Right((result, from_right)):
|
|
127
|
-
data = ChoiceSpec(left=value, right=result)
|
|
128
|
-
return Right((data, from_right.reduce(data)))
|
|
129
|
-
raise SyncraftError("", offending=self)
|
|
130
|
-
return self.__class__(or_else_run, name=f"or_else({self.name} | {other.name})", cache=self.cache | other.cache)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def walk(syntax: Syntax[Any, Any], reducer: Optional[Callable[[Any, Any], SS]] = None, init: Optional[SS] = None) -> Any:
|
|
135
|
-
from syncraft.syntax import run
|
|
136
|
-
v, s = run(syntax=syntax,
|
|
137
|
-
alg=Walker,
|
|
138
|
-
use_cache=True,
|
|
139
|
-
reducer=reducer or (lambda a, s: s),
|
|
140
|
-
init=init)
|
|
141
|
-
if reducer is None:
|
|
142
|
-
return v
|
|
143
|
-
else:
|
|
144
|
-
if s is not None:
|
|
145
|
-
return s.acc
|
|
146
|
-
else:
|
|
147
|
-
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|