syncraft 0.2.7__tar.gz → 0.2.9__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.7/syncraft.egg-info → syncraft-0.2.9}/PKG-INFO +1 -1
- {syncraft-0.2.7 → syncraft-0.2.9}/pyproject.toml +1 -1
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/algebra.py +1 -1
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/ast.py +2 -1
- syncraft-0.2.9/syncraft/cache.py +142 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/generator.py +1 -1
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/parser.py +0 -27
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/syntax.py +9 -18
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/walker.py +52 -52
- {syncraft-0.2.7 → syncraft-0.2.9/syncraft.egg-info}/PKG-INFO +1 -1
- {syncraft-0.2.7 → syncraft-0.2.9}/tests/test_walk.py +4 -4
- syncraft-0.2.7/syncraft/cache.py +0 -113
- {syncraft-0.2.7 → syncraft-0.2.9}/LICENSE +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/README.md +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/setup.cfg +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/__init__.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/constraint.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/dev.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/finder.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/lexer.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/py.typed +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/sqlite3.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft/utils.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft.egg-info/SOURCES.txt +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft.egg-info/dependency_links.txt +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft.egg-info/requires.txt +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/syncraft.egg-info/top_level.txt +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/tests/test_bimap.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/tests/test_constraint.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/tests/test_find.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/tests/test_lazy.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/tests/test_parse.py +0 -0
- {syncraft-0.2.7 → syncraft-0.2.9}/tests/test_to.py +0 -0
|
@@ -77,7 +77,7 @@ class Algebra(Generic[A, S]):
|
|
|
77
77
|
######################################################## shared among all subclasses ########################################################
|
|
78
78
|
run_f: Callable[[S, bool], Generator[Incomplete[S], S, Either[Any, Tuple[A, S]]]]
|
|
79
79
|
name: Hashable
|
|
80
|
-
cache: Cache[
|
|
80
|
+
cache: Cache[Either[Any, Tuple[A, S]]]
|
|
81
81
|
|
|
82
82
|
@classmethod
|
|
83
83
|
def state(cls, **kwargs:Any)->Optional[S]:
|
|
@@ -488,9 +488,10 @@ class ChoiceSpec(SyntaxSpec, Generic[A, B]):
|
|
|
488
488
|
|
|
489
489
|
@dataclass(frozen=True)
|
|
490
490
|
class LazySpec(SyntaxSpec, Generic[A]):
|
|
491
|
-
value: A
|
|
491
|
+
value: None | A
|
|
492
492
|
@dataclass(frozen=True)
|
|
493
493
|
class ThenSpec(SyntaxSpec, Generic[A, B]):
|
|
494
|
+
kind: ThenKind
|
|
494
495
|
left: A
|
|
495
496
|
right: B
|
|
496
497
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Dict, TypeVar, Hashable, Generic, Callable, Any, Generator, overload, Literal
|
|
5
|
+
from weakref import WeakKeyDictionary
|
|
6
|
+
from syncraft.ast import SyncraftError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RecursionError(SyncraftError):
|
|
10
|
+
def __init__(self, message: str, offending: Any, expect: Any = None, **kwargs: Any) -> None:
|
|
11
|
+
super().__init__(message, offending, expect, **kwargs)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class InProgress:
|
|
16
|
+
_instance = None
|
|
17
|
+
def __new__(cls):
|
|
18
|
+
if cls._instance is None:
|
|
19
|
+
cls._instance = super(InProgress, cls).__new__(cls)
|
|
20
|
+
return cls._instance
|
|
21
|
+
def __str__(self)->str:
|
|
22
|
+
return self.__class__.__name__
|
|
23
|
+
def __repr__(self)->str:
|
|
24
|
+
return self.__str__()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Args = TypeVar('Args', bound=Hashable)
|
|
30
|
+
Ret = TypeVar('Ret')
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Cache(Generic[Ret]):
|
|
34
|
+
cache: WeakKeyDictionary[Callable[..., Any], Dict[Hashable, Ret | InProgress]] = field(default_factory=WeakKeyDictionary)
|
|
35
|
+
|
|
36
|
+
def __contains__(self, f: Callable[..., Any]) -> bool:
|
|
37
|
+
return f in self.cache
|
|
38
|
+
|
|
39
|
+
def __repr__(self) -> str:
|
|
40
|
+
return f"Cache({({f.__name__: list(c.keys()) for f, c in self.cache.items()})})"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def __or__(self, other: Cache[Any]) -> Cache[Any]:
|
|
44
|
+
assert self.cache is other.cache, "There should be only one global cache"
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
@overload
|
|
48
|
+
def _execute(self,
|
|
49
|
+
f: Callable[..., Ret],
|
|
50
|
+
*args:Any,
|
|
51
|
+
is_gen: Literal[False],
|
|
52
|
+
**kwargs:Any) -> Ret: ...
|
|
53
|
+
@overload
|
|
54
|
+
def _execute(self,
|
|
55
|
+
f: Callable[..., Generator[Any, Any, Ret]],
|
|
56
|
+
*args: Any,
|
|
57
|
+
is_gen: Literal[True],
|
|
58
|
+
**kwargs: Any) -> Generator[Any, Any, Ret]: ...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _execute(self,
|
|
62
|
+
f: Callable[..., Any],
|
|
63
|
+
*args: Any,
|
|
64
|
+
is_gen: bool,
|
|
65
|
+
**kwargs: Any) -> Ret | Generator[Any, Any, Ret]:
|
|
66
|
+
if f not in self.cache:
|
|
67
|
+
self.cache.setdefault(f, dict())
|
|
68
|
+
c: Dict[Hashable, Ret | InProgress] = self.cache[f]
|
|
69
|
+
key = (args, tuple(sorted(kwargs.items())))
|
|
70
|
+
if key in c:
|
|
71
|
+
v = c[key]
|
|
72
|
+
if isinstance(v, InProgress):
|
|
73
|
+
raise RecursionError(f"Left-recursion detected in Algebra {f}", offending=f, state=args)
|
|
74
|
+
else:
|
|
75
|
+
return v
|
|
76
|
+
try:
|
|
77
|
+
c[key] = InProgress()
|
|
78
|
+
if is_gen:
|
|
79
|
+
result = yield from f(*args, **kwargs)
|
|
80
|
+
else:
|
|
81
|
+
result = f(*args, **kwargs)
|
|
82
|
+
c[key] = result
|
|
83
|
+
if kwargs.get('use_cache', True) is False:
|
|
84
|
+
c.pop(key, None)
|
|
85
|
+
return result
|
|
86
|
+
except Exception as e:
|
|
87
|
+
c.pop(key, None)
|
|
88
|
+
raise e
|
|
89
|
+
|
|
90
|
+
def gen(self,
|
|
91
|
+
f: Callable[..., Generator[Any, Any, Ret]],
|
|
92
|
+
*args: Any,
|
|
93
|
+
**kwargs: Any) -> Generator[Any, Any, Ret]:
|
|
94
|
+
if f not in self.cache:
|
|
95
|
+
self.cache.setdefault(f, dict())
|
|
96
|
+
c: Dict[Hashable, Ret | InProgress] = self.cache[f]
|
|
97
|
+
key = (tuple(filter(lambda x: not isinstance(x, Cache), args)), tuple(sorted(filter(lambda item: not isinstance(item[1], Cache), kwargs.items()))))
|
|
98
|
+
if key in c:
|
|
99
|
+
v = c[key]
|
|
100
|
+
if isinstance(v, InProgress):
|
|
101
|
+
raise RecursionError(f"Left-recursion detected in Algebra {f}", offending=f, state=args)
|
|
102
|
+
else:
|
|
103
|
+
return v
|
|
104
|
+
try:
|
|
105
|
+
c[key] = InProgress()
|
|
106
|
+
result = yield from f(*args, **kwargs)
|
|
107
|
+
c[key] = result
|
|
108
|
+
if kwargs.get('use_cache', True) is False:
|
|
109
|
+
c.pop(key, None)
|
|
110
|
+
return result
|
|
111
|
+
except Exception as e:
|
|
112
|
+
c.pop(key, None)
|
|
113
|
+
raise e
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def call(self,
|
|
117
|
+
f: Callable[..., Ret],
|
|
118
|
+
*args:Any,
|
|
119
|
+
**kwargs:Any) -> Ret:
|
|
120
|
+
if f not in self.cache:
|
|
121
|
+
self.cache.setdefault(f, dict())
|
|
122
|
+
c: Dict[Hashable, Ret | InProgress] = self.cache[f]
|
|
123
|
+
key = (tuple(filter(lambda x: not isinstance(x, Cache), args)), tuple(sorted(filter(lambda item: not isinstance(item[1], Cache), kwargs.items()))))
|
|
124
|
+
if key in c:
|
|
125
|
+
v = c[key]
|
|
126
|
+
if isinstance(v, InProgress):
|
|
127
|
+
raise RecursionError("Left-recursion detected in parser", offending=f, state=args)
|
|
128
|
+
else:
|
|
129
|
+
return v
|
|
130
|
+
try:
|
|
131
|
+
c[key] = InProgress()
|
|
132
|
+
result = f(*args, **kwargs)
|
|
133
|
+
c[key] = result
|
|
134
|
+
if kwargs.get('use_cache', True) is False:
|
|
135
|
+
c.pop(key, None)
|
|
136
|
+
return result
|
|
137
|
+
except Exception as e:
|
|
138
|
+
c.pop(key, None)
|
|
139
|
+
raise e
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
@@ -245,7 +245,7 @@ class Generator(Algebra[ParseResult[T], GenState[T]]):
|
|
|
245
245
|
Algebra[B, GenState[T]]: An algebra yielding the final result.
|
|
246
246
|
"""
|
|
247
247
|
def flat_map_run(input: GenState[T], use_cache:bool) -> PyGenerator[Incomplete[GenState[T]], GenState[T], Either[Any, Tuple[B, GenState[T]]]]:
|
|
248
|
-
if not isinstance(input.ast, Then) or isinstance(input.ast, Nothing):
|
|
248
|
+
if not input.pruned and (not isinstance(input.ast, Then) or isinstance(input.ast, Nothing)):
|
|
249
249
|
return Left(Error(this=self,
|
|
250
250
|
message=f"Expect Then got {input.ast}",
|
|
251
251
|
state=input))
|
|
@@ -122,18 +122,6 @@ class ParserState(Bindable, Generic[T]):
|
|
|
122
122
|
class Parser(Algebra[T, ParserState[T]]):
|
|
123
123
|
@classmethod
|
|
124
124
|
def state(cls, sql: str, dialect: str) -> ParserState[T]: # type: ignore
|
|
125
|
-
"""Tokenize SQL text into an initial ``ParserState``.
|
|
126
|
-
|
|
127
|
-
Uses ``sqlglot.tokenize`` for the given dialect and wraps tokens into
|
|
128
|
-
the project's ``Token`` type.
|
|
129
|
-
|
|
130
|
-
Args:
|
|
131
|
-
sql: The SQL text to tokenize.
|
|
132
|
-
dialect: The sqlglot dialect name (e.g. "sqlite", "duckdb").
|
|
133
|
-
|
|
134
|
-
Returns:
|
|
135
|
-
ParserState[T]: Initial parser state at index 0.
|
|
136
|
-
"""
|
|
137
125
|
tokens = tuple([Token(token_type=token.token_type, text=token.text) for token in tokenize(sql, dialect=dialect)])
|
|
138
126
|
return ParserState.from_tokens(tokens) # type: ignore
|
|
139
127
|
|
|
@@ -146,21 +134,6 @@ class Parser(Algebra[T, ParserState[T]]):
|
|
|
146
134
|
case_sensitive: bool = False,
|
|
147
135
|
regex: Optional[re.Pattern[str]] = None
|
|
148
136
|
)-> Algebra[T, ParserState[T]]:
|
|
149
|
-
"""Match a single token according to a specification.
|
|
150
|
-
|
|
151
|
-
Succeeds when the current token satisfies the provided
|
|
152
|
-
``TokenSpec`` (by type, exact text, or regex). On failure,
|
|
153
|
-
an informative ``Error`` is produced with location context.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
token_type: Expected enum type of the token.
|
|
157
|
-
text: Exact token text to match.
|
|
158
|
-
case_sensitive: Whether text matching is case sensitive.
|
|
159
|
-
regex: Regular expression pattern to match token text.
|
|
160
|
-
|
|
161
|
-
Returns:
|
|
162
|
-
Algebra[T, ParserState[T]]: An algebra yielding the matched token.
|
|
163
|
-
"""
|
|
164
137
|
spec = TokenSpec(token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
|
|
165
138
|
def token_run(state: ParserState[T], use_cache:bool) -> Generator[Incomplete[ParserState[T]],ParserState[T], Either[Any, Tuple[T, ParserState[T]]]]:
|
|
166
139
|
while True:
|
|
@@ -54,7 +54,7 @@ class Syntax(Generic[A, S]):
|
|
|
54
54
|
"""
|
|
55
55
|
The core signature of Syntax is take an Algebra Class and return an Algebra Instance.
|
|
56
56
|
"""
|
|
57
|
-
alg: Callable[[Type[Algebra[Any, Any]], Cache[Any
|
|
57
|
+
alg: Callable[[Type[Algebra[Any, Any]], Cache[Any]], Algebra[A, S]]
|
|
58
58
|
meta: Description = field(default_factory=Description, repr=False)
|
|
59
59
|
|
|
60
60
|
def algebra(self, name: str | MethodType | FunctionType, *args: Any, **kwargs: Any) -> Syntax[A, S]:
|
|
@@ -71,7 +71,7 @@ class Syntax(Generic[A, S]):
|
|
|
71
71
|
Returns:
|
|
72
72
|
A new Syntax reflecting the transformed algebra.
|
|
73
73
|
"""
|
|
74
|
-
def algebra_run(cls: Type[Algebra[Any, S]], cache: Cache[Any
|
|
74
|
+
def algebra_run(cls: Type[Algebra[Any, S]], cache: Cache[Any]) -> Algebra[Any, S]:
|
|
75
75
|
a = self(cls, cache)
|
|
76
76
|
if isinstance(name, str):
|
|
77
77
|
attr = getattr(a, name, None) or getattr(cls, name, None)
|
|
@@ -97,7 +97,7 @@ class Syntax(Generic[A, S]):
|
|
|
97
97
|
def as_(self, typ: Type[B]) -> B:
|
|
98
98
|
return cast(typ, self) # type: ignore
|
|
99
99
|
|
|
100
|
-
def __call__(self, alg: Type[Algebra[Any, Any]], cache: Cache[Any
|
|
100
|
+
def __call__(self, alg: Type[Algebra[Any, Any]], cache: Cache[Any]) -> Algebra[A, S]:
|
|
101
101
|
return self.alg(alg, cache)
|
|
102
102
|
|
|
103
103
|
|
|
@@ -562,10 +562,11 @@ def run(*,
|
|
|
562
562
|
|
|
563
563
|
|
|
564
564
|
def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
|
|
565
|
-
syntax: Optional[Syntax[A, S]] = None
|
|
566
565
|
algebra: Optional[Algebra[A, S]] = None
|
|
566
|
+
syntax: Optional[Syntax[A, S]] = None
|
|
567
|
+
previous_cls: Optional[Type[Algebra[Any, S]]] = None
|
|
567
568
|
def syntax_lazy_run(cls: Type[Algebra[Any, S]], cache: Cache) -> Algebra[A, S]:
|
|
568
|
-
nonlocal syntax,
|
|
569
|
+
nonlocal algebra, syntax, previous_cls
|
|
569
570
|
# print('==' * 20, 'Syntax.lazy.syntax_lazy_run', '==' * 20)
|
|
570
571
|
# print('thunk', thunk, id(thunk))
|
|
571
572
|
# print('syntax', syntax, id(syntax))
|
|
@@ -576,23 +577,13 @@ def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
|
|
|
576
577
|
if syntax is None:
|
|
577
578
|
raise SyncraftError("Lazy thunk did not resolve to a Syntax", offending=thunk, expect="a Syntax")
|
|
578
579
|
return syntax(cls, cache)
|
|
579
|
-
if algebra is None:
|
|
580
|
-
algebra = cls.lazy(algebra_lazy_f, cache=cache)
|
|
580
|
+
if algebra is None or (previous_cls is not None and previous_cls is not cls):
|
|
581
|
+
algebra = cls.lazy(algebra_lazy_f, cache=cache)
|
|
582
|
+
previous_cls = cls
|
|
581
583
|
return algebra
|
|
582
584
|
return Syntax(syntax_lazy_run).describe(name='lazy(?)', fixity='postfix')
|
|
583
585
|
|
|
584
586
|
|
|
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
587
|
|
|
597
588
|
|
|
598
589
|
def token(*,
|
|
@@ -7,13 +7,14 @@ from dataclasses import dataclass, replace, field
|
|
|
7
7
|
from syncraft.algebra import (
|
|
8
8
|
Algebra, Either, Right, Incomplete, Left, SyncraftError
|
|
9
9
|
)
|
|
10
|
-
from syncraft.ast import TokenSpec, ThenSpec, ManySpec, ChoiceSpec, LazySpec
|
|
10
|
+
from syncraft.ast import TokenSpec, ThenSpec, ManySpec, ChoiceSpec, LazySpec, ThenKind
|
|
11
11
|
from syncraft.parser import TokenType
|
|
12
12
|
from syncraft.constraint import Bindable, FrozenDict
|
|
13
13
|
|
|
14
14
|
import re
|
|
15
15
|
from syncraft.syntax import Syntax
|
|
16
|
-
from syncraft.cache import Cache
|
|
16
|
+
from syncraft.cache import Cache, RecursionError
|
|
17
|
+
from rich import print
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
S = TypeVar('S', bound=Bindable)
|
|
@@ -22,17 +23,10 @@ B = TypeVar('B')
|
|
|
22
23
|
SS = TypeVar('SS', bound=Hashable)
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
26
|
@dataclass(frozen=True)
|
|
31
27
|
class WalkerState(Bindable, Generic[SS]):
|
|
32
28
|
reducer: Optional[Callable[[Any, SS], SS]] = None
|
|
33
29
|
acc: Optional[SS] = None
|
|
34
|
-
visited: frozenset = field(default_factory=frozenset)
|
|
35
|
-
|
|
36
30
|
|
|
37
31
|
def reduce(self, value: Any) -> WalkerState[SS]:
|
|
38
32
|
if self.reducer:
|
|
@@ -41,11 +35,6 @@ class WalkerState(Bindable, Generic[SS]):
|
|
|
41
35
|
else:
|
|
42
36
|
return replace(self, acc=value)
|
|
43
37
|
|
|
44
|
-
def visit(self, key: Hashable) -> WalkerState[SS]:
|
|
45
|
-
return replace(self, visited=self.visited | {key})
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
38
|
|
|
50
39
|
@dataclass(frozen=True)
|
|
51
40
|
class Walker(Algebra[SS, WalkerState[SS]]):
|
|
@@ -55,24 +44,43 @@ class Walker(Algebra[SS, WalkerState[SS]]):
|
|
|
55
44
|
return WalkerState(reducer=reducer, acc=init)
|
|
56
45
|
|
|
57
46
|
|
|
47
|
+
|
|
58
48
|
@classmethod
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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)
|
|
70
75
|
match thunk_result:
|
|
71
76
|
case Right((value, from_thunk)):
|
|
72
|
-
data = LazySpec(value=value)
|
|
73
|
-
return Right((data, from_thunk.
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
|
|
76
84
|
|
|
77
85
|
|
|
78
86
|
def then_both(self, other: Algebra[Any, WalkerState[SS]]) -> Algebra[Any, WalkerState[SS]]:
|
|
@@ -83,16 +91,16 @@ class Walker(Algebra[SS, WalkerState[SS]]):
|
|
|
83
91
|
other_result = yield from other.run(from_left, use_cache)
|
|
84
92
|
match other_result:
|
|
85
93
|
case Right((result, from_right)):
|
|
86
|
-
data = ThenSpec(left=value, right=result)
|
|
94
|
+
data = ThenSpec(kind=ThenKind.BOTH, left=value, right=result)
|
|
87
95
|
return Right((data, from_right.reduce(data)))
|
|
88
96
|
raise SyncraftError("flat_map should always return a value or an error.", offending=self_result, expect=(Left, Right))
|
|
89
97
|
return self.__class__(then_run, name=self.name, cache=self.cache | other.cache)
|
|
90
98
|
|
|
91
99
|
def then_left(self, other: Algebra[Any, WalkerState[SS]]) -> Algebra[Any, WalkerState[SS]]:
|
|
92
|
-
return self.then_both(other)
|
|
100
|
+
return self.then_both(other).map(lambda t: replace(t, kind=ThenKind.LEFT))
|
|
93
101
|
|
|
94
102
|
def then_right(self, other: Algebra[Any, WalkerState[SS]]) -> Algebra[Any, WalkerState[SS]]:
|
|
95
|
-
return self.then_both(other)
|
|
103
|
+
return self.then_both(other).map(lambda t: replace(t, kind=ThenKind.RIGHT))
|
|
96
104
|
|
|
97
105
|
|
|
98
106
|
def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[Any, WalkerState[SS]]:
|
|
@@ -121,27 +129,19 @@ class Walker(Algebra[SS, WalkerState[SS]]):
|
|
|
121
129
|
raise SyncraftError("", offending=self)
|
|
122
130
|
return self.__class__(or_else_run, name=f"or_else({self.name} | {other.name})", cache=self.cache | other.cache)
|
|
123
131
|
|
|
124
|
-
@classmethod
|
|
125
|
-
def token(cls,
|
|
126
|
-
*,
|
|
127
|
-
cache: Cache,
|
|
128
|
-
token_type: Optional[TokenType] = None,
|
|
129
|
-
text: Optional[str] = None,
|
|
130
|
-
case_sensitive: bool = False,
|
|
131
|
-
regex: Optional[re.Pattern[str]] = None
|
|
132
|
-
)-> Algebra[Any, WalkerState[SS]]:
|
|
133
|
-
def token_run(input: WalkerState[SS], use_cache:bool) -> PyGenerator[Incomplete[WalkerState[SS]], WalkerState[SS], Either[Any, Tuple[Any, WalkerState[SS]]]]:
|
|
134
|
-
yield from ()
|
|
135
|
-
data = TokenSpec(token_type=token_type, text=text, regex=regex, case_sensitive=case_sensitive)
|
|
136
|
-
return Right((data, input.reduce(data)))
|
|
137
|
-
return cls(token_run, name=cls.__name__ + f'.token({token_type or text or regex})', cache=cache)
|
|
138
132
|
|
|
139
133
|
|
|
140
|
-
def walk(syntax: Syntax[Any, Any], reducer: Callable[[Any, Any], SS], init: SS)->
|
|
134
|
+
def walk(syntax: Syntax[Any, Any], reducer: Optional[Callable[[Any, Any], SS]] = None, init: Optional[SS] = None) -> Any:
|
|
141
135
|
from syncraft.syntax import run
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
146
143
|
else:
|
|
147
|
-
|
|
144
|
+
if s is not None:
|
|
145
|
+
return s.acc
|
|
146
|
+
else:
|
|
147
|
+
return None
|
|
@@ -6,17 +6,17 @@ from syncraft.ast import TokenSpec
|
|
|
6
6
|
|
|
7
7
|
def test_walk() -> None:
|
|
8
8
|
syntax = literal("test")
|
|
9
|
-
result = walk(syntax, lambda a, s: s + (a,), ())
|
|
10
|
-
assert
|
|
9
|
+
result, s = walk(syntax, lambda a, s: s + (a,), ())
|
|
10
|
+
assert s and s.acc == (TokenSpec.create(text='test', case_sensitive=True),)
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def test_walk_case_insensitive() -> None:
|
|
14
14
|
A = literal('a').many()
|
|
15
15
|
B = literal('b').many()
|
|
16
16
|
syntax = literal("if") >> (A | B) + literal('then')
|
|
17
|
-
result = walk(syntax, lambda a, s: s + (a,) if isinstance(a, TokenSpec) else s, ())
|
|
17
|
+
result, s = walk(syntax, lambda a, s: s + (a,) if isinstance(a, TokenSpec) else s, ())
|
|
18
18
|
print(result)
|
|
19
|
-
assert
|
|
19
|
+
assert s and s.acc == (
|
|
20
20
|
TokenSpec.create(text='if', case_sensitive=True),
|
|
21
21
|
TokenSpec.create(text='a', case_sensitive=True),
|
|
22
22
|
TokenSpec.create(text='b', case_sensitive=True),
|
syncraft-0.2.7/syncraft/cache.py
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Dict, TypeVar, Hashable, Generic, Callable, Any, Generator, overload, Literal
|
|
5
|
-
from weakref import WeakKeyDictionary
|
|
6
|
-
from syncraft.ast import SyncraftError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class RecursionError(SyncraftError):
|
|
10
|
-
def __init__(self, message: str, offending: Any, expect: Any = None, **kwargs: Any) -> None:
|
|
11
|
-
super().__init__(message, offending, expect, **kwargs)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@dataclass(frozen=True)
|
|
15
|
-
class InProgress:
|
|
16
|
-
_instance = None
|
|
17
|
-
def __new__(cls):
|
|
18
|
-
if cls._instance is None:
|
|
19
|
-
cls._instance = super(InProgress, cls).__new__(cls)
|
|
20
|
-
return cls._instance
|
|
21
|
-
def __str__(self)->str:
|
|
22
|
-
return self.__class__.__name__
|
|
23
|
-
def __repr__(self)->str:
|
|
24
|
-
return self.__str__()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Args = TypeVar('Args', bound=Hashable)
|
|
30
|
-
Ret = TypeVar('Ret')
|
|
31
|
-
|
|
32
|
-
@dataclass
|
|
33
|
-
class Cache(Generic[Args, Ret]):
|
|
34
|
-
cache: WeakKeyDictionary[Callable[..., Any], Dict[Args, Ret | InProgress]] = field(default_factory=WeakKeyDictionary)
|
|
35
|
-
|
|
36
|
-
def __contains__(self, f: Callable[..., Any]) -> bool:
|
|
37
|
-
return f in self.cache
|
|
38
|
-
|
|
39
|
-
def __repr__(self) -> str:
|
|
40
|
-
return f"Cache({({f.__name__: list(c.keys()) for f, c in self.cache.items()})})"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def __or__(self, other: Cache[Args, Any]) -> Cache[Args, Any]:
|
|
44
|
-
assert self.cache is other.cache, "There should be only one global cache"
|
|
45
|
-
if self.cache is other.cache:
|
|
46
|
-
return self
|
|
47
|
-
elif len(self.cache) == 0:
|
|
48
|
-
return other
|
|
49
|
-
elif len(other.cache) == 0:
|
|
50
|
-
return self
|
|
51
|
-
merged = Cache[Args, Ret]()
|
|
52
|
-
for f, c in self.cache.items():
|
|
53
|
-
merged.cache[f] = c.copy()
|
|
54
|
-
for f, c in other.cache.items():
|
|
55
|
-
merged.cache.setdefault(f, {}).update(c)
|
|
56
|
-
return merged
|
|
57
|
-
|
|
58
|
-
@overload
|
|
59
|
-
def _execute(self,
|
|
60
|
-
f: Callable[[Args, bool], Ret],
|
|
61
|
-
args: Args,
|
|
62
|
-
use_cache: bool,
|
|
63
|
-
is_gen: Literal[False]) -> Ret: ...
|
|
64
|
-
@overload
|
|
65
|
-
def _execute(self,
|
|
66
|
-
f: Callable[[Args, bool], Generator[Any, Any, Ret]],
|
|
67
|
-
args: Args,
|
|
68
|
-
use_cache: bool,
|
|
69
|
-
is_gen: Literal[True]) -> Generator[Any, Any, Ret]: ...
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _execute(self,
|
|
73
|
-
f: Callable[[Args, bool], Any],
|
|
74
|
-
args: Args,
|
|
75
|
-
use_cache:bool,
|
|
76
|
-
is_gen: bool
|
|
77
|
-
) -> Ret | Generator[Any, Any, Ret]:
|
|
78
|
-
if f not in self.cache:
|
|
79
|
-
self.cache.setdefault(f, dict())
|
|
80
|
-
c: Dict[Args, Ret | InProgress] = self.cache[f]
|
|
81
|
-
if args in c:
|
|
82
|
-
v = c[args]
|
|
83
|
-
if isinstance(v, InProgress):
|
|
84
|
-
raise RecursionError("Left-recursion detected in parser", offending=f, state=args)
|
|
85
|
-
else:
|
|
86
|
-
return v
|
|
87
|
-
try:
|
|
88
|
-
c[args] = InProgress()
|
|
89
|
-
if is_gen:
|
|
90
|
-
result = yield from f(args, use_cache)
|
|
91
|
-
else:
|
|
92
|
-
result = f(args, use_cache)
|
|
93
|
-
c[args] = result
|
|
94
|
-
if not use_cache:
|
|
95
|
-
c.pop(args, None)
|
|
96
|
-
return result
|
|
97
|
-
except Exception as e:
|
|
98
|
-
c.pop(args, None)
|
|
99
|
-
raise e
|
|
100
|
-
|
|
101
|
-
def gen(self,
|
|
102
|
-
f: Callable[[Args, bool], Generator[Any, Any, Ret]],
|
|
103
|
-
args: Args,
|
|
104
|
-
use_cache:bool) -> Generator[Any, Any, Ret]:
|
|
105
|
-
return (yield from self._execute(f, args, use_cache, is_gen=True))
|
|
106
|
-
|
|
107
|
-
def call(self,
|
|
108
|
-
f: Callable[[Args, bool], Ret],
|
|
109
|
-
args: Args,
|
|
110
|
-
use_cache:bool) -> Ret:
|
|
111
|
-
return self._execute(f, args, use_cache, is_gen=False)
|
|
112
|
-
|
|
113
|
-
|
|
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
|