syncraft 0.1.19__py3-none-any.whl → 0.1.21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of syncraft might be problematic. Click here for more details.

syncraft/algebra.py CHANGED
@@ -1,163 +1,136 @@
1
- """
2
- We want to parse a token stream into an AST, and then generate a new token stream from that AST.
3
- The generation should be a dual to the parsing. By 'dual' we mean that the generation algebra should be
4
- as close as possible to the parsing algebra. The closest algebra to the parsing algebra is the parsing
5
- algebra itself.
6
-
7
- Given:
8
- AST = Syntax(Parser)(ParserState([Token, ...]))
9
- AST =?= Syntax(Parser)(GenState(AST))
10
-
11
- where =?= means the LHS and RHS induce the same text output, e.g. the same token stream
12
- inspite of the token metadata, token types, and/or potentially different structure of the AST.
13
-
14
- With the above setting, Generator as a dual to Parser, can reuse most of the parsing combinator, the
15
- change needed is to introduce randomness in the generation process, e.g. to generate a random variable name, etc.
16
-
17
- [Token, ...] == Syntax(Generator)(GenState(AST))
18
-
19
- """
20
-
21
-
22
1
  from __future__ import annotations
23
-
24
2
  from typing import (
25
3
  Optional, List, Any, TypeVar, Generic, Callable, Tuple, cast,
26
- Dict, Type, ClassVar, Hashable,
27
- Mapping, Iterator
4
+ Dict, Type, ClassVar, Hashable
28
5
  )
29
6
 
30
7
  import traceback
31
- from dataclasses import dataclass, fields, replace, field
32
- from functools import cached_property
8
+ from dataclasses import dataclass, replace
33
9
  from weakref import WeakKeyDictionary
34
- from abc import ABC, abstractmethod
10
+ from abc import ABC
35
11
  from enum import Enum
12
+ from functools import reduce
36
13
 
37
14
 
38
-
39
-
40
- class Insptectable(ABC):
41
- @abstractmethod
42
- def to_string(self, interested: Callable[[Any], bool]) -> Optional[str]:
43
- raise NotImplementedError("Subclasses must implement to_string")
44
-
45
- @cached_property
46
- def _string(self)->Optional[str]:
47
- return self.to_string(lambda _: True)
48
-
49
- def __repr__(self) -> str:
50
- return self._string or self.__class__.__name__
51
- def __str__(self) -> str:
52
- return self._string or self.__class__.__name__
53
-
54
-
55
- A = TypeVar('A') # Result type
56
- B = TypeVar('B') # Result type for mapping
57
-
58
- S = TypeVar('S') # State type for the Algebra
59
-
60
-
61
-
62
- class FrozenDict(Generic[A]):
63
- def __init__(self, items: Mapping[str, A]):
64
- for k, v in items.items():
65
- if not isinstance(k, Hashable) or not isinstance(v, Hashable):
66
- raise TypeError(f"Metadata key or value not hashable: {k} = {v}")
67
- self._items = tuple(sorted(items.items()))
68
-
69
- def __getitem__(self, key: str) -> A:
70
- return dict(self._items)[key]
71
-
72
- def __contains__(self, key: str) -> bool:
73
- return key in dict(self._items)
74
-
75
- def items(self) -> Iterator[tuple[str, A]]:
76
- return iter(self._items)
77
-
78
- def to_dict(self) -> dict[str, A]:
79
- return dict(self._items)
80
-
81
- def __hash__(self) -> int:
82
- return hash(self._items)
83
-
84
- def __eq__(self, other: object) -> bool:
85
- return isinstance(other, FrozenDict) and self._items == other._items
86
-
87
- def __repr__(self) -> str:
88
- return f"FrozenDict({dict(self._items)})"
89
-
15
+ A = TypeVar('A')
16
+ B = TypeVar('B')
17
+ C = TypeVar('C')
18
+ S = TypeVar('S')
90
19
 
91
20
 
92
21
  @dataclass(frozen=True)
93
- class Lens(Generic[S, A]):
94
- get: Callable[[S], A]
95
- set: Callable[[S, A], S]
96
-
97
- def modify(self, source: S, f: Callable[[A], A]) -> S:
98
- return self.set(source, f(self.get(source)))
22
+ class Bimap(Generic[S, A, B]):
23
+ forward: Callable[[S, A], Tuple[S, B]]
24
+ inverse: Callable[[S, B], Tuple[S, A]]
25
+ def __rshift__(self, other: Bimap[S, B, C]) -> Bimap[S, A, C]:
26
+ def fwd(s: S, a: A) -> Tuple[S, C]:
27
+ s1, b = self.forward(s, a)
28
+ return other.forward(s1, b)
29
+ def inv(s: S, c: C) -> Tuple[S, A]:
30
+ s1, b = other.inverse(s, c)
31
+ return self.inverse(s1, b)
32
+ return Bimap(
33
+ forward=fwd,
34
+ inverse=inv
35
+ )
36
+ @staticmethod
37
+ def identity()->Bimap[S, A, A]:
38
+ return Bimap(
39
+ forward=lambda s, x: (s, x),
40
+ inverse=lambda s, y: (s, y)
41
+ )
42
+
43
+ @staticmethod
44
+ def when(condition: Callable[..., bool],
45
+ then: Bimap[S, A, B],
46
+ otherwise: Optional[Bimap[S, A, B]] = None) -> Callable[..., Bimap[S, A, B]]:
47
+ def _when(*args:Any, **kwargs:Any) -> Bimap[S, A, B]:
48
+ return then if condition(*args, **kwargs) else (otherwise or Bimap.identity())
49
+ return _when
99
50
 
100
- def bimap(self, ff: Callable[[A], B], bf: Callable[[B], A]) -> Lens[S, B]:
101
- def getf(data: S) -> B:
102
- return ff(self.get(data))
103
-
104
- def setf(data: S, value: B) -> S:
105
- return self.set(data, bf(value))
106
-
107
- return Lens(get=getf, set=setf)
108
51
 
109
- def __truediv__(self, other: Lens[A, B]) -> Lens[S, B]:
110
- def get_composed(obj: S) -> B:
111
- return other.get(self.get(obj))
112
- def set_composed(obj: S, value: B) -> S:
113
- return self.set(obj, other.set(self.get(obj), value))
114
- return Lens(get=get_composed, set=set_composed)
115
52
 
116
- def __rtruediv__(self, other: Lens[B, S])->Lens[B, A]:
117
- return other.__truediv__(self)
118
-
119
53
  class StructuralResult:
120
- def bimap(self, ctx: Any)->Tuple[Any, Callable[[Any], StructuralResult]]:
121
- return (self, lambda x: self)
122
-
54
+ def bimap(self,
55
+ before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
56
+ after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
57
+ ) -> Bimap[Any, Any, Any]:
58
+ return Bimap.identity()
123
59
 
124
60
  @dataclass(frozen=True)
125
61
  class NamedResult(Generic[A], StructuralResult):
126
62
  name: str
127
63
  value: A
128
- def bimap(self, ctx: Any)->Tuple[NamedResult[Any], Callable[[NamedResult[Any]], StructuralResult]]:
129
- value, backward = self.value.bimap(ctx) if isinstance(self.value, StructuralResult) else (self.value, lambda x: x)
130
- def named_back(data: Any)->NamedResult[Any]:
131
- v = backward(data)
132
- if isinstance(v, NamedResult):
133
- return replace(v, name=self.name)
134
- else:
135
- return NamedResult(name=self.name, value=v)
136
- return NamedResult(self.name, value), named_back
137
-
64
+ def bimap(self,
65
+ before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
66
+ after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
67
+ ) -> Bimap[Any, NamedResult[A], NamedResult[Any]]:
68
+ inner_b = self.value.bimap(before, after) if isinstance(self.value, StructuralResult) else before(self.value) >> after(self.value)
69
+ def fwd(s: S, a: NamedResult[A])-> Tuple[S, NamedResult[Any]]:
70
+ assert a == self, f"Expected {self}, got {a}"
71
+ inner_s, inner_v = inner_b.forward(s, a.value)
72
+ return (inner_s, replace(a, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, inner_v)
73
+
74
+ def inv(s: S, a: NamedResult[Any]) -> Tuple[S, NamedResult[A]]:
75
+ assert isinstance(a, NamedResult), f"Expected NamedResult, got {type(a)}"
76
+ inner_s, inner_v = inner_b.inverse(s, a.value)
77
+ return (inner_s, replace(self, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, replace(self, value=inner_v.value))
78
+ ret: Bimap[Any, Any, Any] = Bimap(
79
+ forward=fwd,
80
+ inverse=inv
81
+ )
82
+ return before(self) >> ret >> after(self)
138
83
  @dataclass(eq=True, frozen=True)
139
84
  class ManyResult(Generic[A], StructuralResult):
140
85
  value: Tuple[A, ...]
141
- def bimap(self, ctx: Any)->Tuple[List[Any], Callable[[List[Any]], StructuralResult]]:
142
- transformed = [v.bimap(ctx) if isinstance(v, StructuralResult) else (v, lambda x: x) for v in self.value]
143
- backmaps = [b for (_, b) in transformed]
144
- ret = [a for (a, _) in transformed]
145
- def backward(data: List[Any]) -> StructuralResult:
146
- if len(data) != len(transformed):
147
- raise ValueError("Incompatible data length")
148
- return ManyResult(value=tuple([backmaps[i](x) for i, x in enumerate(data)]))
149
- return ret, lambda data: backward(data)
150
-
86
+ def bimap(self,
87
+ before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
88
+ after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
89
+ ) -> Bimap[Any, ManyResult[A], List[A]]:
90
+ # We don't allow zero length ManyResult e.g. many(at_least >= 1) at the syntax level
91
+ # so inner_b has at least 1 element
92
+ assert len(self.value) > 0, "ManyResult must have at least one element"
93
+ inner_b = [v.bimap(before, after) if isinstance(v, StructuralResult) else before(v) >> after(v) for v in self.value]
94
+ def fwd(s: Any, a: ManyResult[A]) -> Tuple[Any, List[A]]:
95
+ assert a == self, f"Expected {self}, got {a}"
96
+ return s, [inner_b[i].forward(s, v)[1] for i, v in enumerate(a.value)]
97
+
98
+ def inv(s: Any, a: List[A]) -> Tuple[Any, ManyResult[A]]:
99
+ assert isinstance(a, list), f"Expected list, got {type(a)}"
100
+ ret = [inner_b[i].inverse(s, v)[1] for i, v in enumerate(a)]
101
+ if len(ret) <= len(inner_b):
102
+ return s, ManyResult(value=tuple(ret))
103
+ else:
104
+ extra = [inner_b[-1].inverse(s, v)[1] for v in a[len(inner_b):]]
105
+ return s, ManyResult(value=tuple(ret + extra))
151
106
 
107
+ ret = Bimap(
108
+ forward=fwd,
109
+ inverse=inv
110
+ )
111
+ return before(self) >> ret >> after(self)
152
112
 
153
113
  @dataclass(eq=True, frozen=True)
154
114
  class OrResult(Generic[A], StructuralResult):
155
115
  value: A
156
- def bimap(self, ctx: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
157
- value, backward = self.value.bimap(ctx) if isinstance(self.value, StructuralResult) else (self.value, lambda x: x)
158
- return value, lambda data: OrResult(value=backward(data))
159
-
160
-
116
+ def bimap(self,
117
+ before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
118
+ after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
119
+ ) -> Bimap[Any, OrResult[A], Any]:
120
+ inner_b = self.value.bimap(before, after) if isinstance(self.value, StructuralResult) else before(self.value) >> after(self.value)
121
+ def fwd(s: Any, a: OrResult[A]) -> Tuple[Any, Any]:
122
+ assert a == self, f"Expected {self}, got {a}"
123
+ return inner_b.forward(s, a.value)
124
+
125
+ def inv(s: Any, a: Any) -> Tuple[Any, OrResult[A]]:
126
+ inner_s, inner_v = inner_b.inverse(s, a)
127
+ return inner_s, OrResult(value=inner_v)
128
+
129
+ ret = Bimap(
130
+ forward=fwd,
131
+ inverse=inv
132
+ )
133
+ return before(self) >> ret >> after(self)
161
134
  class ThenKind(Enum):
162
135
  BOTH = '+'
163
136
  LEFT = '//'
@@ -168,49 +141,64 @@ class ThenResult(Generic[A, B], StructuralResult):
168
141
  kind: ThenKind
169
142
  left: A
170
143
  right: B
171
- def bimap(self, ctx: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
172
- def branch(b: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
173
- return b.bimap(ctx) if isinstance(b, StructuralResult) else (b, lambda x: x)
174
- match self.kind:
175
- case ThenKind.BOTH:
176
- left_value, left_bmap = branch(self.left)
177
- right_value, right_bmap = branch(self.right)
178
- def backward(x: Tuple[Any, Any]) -> StructuralResult:
179
- return ThenResult(self.kind, left_bmap(x[0]), right_bmap(x[1]))
180
- x, y = ThenResult.flat((left_value, right_value))
181
- return x, lambda data: backward(y(data))
182
- case ThenKind.LEFT:
183
- left_value, left_bmap = branch(self.left)
184
- return left_value, lambda data: ThenResult(self.kind, left_bmap(data), self.right)
185
- case ThenKind.RIGHT:
186
- right_value, right_bmap = branch(self.right)
187
- return right_value, lambda data: ThenResult(self.kind, self.left, right_bmap(data))
188
- @staticmethod
189
- def flat(array: Tuple[Any, Any]) -> Tuple[Tuple[Any, ...], Callable[[Tuple[Any, ...]], Tuple[Any, Any]]]:
190
- index: Dict[int, int] = {}
191
- ret: List[Any] = []
192
- for e in array:
193
- if isinstance(e, tuple):
194
- index[len(ret)] = len(e)
195
- ret.extend(e)
196
- else:
197
- ret.append(e)
198
- def backward(data: Tuple[Any, ...]) -> Tuple[Any, Any]:
199
- tmp: List[Any] = []
200
- skip: int = 0
201
- for i, e in enumerate(data):
202
- if skip <= 0:
203
- if i in index:
204
- tmp.append(tuple(data[i:i + index[i]]))
205
- skip = index[i] - 1
206
- else:
207
- tmp.append(e)
208
- else:
209
- skip -= 1
210
- return tuple(tmp)
211
- return tuple(ret), backward
212
-
213
-
144
+ def arity(self)->int:
145
+ if self.kind == ThenKind.LEFT:
146
+ return self.left.arity() if isinstance(self.left, ThenResult) else 1
147
+ elif self.kind == ThenKind.RIGHT:
148
+ return self.right.arity() if isinstance(self.right, ThenResult) else 1
149
+ elif self.kind == ThenKind.BOTH:
150
+ left_arity = self.left.arity() if isinstance(self.left, ThenResult) else 1
151
+ right_arity = self.right.arity() if isinstance(self.right, ThenResult) else 1
152
+ return left_arity + right_arity
153
+ else:
154
+ return 1
155
+
156
+ def bimap(self,
157
+ before: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity(),
158
+ after: Callable[[Any], Bimap[Any, Any, Any]] = lambda _: Bimap.identity()
159
+ ) -> Bimap[Any, ThenResult[A, B], Tuple[Any, ...] | Any]:
160
+ kind = self.kind
161
+ lb = self.left.bimap(before, after) if isinstance(self.left, StructuralResult) else before(self.left) >> after(self.left)
162
+ rb = self.right.bimap(before, after) if isinstance(self.right, StructuralResult) else before(self.right) >> after(self.right)
163
+ left_size = self.left.arity() if isinstance(self.left, ThenResult) else 1
164
+ right_size = self.right.arity() if isinstance(self.right, ThenResult) else 1
165
+ def fwd(s : S, a : ThenResult[A, B]) -> Tuple[S, Tuple[Any, ...] | Any]:
166
+ assert a == self, f"Expected {self}, got {a}"
167
+ match kind:
168
+ case ThenKind.LEFT:
169
+ return lb.forward(s, a.left)
170
+ case ThenKind.RIGHT:
171
+ return rb.forward(s, a.right)
172
+ case ThenKind.BOTH:
173
+ s1, left_v = lb.forward(s, a.left)
174
+ s2, right_v = rb.forward(s1, a.right)
175
+ left_v = (left_v,) if not isinstance(a.left, ThenResult) else left_v
176
+ right_v = (right_v,) if not isinstance(a.right, ThenResult) else right_v
177
+ return s2, left_v + right_v
178
+
179
+ def inv(s: S, b: Tuple[Any, ...] | Any) -> Tuple[S, ThenResult[A, B]]:
180
+ match kind:
181
+ case ThenKind.LEFT:
182
+ s1, lv = lb.inverse(s, b)
183
+ return s1, replace(self, left=lv)
184
+ case ThenKind.RIGHT:
185
+ s1, rv = rb.inverse(s, b)
186
+ return s1, replace(self, right=rv)
187
+ case ThenKind.BOTH:
188
+ lraw = b[:left_size]
189
+ rraw = b[left_size:left_size + right_size]
190
+ lraw = lraw[0] if left_size == 1 else lraw
191
+ rraw = rraw[0] if right_size == 1 else rraw
192
+ s1, lv = lb.inverse(s, lraw)
193
+ s2, rv = rb.inverse(s1, rraw)
194
+ return s2, replace(self, left=lv, right=rv)
195
+
196
+ ret: Bimap[Any, Any, Any] = Bimap(
197
+ forward=fwd,
198
+ inverse=inv
199
+ )
200
+ return before(self) >> ret >> after(self)
201
+
214
202
  InProgress = object() # Marker for in-progress state, used to prevent re-entrance in recursive calls
215
203
  L = TypeVar('L') # Left type for combined results
216
204
  R = TypeVar('R') # Right type for combined results
@@ -233,7 +221,7 @@ class Right(Either[L, R]):
233
221
 
234
222
 
235
223
  @dataclass(frozen=True)
236
- class Error(Insptectable):
224
+ class Error:
237
225
  this: Any
238
226
  message: Optional[str] = None
239
227
  error: Optional[Any] = None
@@ -254,59 +242,9 @@ class Error(Insptectable):
254
242
  state=state,
255
243
  previous=self
256
244
  )
257
-
258
245
 
259
246
 
260
247
 
261
- def to_list(self, interested: Callable[[Any], bool]) -> List[Dict[str, str]]:
262
-
263
- def to_dict() -> Dict[str, str]:
264
- data: Dict[str, str] = {}
265
- for f in fields(self):
266
- value = getattr(self, f.name)
267
- if isinstance(value, Error):
268
- # self.previous
269
- pass
270
- elif isinstance(value, Insptectable):
271
- # self.this
272
- def inst(x: Any) -> bool:
273
- return x in self.algebras
274
- s = value.to_string(inst)
275
- data[f.name] = s if s is not None else repr(value)
276
- elif value is not None:
277
- # self.committed, self.message, self.expect, self.exception
278
- data[f.name] = repr(value)
279
- return data
280
- ret = []
281
- tmp : None | Error = self
282
- while tmp is not None:
283
- ret.append(to_dict())
284
- tmp = tmp.previous
285
- return ret
286
-
287
- def find(self, predicate: Callable[[Error], bool]) -> Optional[Error]:
288
- if predicate(self):
289
- return self
290
- if self.previous is not None:
291
- return self.previous.find(predicate)
292
- return None
293
-
294
- def to_string(self, interested: Callable[[Any], bool])->str:
295
- lst = self.to_list(interested)
296
- root, leaf = lst[0], lst[-1]
297
- root_fields = ',\n '.join([f"{key}: {value}" for key, value in root.items() if value is not None])
298
- leaf_fields = ',\n '.join([f"{key}: {value}" for key, value in leaf.items() if value is not None])
299
-
300
- return f"{self.__class__.__name__}: ROOT\n"\
301
- f" {root_fields}\n"\
302
- f"\u25cf \u25cf \u25cf LEAF\n"\
303
- f" {leaf_fields}\n"
304
-
305
-
306
- @cached_property
307
- def algebras(self) -> List[Any]:
308
- return [self.this] + (self.previous.algebras if self.previous is not None else [])
309
-
310
248
  @dataclass(frozen=True)
311
249
  class Algebra(ABC, Generic[A, S]):
312
250
  ######################################################## shared among all subclasses ########################################################
syncraft/ast.py CHANGED
@@ -3,12 +3,11 @@
3
3
  from __future__ import annotations
4
4
  import re
5
5
  from typing import (
6
- Optional, Any, TypeVar, Tuple, runtime_checkable, Dict,
6
+ Optional, Any, TypeVar, Tuple, runtime_checkable,
7
7
  Protocol, Generic, Callable, Union, cast
8
8
  )
9
9
  from syncraft.algebra import (
10
- OrResult,ThenResult, ManyResult, ThenKind,NamedResult, StructuralResult,
11
- Lens
10
+ OrResult,ThenResult, ManyResult, ThenKind,NamedResult, StructuralResult
12
11
  )
13
12
  from dataclasses import dataclass, replace, is_dataclass, asdict
14
13
  from enum import Enum
@@ -67,13 +66,17 @@ class AST(Generic[T]):
67
66
  pruned: bool = False
68
67
  parent: Optional[AST[T]] = None
69
68
 
70
-
71
- def bimap(self, ctx: Any) -> Tuple[Any, Callable[[Any], AST[T]]]:
72
- value, backward = self.focus.bimap(ctx) if isinstance(self.focus, StructuralResult) else (self.focus, lambda x: x)
73
- def back2ast(data: Any) -> AST[T]:
74
- return replace(self, focus=backward(data)) # type: ignore
75
- return value, back2ast
76
-
69
+ def bimap(self)->Tuple[Any, Callable[[Any], AST[T]]]:
70
+ if isinstance(self.focus, StructuralResult):
71
+ b = self.focus.bimap()
72
+ s, v = b.forward(None, self.focus)
73
+ def inverse(data: Any) -> AST[T]:
74
+ s1, v1 = b.inverse(None, data)
75
+ return replace(self, focus=v1)
76
+ return v, inverse
77
+ else:
78
+ return self.focus, lambda x: replace(self, focus=x)
79
+
77
80
  def wrapper(self)-> Callable[[Any], Any]:
78
81
  if isinstance(self.focus, NamedResult):
79
82
  focus = cast(NamedResult[Any], self.focus)
syncraft/finder.py ADDED
@@ -0,0 +1,149 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import (
4
+ Any, Tuple, Optional, Callable,
5
+ List, Generator as YieldGen
6
+ )
7
+ from dataclasses import dataclass
8
+ from syncraft.algebra import (
9
+ Algebra, Either, Left, Right, Error,
10
+ OrResult, ManyResult, ThenResult, NamedResult
11
+ )
12
+
13
+ from syncraft.ast import T, ParseResult, Token, AST
14
+ from syncraft.generator import GenState, TokenGen, B
15
+ from sqlglot import TokenType
16
+ from syncraft.syntax import Syntax
17
+ import re
18
+
19
+ @dataclass(frozen=True)
20
+ class Finder(Algebra[ParseResult[T], GenState[T]]):
21
+ def flat_map(self, f: Callable[[ParseResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
22
+ def flat_map_run(original: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
23
+ wrapper = original.wrapper()
24
+ input = original if not original.is_named else original.down(0) # If the input is named, we need to go down to the first child
25
+ try:
26
+ lft = input.left()
27
+ match self.run(lft, use_cache=use_cache):
28
+ case Left(error):
29
+ return Left(error)
30
+ case Right((value, next_input)):
31
+ r = input.right()
32
+ match f(value).run(r, use_cache):
33
+ case Left(e):
34
+ return Left(e)
35
+ case Right((result, next_input)):
36
+ return Right((wrapper(result), next_input))
37
+ raise ValueError("flat_map should always return a value or an error.")
38
+ except Exception as e:
39
+ return Left(Error(
40
+ message=str(e),
41
+ this=self,
42
+ state=original,
43
+ error=e
44
+ ))
45
+ return Finder(run_f = flat_map_run, name=self.name) # type: ignore
46
+
47
+
48
+ def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[ParseResult[T]], GenState[T]]:
49
+ assert at_least > 0, "at_least must be greater than 0"
50
+ assert at_most is None or at_least <= at_most, "at_least must be less than or equal to at_most"
51
+ def many_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ManyResult[ParseResult[T]], GenState[T]]]:
52
+ wrapper = input.wrapper()
53
+ input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
54
+ ret = []
55
+ for index in range(input.how_many):
56
+ match self.run(input.down(index), use_cache):
57
+ case Right((value, next_input)):
58
+ ret.append(value)
59
+ if at_most is not None and len(ret) > at_most:
60
+ return Left(Error(
61
+ message=f"Expected at most {at_most} matches, got {len(ret)}",
62
+ this=self,
63
+ state=input.down(index)
64
+ ))
65
+ case Left(_):
66
+ pass
67
+ if len(ret) < at_least:
68
+ return Left(Error(
69
+ message=f"Expected at least {at_least} matches, got {len(ret)}",
70
+ this=self,
71
+ state=input.down(index)
72
+ ))
73
+ return Right((wrapper(ManyResult(tuple(ret))), input))
74
+ return self.__class__(many_run, name=f"many({self.name})") # type: ignore
75
+
76
+
77
+ def or_else(self, # type: ignore
78
+ other: Algebra[ParseResult[T], GenState[T]]
79
+ ) -> Algebra[OrResult[ParseResult[T]], GenState[T]]:
80
+ def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[OrResult[ParseResult[T]], GenState[T]]]:
81
+ wrapper = input.wrapper()
82
+ input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
83
+ match self.run(input.down(0), use_cache):
84
+ case Right((value, next_input)):
85
+ return Right((wrapper(OrResult(value)), next_input))
86
+ case Left(error):
87
+ match other.run(input.down(0), use_cache):
88
+ case Right((value, next_input)):
89
+ return Right((wrapper(OrResult(value)), next_input))
90
+ case Left(error):
91
+ return Left(error)
92
+ raise ValueError("or_else should always return a value or an error.")
93
+ return self.__class__(or_else_run, name=f"or_else({self.name} | {other.name})") # type: ignore
94
+
95
+ @classmethod
96
+ def token(cls,
97
+ token_type: Optional[TokenType] = None,
98
+ text: Optional[str] = None,
99
+ case_sensitive: bool = False,
100
+ regex: Optional[re.Pattern[str]] = None
101
+ )-> Algebra[ParseResult[T], GenState[T]]:
102
+ gen = TokenGen(token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
103
+ lazy_self: Algebra[ParseResult[T], GenState[T]]
104
+ def token_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ParseResult[Token], GenState[T]]]:
105
+ wrapper = input.wrapper()
106
+ input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
107
+ current = input.focus
108
+ if not isinstance(current, Token) or not gen.is_valid(current):
109
+ return Left(Error(None,
110
+ message=f"Expected a Token, but got {type(current)}.",
111
+ state=input))
112
+ return Right((wrapper(current), input))
113
+ lazy_self = cls(token_run, name=cls.__name__ + f'.token({token_type or text or regex})') # type: ignore
114
+ return lazy_self
115
+
116
+ @classmethod
117
+ def anything(cls)->Algebra[ParseResult[T], GenState[T]]:
118
+ def anything_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ParseResult[T], GenState[T]]]:
119
+ wrapper = input.wrapper()
120
+ return Right((wrapper(input.focus), input))
121
+ return cls(anything_run, name=cls.__name__ + '.anything()')
122
+
123
+
124
+
125
+ anything = Syntax(lambda cls: cls.factory('anything')).describe(name="anything", fixity='infix')
126
+
127
+ def matches(syntax: Syntax[Any, Any], data: AST[Any])-> bool:
128
+ gen = syntax(Finder)
129
+ state = GenState.from_ast(data)
130
+ result = gen.run(state, use_cache=True)
131
+ return isinstance(result, Right)
132
+
133
+
134
+ def find(syntax: Syntax[Any, Any], data: AST[Any]) -> YieldGen[AST[Any], None, None]:
135
+ if matches(syntax, data):
136
+ yield data
137
+ match data.focus:
138
+ case ThenResult(left = left, right=right):
139
+ yield from find(syntax, AST(left))
140
+ yield from find(syntax, AST(right))
141
+ case ManyResult(value = value):
142
+ for e in value:
143
+ yield from find(syntax, AST(e))
144
+ case NamedResult(value=value):
145
+ yield from find(syntax, AST(value))
146
+ case OrResult(value=value):
147
+ yield from find(syntax, AST(value))
148
+ case _:
149
+ pass
syncraft/generator.py CHANGED
@@ -1,17 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from typing import (
4
- Any, TypeVar, Tuple, Optional, Callable, Generic, Union,
5
- List, Generator as YieldGen
4
+ Any, TypeVar, Tuple, Optional, Callable, Generic,
5
+ List,
6
6
  )
7
7
  from functools import cached_property
8
8
  from dataclasses import dataclass, replace
9
9
  from syncraft.algebra import (
10
- Algebra, ThenResult, Either, Left, Right, Error, Insptectable,
11
- OrResult, ManyResult, NamedResult
10
+ Algebra, Either, Left, Right, Error,
11
+ OrResult, ManyResult
12
12
  )
13
13
 
14
- from syncraft.ast import TokenProtocol, ParseResult, AST, Token, TokenSpec
14
+ from syncraft.ast import T, ParseResult, AST, Token, TokenSpec
15
15
  from syncraft.syntax import Syntax
16
16
  from sqlglot import TokenType
17
17
  import re
@@ -20,18 +20,10 @@ from functools import lru_cache
20
20
  import random
21
21
 
22
22
  B = TypeVar('B')
23
- T = TypeVar('T', bound=TokenProtocol)
24
23
 
25
- GenResult = Union[
26
- ThenResult['GenResult[T]', 'GenResult[T]'],
27
- ManyResult['GenResult[T]'],
28
- OrResult['GenResult[T]'],
29
-
30
- T
31
- ]
32
24
 
33
25
  @dataclass(frozen=True)
34
- class GenState(Generic[T], Insptectable):
26
+ class GenState(Generic[T]):
35
27
  ast: Optional[AST[T]]
36
28
  seed: int
37
29
 
@@ -150,8 +142,8 @@ class TokenGen(TokenSpec):
150
142
 
151
143
 
152
144
  @dataclass(frozen=True)
153
- class Generator(Algebra[GenResult[T], GenState[T]]):
154
- def flat_map(self, f: Callable[[GenResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
145
+ class Generator(Algebra[ParseResult[T], GenState[T]]):
146
+ def flat_map(self, f: Callable[[ParseResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
155
147
  def flat_map_run(original: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
156
148
  wrapper = original.wrapper()
157
149
  input = original if not original.is_named else original.down(0) # If the input is named, we need to go down to the first child
@@ -178,10 +170,10 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
178
170
  return Generator(run_f = flat_map_run, name=self.name) # type: ignore
179
171
 
180
172
 
181
- def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[GenResult[T]], GenState[T]]:
173
+ def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[ParseResult[T]], GenState[T]]:
182
174
  assert at_least > 0, "at_least must be greater than 0"
183
175
  assert at_most is None or at_least <= at_most, "at_least must be less than or equal to at_most"
184
- def many_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ManyResult[GenResult[T]], GenState[T]]]:
176
+ def many_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ManyResult[ParseResult[T]], GenState[T]]]:
185
177
  wrapper = input.wrapper()
186
178
  input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
187
179
  if input.pruned:
@@ -221,9 +213,9 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
221
213
 
222
214
 
223
215
  def or_else(self, # type: ignore
224
- other: Algebra[GenResult[T], GenState[T]]
225
- ) -> Algebra[OrResult[GenResult[T]], GenState[T]]:
226
- def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[OrResult[GenResult[T]], GenState[T]]]:
216
+ other: Algebra[ParseResult[T], GenState[T]]
217
+ ) -> Algebra[OrResult[ParseResult[T]], GenState[T]]:
218
+ def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[OrResult[ParseResult[T]], GenState[T]]]:
227
219
  wrapper = input.wrapper()
228
220
  input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
229
221
  if input.pruned:
@@ -252,10 +244,10 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
252
244
  text: Optional[str] = None,
253
245
  case_sensitive: bool = False,
254
246
  regex: Optional[re.Pattern[str]] = None
255
- )-> Algebra[GenResult[T], GenState[T]]:
247
+ )-> Algebra[ParseResult[T], GenState[T]]:
256
248
  gen = TokenGen(token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
257
- lazy_self: Algebra[GenResult[T], GenState[T]]
258
- def token_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[GenResult[Token], GenState[T]]]:
249
+ lazy_self: Algebra[ParseResult[T], GenState[T]]
250
+ def token_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ParseResult[Token], GenState[T]]]:
259
251
  wrapper = input.wrapper()
260
252
  input = input if not input.is_named else input.down(0) # If the input is named, we need to go down to the first child
261
253
  if input.pruned:
@@ -282,27 +274,4 @@ def generate(syntax: Syntax[Any, Any], data: Optional[AST[Any]] = None, seed: in
282
274
  return result.value
283
275
 
284
276
 
285
- def matches(syntax: Syntax[Any, Any], data: AST[Any])-> bool:
286
- gen = syntax(Generator)
287
- state = GenState.from_ast(data)
288
- result = gen.run(state, use_cache=True)
289
- return isinstance(result, Right)
290
-
291
-
292
- def search(syntax: Syntax[Any, Any], data: AST[Any]) -> YieldGen[AST[Any], None, None]:
293
- if matches(syntax, data):
294
- yield data
295
- match data.focus:
296
- case ThenResult(left = left, right=right):
297
- yield from search(syntax, AST(left))
298
- yield from search(syntax, AST(right))
299
- case ManyResult(value = value):
300
- for e in value:
301
- yield from search(syntax, AST(e))
302
- case NamedResult(value=value):
303
- yield from search(syntax, AST(value))
304
- case OrResult(value=value):
305
- yield from search(syntax, AST(value))
306
- case _:
307
- pass
308
277
 
syncraft/parser.py CHANGED
@@ -6,7 +6,7 @@ from typing import (
6
6
  Generic, Callable
7
7
  )
8
8
  from syncraft.algebra import (
9
- Either, Left, Right, Error, Insptectable, Algebra
9
+ Either, Left, Right, Error, Algebra
10
10
  )
11
11
  from dataclasses import dataclass, field, replace
12
12
  from enum import Enum
@@ -16,15 +16,8 @@ from syncraft.syntax import Syntax
16
16
  from syncraft.ast import Token, TokenSpec, AST, T
17
17
 
18
18
 
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
19
  @dataclass(frozen=True)
27
- class ParserState(Generic[T], Insptectable):
20
+ class ParserState(Generic[T]):
28
21
  input: Tuple[T, ...] = field(default_factory=tuple)
29
22
  index: int = 0
30
23
 
@@ -40,13 +33,6 @@ class ParserState(Generic[T], Insptectable):
40
33
  def after(self, length: Optional[int] = 5)->str:
41
34
  length = min(length, len(self.input) - self.index) if length is not None else len(self.input) - self.index
42
35
  return " ".join(token.text for token in self.input[self.index:self.index + length])
43
-
44
- def to_string(self, interested: Callable[[Any], bool])->str:
45
- return f"ParserState(\n"\
46
- f"index={self.index}, \n"\
47
- f"input({len(self.input)})=[{self.token_sample_string()}, ...]), \n"\
48
- f"before=({self.before()}), \n"\
49
- f"after=({self.after()})"
50
36
 
51
37
 
52
38
  def current(self)->T:
syncraft/syntax.py CHANGED
@@ -6,7 +6,7 @@ from typing import (
6
6
  )
7
7
  from dataclasses import dataclass, field, replace
8
8
  from functools import reduce
9
- from syncraft.algebra import Algebra, Error, Either, Insptectable, ThenResult, ManyResult, ThenKind, NamedResult
9
+ from syncraft.algebra import Algebra, Error, Either, ThenResult, ManyResult, ThenKind, NamedResult
10
10
  from types import MethodType, FunctionType
11
11
 
12
12
 
@@ -17,8 +17,35 @@ B = TypeVar('B') # Result type for mapping
17
17
  C = TypeVar('C') # Result type for else branch
18
18
  S = TypeVar('S') # State type
19
19
 
20
+
21
+ class Variable(Generic[A]):
22
+ def __init__(self, name: Optional[str] = None):
23
+ self.name = name
24
+ self.initialized = False
25
+ self._value = None
26
+ def __call__(self, a: Any) -> Any:
27
+ if self.initialized:
28
+ if self._value != a:
29
+ raise ValueError(f"Variable {self.name or ''} is already initialized")
30
+ else:
31
+ return a
32
+ elif self.name is not None and isinstance(a, NamedResult):
33
+ if a.name == self.name:
34
+ self._value = a.value
35
+ self.initialized = True
36
+ else:
37
+ self._value = a
38
+ self.initialized = True
39
+ return a
40
+ @property
41
+ def value(self) -> A:
42
+ if self.initialized is False or self._value is None:
43
+ raise ValueError(f"Variable {self.name or ''} is not initialized")
44
+ return self._value
45
+
46
+
20
47
  @dataclass(frozen=True)
21
- class Description(Insptectable):
48
+ class Description:
22
49
  name: Optional[str] = None
23
50
  newline: Optional[str] = None
24
51
  fixity: Literal['infix', 'prefix', 'postfix'] = 'infix'
@@ -67,7 +94,7 @@ class Description(Insptectable):
67
94
 
68
95
 
69
96
  @dataclass(frozen=True)
70
- class Syntax(Generic[A, S], Insptectable):
97
+ class Syntax(Generic[A, S]):
71
98
  alg: Callable[[Type[Algebra[Any, Any]]], Algebra[A, S]]
72
99
  meta: Description = field(default_factory=Description, repr=False)
73
100
 
@@ -98,8 +125,6 @@ class Syntax(Generic[A, S], Insptectable):
98
125
  return self.__class__(alg=algebra_run, meta=self.meta)
99
126
 
100
127
 
101
-
102
-
103
128
  def as_(self, typ: Type[B])->B:
104
129
  return cast(typ, self) # type: ignore
105
130
 
@@ -234,6 +259,8 @@ class Syntax(Generic[A, S], Insptectable):
234
259
  return NamedResult(name=name, value=value)
235
260
  return self.map(bind_f).describe(name=f'bind("{name}")', fixity='postfix', parameter=[self])
236
261
 
262
+ def assign(self, var: Variable[A]) -> Syntax[A, S]:
263
+ return self.map(var).describe(name=f'assign({var.name or ""})', fixity='postfix', parameter=[self])
237
264
 
238
265
  def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> Syntax[A, S]:
239
266
  def dump_error_run(err: Any)->Any:
@@ -263,12 +290,6 @@ def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
263
290
  return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(None)
264
291
 
265
292
 
266
-
267
-
268
-
269
-
270
-
271
-
272
293
  def all(*parsers: Syntax[Any, S]) -> Syntax[ThenResult[Any, Any], S]:
273
294
  return reduce(lambda a, b: a + b, parsers) if len(parsers) > 0 else success(None)
274
295
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.19
3
+ Version: 0.1.21
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -31,6 +31,7 @@ pip install syncraft
31
31
 
32
32
 
33
33
  ## TODO
34
- - [ ] Test Walker.get/set for bidirectional mapping between source code and data class
35
- - [ ] Annotate sqlite3 grammar with named nodes and data classes
36
- - [ ]
34
+ - [ ] Add a collect method to AST to collect all named entries and pack them into a dict or a custom dataclass. This method will be called as the last step of my current bimap. So it shares the signature of bimap and can combine with the current bimap
35
+ - [ ] Amend all, first, last, and named helper functions to support bimap and named results.
36
+ - [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
37
+ - [ ] Make the library as fast as possible and feasible.
@@ -0,0 +1,16 @@
1
+ syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ syncraft/algebra.py,sha256=bya-2b5WpCcOMxgJjGqrFaNG8dCjs5NUqnxbJTc4uOM,21908
3
+ syncraft/ast.py,sha256=Hn88_xOqSzrHYtPjNFKREJuE6yISmNI-VVTcU6PJKI0,4737
4
+ syncraft/cmd.py,sha256=DzXgU8QLVOg0YTFKpmOyzbf02LKPphQ4EeKSLzqpJ_s,2012
5
+ syncraft/diagnostic.py,sha256=mnzzceE9HpN2jVa7ztecZCz3sbCD79JxgkdLhJRBsvY,2822
6
+ syncraft/finder.py,sha256=zADOQQdcHu60413ZrRxTSq-E5EAJ9xv0OQodCpoYpo0,7202
7
+ syncraft/generator.py,sha256=Mh9Sx1hNR0EAWcox6MMvVw7aq2s33TU4stGZFZ1N1cw,11287
8
+ syncraft/parser.py,sha256=PSy9s3q_KXQh-Qlg0NxvJixxcgVYi3yCq0ebqlA5WwM,11288
9
+ syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ syncraft/sqlite3.py,sha256=Pq09IHZOwuWg5W82l9D1flzd36QV0TOHQpTJ5U02V8g,34701
11
+ syncraft/syntax.py,sha256=yUp3DmDnkwNEqDAFZH4vesjdnbIUsrwFjWN-Ljy1qN4,14880
12
+ syncraft-0.1.21.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
13
+ syncraft-0.1.21.dist-info/METADATA,sha256=cJyW-BifZOf3RdmFKp1-auJv13aMNjRwk1VDqPG5gR4,1381
14
+ syncraft-0.1.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ syncraft-0.1.21.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
16
+ syncraft-0.1.21.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- syncraft/algebra.py,sha256=l_ptzSkXbRGSLzu46GXixcNQPdXAi1s5R9nRDI86JkY,22785
3
- syncraft/ast.py,sha256=9CuHqXRiw_7UMDLd4x2def3kHgTVQ0hnPxyIbBlx1Zc,4640
4
- syncraft/cmd.py,sha256=DzXgU8QLVOg0YTFKpmOyzbf02LKPphQ4EeKSLzqpJ_s,2012
5
- syncraft/diagnostic.py,sha256=mnzzceE9HpN2jVa7ztecZCz3sbCD79JxgkdLhJRBsvY,2822
6
- syncraft/generator.py,sha256=P2NoSBnTn4i3YV-fuIozmzXwRPR7J8sN-Paj3gQXrtg,12383
7
- syncraft/parser.py,sha256=BzNOiuc9iLt0wSxN79bNC9x0xsMIQcDdIN1Ndp0D3B8,11642
8
- syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- syncraft/sqlite3.py,sha256=Pq09IHZOwuWg5W82l9D1flzd36QV0TOHQpTJ5U02V8g,34701
10
- syncraft/syntax.py,sha256=XTXx0Sf2Elbt0GVTYPt-g-1InTPvZeptcpquugf8My0,13884
11
- syncraft-0.1.19.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
12
- syncraft-0.1.19.dist-info/METADATA,sha256=c-xyYpD0DMsrsZCHNzqyK7X5e8JneLA84Rm5T7m2kHc,946
13
- syncraft-0.1.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- syncraft-0.1.19.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
15
- syncraft-0.1.19.dist-info/RECORD,,