syncraft 0.1.20__py3-none-any.whl → 0.1.22__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.
syncraft/algebra.py CHANGED
@@ -5,180 +5,15 @@ from typing import (
5
5
  )
6
6
 
7
7
  import traceback
8
- from dataclasses import dataclass, replace
8
+ from dataclasses import dataclass, replace, asdict
9
9
  from weakref import WeakKeyDictionary
10
10
  from abc import ABC
11
- from enum import Enum
12
- from functools import reduce
13
-
14
-
15
- A = TypeVar('A')
16
- B = TypeVar('B')
17
- C = TypeVar('C')
18
- S = TypeVar('S')
11
+ from syncraft.ast import ThenKind, ThenResult, ManyResult, OrResult, S
12
+
13
+ A = TypeVar('A') # Result type
14
+ B = TypeVar('B') # Mapped result type
19
15
 
20
16
 
21
- @dataclass(frozen=True)
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
- @staticmethod
43
- def variable(value: A)->Bimap[S, A, A]:
44
- return Bimap(
45
- forward=lambda s, _: (s, value),
46
- inverse=lambda s, y: (s, y)
47
- )
48
- @staticmethod
49
- def const(state: S, value: A)->Bimap[Any, A, A]:
50
- return Bimap(
51
- forward=lambda s, _: (state, value),
52
- inverse=lambda s, y: (state, value)
53
- )
54
- @staticmethod
55
- def combine(*biarrows: Bimap[Any, Any, Any]) -> Bimap[Any, Any, Any]:
56
- return reduce(lambda a, b: a >> b, biarrows, Bimap.identity())
57
-
58
-
59
-
60
- class StructuralResult:
61
- def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, Any, Any]:
62
- return Bimap.identity()
63
-
64
- @dataclass(frozen=True)
65
- class NamedResult(Generic[A], StructuralResult):
66
- name: str
67
- value: A
68
- def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, NamedResult[A], NamedResult[Any]]:
69
- inner_b = self.value.bimap(arr) if isinstance(self.value, StructuralResult) else arr
70
- def fwd(s: S, a: NamedResult[A])-> Tuple[S, NamedResult[Any]]:
71
- assert a == self, f"Expected {self}, got {a}"
72
- inner_s, inner_v = inner_b.forward(s, a.value)
73
- return (inner_s, replace(a, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, inner_v)
74
-
75
- def inv(s: S, a: NamedResult[Any]) -> Tuple[S, NamedResult[A]]:
76
- assert isinstance(a, NamedResult), f"Expected NamedResult, got {type(a)}"
77
- inner_s, inner_v = inner_b.inverse(s, a.value)
78
- return (inner_s, replace(self, value=inner_v)) if not isinstance(inner_v, NamedResult) else (inner_s, replace(self, value=inner_v.value))
79
-
80
- return Bimap(
81
- forward=fwd,
82
- inverse=inv
83
- )
84
- @dataclass(eq=True, frozen=True)
85
- class ManyResult(Generic[A], StructuralResult):
86
- value: Tuple[A, ...]
87
- def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, ManyResult[A], List[A]]:
88
- inner_b = [v.bimap(arr) if isinstance(v, StructuralResult) else arr for v in self.value]
89
- def fwd(s: Any, a: ManyResult[A]) -> Tuple[Any, List[A]]:
90
- assert a == self, f"Expected {self}, got {a}"
91
- return s, [inner_b[i].forward(s, v)[1] for i, v in enumerate(a.value)]
92
-
93
- def inv(s: Any, a: List[A]) -> Tuple[Any, ManyResult[A]]:
94
- assert isinstance(a, list), f"Expected list, got {type(a)}"
95
- assert len(a) == len(inner_b), f"Expected {len(inner_b)} elements, got {len(a)}"
96
- return s, ManyResult(value=tuple(inner_b[i].inverse(s, v)[1] for i, v in enumerate(a)))
97
- return Bimap(
98
- forward=fwd,
99
- inverse=inv
100
- )
101
- @dataclass(eq=True, frozen=True)
102
- class OrResult(Generic[A], StructuralResult):
103
- value: A
104
- def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, OrResult[A], Any]:
105
- inner_b = self.value.bimap(arr) if isinstance(self.value, StructuralResult) else arr
106
- def fwd(s: Any, a: OrResult[A]) -> Tuple[Any, Any]:
107
- assert a == self, f"Expected {self}, got {a}"
108
- return inner_b.forward(s, a.value)
109
-
110
- def inv(s: Any, a: Any) -> Tuple[Any, OrResult[A]]:
111
- inner_s, inner_v = inner_b.inverse(s, a)
112
- return inner_s, OrResult(value=inner_v)
113
-
114
- return Bimap(
115
- forward=fwd,
116
- inverse=inv
117
- )
118
- class ThenKind(Enum):
119
- BOTH = '+'
120
- LEFT = '//'
121
- RIGHT = '>>'
122
-
123
- @dataclass(eq=True, frozen=True)
124
- class ThenResult(Generic[A, B], StructuralResult):
125
- kind: ThenKind
126
- left: A
127
- right: B
128
- def arity(self)->int:
129
- if self.kind == ThenKind.LEFT:
130
- return self.left.arity() if isinstance(self.left, ThenResult) else 1
131
- elif self.kind == ThenKind.RIGHT:
132
- return self.right.arity() if isinstance(self.right, ThenResult) else 1
133
- elif self.kind == ThenKind.BOTH:
134
- left_arity = self.left.arity() if isinstance(self.left, ThenResult) else 1
135
- right_arity = self.right.arity() if isinstance(self.right, ThenResult) else 1
136
- return left_arity + right_arity
137
- else:
138
- return 1
139
-
140
- def bimap(self, arr: Bimap[Any, Any, Any] = Bimap.identity()) -> Bimap[Any, ThenResult[A, B], Tuple[Any, ...] | Any]:
141
- kind = self.kind
142
- lb = self.left.bimap(arr) if isinstance(self.left, StructuralResult) else arr
143
- rb = self.right.bimap(arr) if isinstance(self.right, StructuralResult) else arr
144
- left_size = self.left.arity() if isinstance(self.left, ThenResult) else 1
145
- right_size = self.right.arity() if isinstance(self.right, ThenResult) else 1
146
- def fwd(s : S, a : ThenResult[A, B]) -> Tuple[S, Tuple[Any, ...] | Any]:
147
- assert a == self, f"Expected {self}, got {a}"
148
- match kind:
149
- case ThenKind.LEFT:
150
- return lb.forward(s, a.left)
151
- case ThenKind.RIGHT:
152
- return rb.forward(s, a.right)
153
- case ThenKind.BOTH:
154
- s1, left_v = lb.forward(s, a.left)
155
- s2, right_v = rb.forward(s1, a.right)
156
- left_v = (left_v,) if not isinstance(a.left, ThenResult) else left_v
157
- right_v = (right_v,) if not isinstance(a.right, ThenResult) else right_v
158
- return s2, left_v + right_v
159
-
160
- def inv(s: S, b: Tuple[Any, ...] | Any) -> Tuple[S, ThenResult[A, B]]:
161
- match kind:
162
- case ThenKind.LEFT:
163
- s1, lv = lb.inverse(s, b)
164
- return s1, replace(self, left=lv)
165
- case ThenKind.RIGHT:
166
- s1, rv = rb.inverse(s, b)
167
- return s1, replace(self, right=rv)
168
- case ThenKind.BOTH:
169
- lraw = b[:left_size]
170
- rraw = b[left_size:left_size + right_size]
171
- lraw = lraw[0] if left_size == 1 else lraw
172
- rraw = rraw[0] if right_size == 1 else rraw
173
- s1, lv = lb.inverse(s, lraw)
174
- s2, rv = rb.inverse(s1, rraw)
175
- return s2, replace(self, left=lv, right=rv)
176
-
177
- return Bimap(
178
- forward=fwd,
179
- inverse=inv
180
- )
181
-
182
17
  InProgress = object() # Marker for in-progress state, used to prevent re-entrance in recursive calls
183
18
  L = TypeVar('L') # Left type for combined results
184
19
  R = TypeVar('R') # Right type for combined results
@@ -222,7 +57,14 @@ class Error:
222
57
  state=state,
223
58
  previous=self
224
59
  )
225
-
60
+ def to_list(self)->List[Dict[str, Any]]:
61
+ lst = []
62
+ current: Optional[Error] = self
63
+ while current is not None:
64
+ d = asdict(current)
65
+ lst.append({k:v for k,v in d.items() if v is not None and k != 'previous'})
66
+ current = current.previous
67
+ return lst
226
68
 
227
69
 
228
70
  @dataclass(frozen=True)
@@ -403,6 +245,12 @@ class Algebra(ABC, Generic[A, S]):
403
245
  return cast(Either[Any, Tuple[B, S]], parsed)
404
246
  return self.__class__(map_run, name=self.name) # type: ignore
405
247
 
248
+ def map_all(self, f: Callable[[Either[Any, Tuple[A, S]]], Either[Any, Tuple[B, S]]])->Algebra[B, S]:
249
+ def map_all_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
250
+ parsed = self.run(input, use_cache)
251
+ return f(parsed)
252
+ return self.__class__(map_all_run, name=self.name) # type: ignore
253
+
406
254
  def map_error(self, f: Callable[[Optional[Any]], Any]) -> Algebra[A, S]:
407
255
  def map_error_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
408
256
  parsed = self.run(input, use_cache)
syncraft/ast.py CHANGED
@@ -4,15 +4,224 @@ from __future__ import annotations
4
4
  import re
5
5
  from typing import (
6
6
  Optional, Any, TypeVar, Tuple, runtime_checkable,
7
- Protocol, Generic, Callable, Union, cast
8
- )
9
- from syncraft.algebra import (
10
- OrResult,ThenResult, ManyResult, ThenKind,NamedResult, StructuralResult
7
+ Dict, Generic, Callable, Union, cast, List, Protocol, Type
11
8
  )
9
+
10
+
12
11
  from dataclasses import dataclass, replace, is_dataclass, asdict
13
12
  from enum import Enum
14
13
  from functools import cached_property
14
+ from syncraft.constraint import Binding, Variable, Bindable
15
+
16
+
17
+
18
+ A = TypeVar('A')
19
+ B = TypeVar('B')
20
+ C = TypeVar('C')
21
+ S = TypeVar('S', bound=Bindable)
22
+
23
+ @dataclass(frozen=True)
24
+ class Reducer(Generic[A, S]):
25
+ run_f: Callable[[A, S], S]
26
+ def __call__(self, a: A, s: S) -> S:
27
+ return self.run_f(a, s)
28
+
29
+ def map(self, f: Callable[[B], A]) -> Reducer[B, S]:
30
+ def map_run(b: B, s: S) -> S:
31
+ return self(f(b), s)
32
+ return Reducer(map_run)
33
+
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class Bimap(Generic[A, B]):
38
+ run_f: Callable[[A], Tuple[B, Callable[[B], A]]]
39
+ def __call__(self, a: A) -> Tuple[B, Callable[[B], A]]:
40
+ return self.run_f(a)
41
+ def __rshift__(self, other: Bimap[B, C]) -> Bimap[A, C]:
42
+ def then_run(a: A) -> Tuple[C, Callable[[C], A]]:
43
+ b, inv1 = self(a)
44
+ c, inv2 = other(b)
45
+ def inv(c2: C) -> A:
46
+ return inv1(inv2(c2))
47
+ return c, inv
48
+ return Bimap(then_run)
49
+ @staticmethod
50
+ def const(a: B)->Bimap[B, B]:
51
+ return Bimap(lambda _: (a, lambda b: b))
52
+
53
+ @staticmethod
54
+ def identity()->Bimap[Any, Any]:
55
+ return Bimap(lambda a: (a, lambda b: b))
56
+
57
+ @staticmethod
58
+ def when(cond: Callable[[A], bool],
59
+ then: Bimap[A, B],
60
+ otherwise: Optional[Bimap[A, C]] = None) -> Bimap[A, A | B | C]:
61
+ def when_run(a:A) -> Tuple[A | B | C, Callable[[A | B | C], A]]:
62
+ bimap = then if cond(a) else (otherwise if otherwise is not None else Bimap.identity())
63
+ abc, inv = bimap(a)
64
+ def inv_f(b: Any) -> A:
65
+ return inv(b)
66
+ return abc, inv_f
67
+ return Bimap(when_run)
68
+
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class Biarrow(Generic[S, A, B]):
73
+ forward: Callable[[S, A], Tuple[S, B]]
74
+ inverse: Callable[[S, B], Tuple[S, A]]
75
+ def __rshift__(self, other: Biarrow[S, B, C]) -> Biarrow[S, A, C]:
76
+ def fwd(s: S, a: A) -> Tuple[S, C]:
77
+ s1, b = self.forward(s, a)
78
+ return other.forward(s1, b)
79
+ def inv(s: S, c: C) -> Tuple[S, A]:
80
+ s1, b = other.inverse(s, c)
81
+ return self.inverse(s1, b)
82
+ return Biarrow(
83
+ forward=fwd,
84
+ inverse=inv
85
+ )
86
+ @staticmethod
87
+ def identity()->Biarrow[S, A, A]:
88
+ return Biarrow(
89
+ forward=lambda s, x: (s, x),
90
+ inverse=lambda s, y: (s, y)
91
+ )
92
+
93
+ @staticmethod
94
+ def when(condition: Callable[..., bool],
95
+ then: Biarrow[S, A, B],
96
+ otherwise: Optional[Biarrow[S, A, B]] = None) -> Callable[..., Biarrow[S, A, B]]:
97
+ def _when(*args:Any, **kwargs:Any) -> Biarrow[S, A, B]:
98
+ return then if condition(*args, **kwargs) else (otherwise or Biarrow.identity())
99
+ return _when
100
+
101
+
102
+
103
+ class StructuralResult:
104
+
105
+ def bimap(self, f: Bimap[Any, Any]=Bimap.identity())->Tuple[Any, Callable[[Any], Any]]:
106
+ return f(self)
107
+
108
+
109
+ @dataclass(frozen=True)
110
+ class MarkedResult(Generic[A], StructuralResult):
111
+ name: str
112
+ value: A
113
+ def bimap(self, f: Bimap[A, B]=Bimap.identity())->Tuple[MarkedResult[B], Callable[[MarkedResult[B]], MarkedResult[A]]]:
114
+ b, inv = self.value.bimap(f) if isinstance(self.value, StructuralResult) else f(self.value)
115
+ return MarkedResult(name=self.name, value=b), lambda b: replace(self, value=inv(b.value))
116
+
117
+
118
+ @dataclass(eq=True, frozen=True)
119
+ class ManyResult(Generic[A], StructuralResult):
120
+ value: Tuple[A, ...]
121
+ def bimap(self, f: Bimap[A, B]=Bimap.identity())->Tuple[List[B], Callable[[List[B]], ManyResult[A]]]:
122
+ assert self.value
123
+ forward = [v.bimap(f) if isinstance(v, StructuralResult) else f(v) for v in self.value]
124
+ def invf(b: List[B]) -> ManyResult[A]:
125
+ assert len(b) <= len(forward)
126
+ return replace(self, value=tuple([forward[i][1](bb) for i, bb in enumerate(b)]))
127
+ return [b for b, _ in forward], invf
128
+
129
+
130
+
131
+ @dataclass(eq=True, frozen=True)
132
+ class OrResult(Generic[A], StructuralResult):
133
+ value: A
134
+ def bimap(self, f: Bimap[A, B]=Bimap.identity())->Tuple[B, Callable[[B], OrResult[A]]]:
135
+ b, inv = self.value.bimap(f) if isinstance(self.value, StructuralResult) else f(self.value)
136
+ return b, lambda b: replace(self, value=inv(b))
137
+
138
+ class ThenKind(Enum):
139
+ BOTH = '+'
140
+ LEFT = '//'
141
+ RIGHT = '>>'
142
+
143
+ FlatThen = Tuple[Any, ...]
144
+ MarkedThen = Tuple[Dict[str, Any] | Any, FlatThen]
145
+
146
+ @dataclass(eq=True, frozen=True)
147
+ class ThenResult(Generic[A, B], StructuralResult):
148
+ kind: ThenKind
149
+ left: A
150
+ right: B
151
+ @staticmethod
152
+ def collect_marked(a: FlatThen, f: Optional[Callable[..., Any]] = None)->Tuple[MarkedThen, Callable[[MarkedThen], FlatThen]]:
153
+ index: List[str | int] = []
154
+ named_count = 0
155
+ for i, v in enumerate(a):
156
+ if isinstance(v, MarkedResult):
157
+ index.append(v.name)
158
+ named_count += 1
159
+ else:
160
+ index.append(i - named_count)
161
+ named = {v.name: v.value for v in a if isinstance(v, MarkedResult)}
162
+ unnamed = [v for v in a if not isinstance(v, MarkedResult)]
163
+ if f is None:
164
+ ret = (named, tuple(unnamed))
165
+ else:
166
+ ret = (f(**named), tuple(unnamed))
167
+ def invf(b: MarkedThen) -> Tuple[Any, ...]:
168
+ named_value, unnamed_value = b
169
+ assert isinstance(named_value, dict) or is_dataclass(named_value), f"Expected dict or dataclass for named values, got {type(named_value)}"
170
+ if is_dataclass(named_value):
171
+ named_dict = named | asdict(cast(Any, named_value))
172
+ else:
173
+ named_dict = named | named_value
174
+ ret = []
175
+ for x in index:
176
+ if isinstance(x, str):
177
+ assert x in named_dict, f"Missing named value: {x}"
178
+ ret.append(named_dict[x])
179
+ else:
180
+ assert 0 <= x < len(unnamed_value), f"Missing unnamed value at index: {x}"
181
+ ret.append(unnamed_value[x])
182
+ return tuple(ret)
183
+ return ret, invf
184
+
185
+ def bimap(self, f: Bimap[Any, Any]=Bimap.identity()) -> Tuple[MarkedThen, Callable[[MarkedThen], ThenResult[A, B]]]:
186
+ match self.kind:
187
+ case ThenKind.LEFT:
188
+ lb, linv = self.left.bimap(f) if isinstance(self.left, StructuralResult) else f(self.left)
189
+ return lb, lambda b: replace(self, left=linv(b))
190
+ case ThenKind.RIGHT:
191
+ rb, rinv = self.right.bimap(f) if isinstance(self.right, StructuralResult) else f(self.right)
192
+ return rb, lambda b: replace(self, right=rinv(b))
193
+ case ThenKind.BOTH:
194
+ lb, linv = self.left.bimap(f) if isinstance(self.left, StructuralResult) else f(self.left)
195
+ rb, rinv = self.right.bimap(f) if isinstance(self.right, StructuralResult) else f(self.right)
196
+ left_v = (lb,) if not isinstance(self.left, ThenResult) else lb
197
+ right_v = (rb,) if not isinstance(self.right, ThenResult) else rb
198
+ def invf(b: Tuple[Any, ...]) -> ThenResult[A, B]:
199
+ left_size = self.left.arity() if isinstance(self.left, ThenResult) else 1
200
+ right_size = self.right.arity() if isinstance(self.right, ThenResult) else 1
201
+ lraw = b[:left_size]
202
+ rraw = b[left_size:left_size + right_size]
203
+ lraw = lraw[0] if left_size == 1 else lraw
204
+ rraw = rraw[0] if right_size == 1 else rraw
205
+ la = linv(lraw)
206
+ ra = rinv(rraw)
207
+ return replace(self, left=la, right=ra)
208
+ return left_v + right_v, invf
209
+ # data, func = ThenResult.collect_marked(left_v + right_v)
210
+ # return data, lambda d: invf(func(d))
211
+
15
212
 
213
+ def arity(self)->int:
214
+ if self.kind == ThenKind.LEFT:
215
+ return self.left.arity() if isinstance(self.left, ThenResult) else 1
216
+ elif self.kind == ThenKind.RIGHT:
217
+ return self.right.arity() if isinstance(self.right, ThenResult) else 1
218
+ elif self.kind == ThenKind.BOTH:
219
+ left_arity = self.left.arity() if isinstance(self.left, ThenResult) else 1
220
+ right_arity = self.right.arity() if isinstance(self.right, ThenResult) else 1
221
+ return left_arity + right_arity
222
+ else:
223
+ return 1
224
+
16
225
  @runtime_checkable
17
226
  class TokenProtocol(Protocol):
18
227
  @property
@@ -55,11 +264,14 @@ T = TypeVar('T', bound=TokenProtocol)
55
264
 
56
265
  ParseResult = Union[
57
266
  ThenResult['ParseResult[T]', 'ParseResult[T]'],
58
- NamedResult['ParseResult[T]'],
267
+ MarkedResult['ParseResult[T]'],
59
268
  ManyResult['ParseResult[T]'],
60
269
  OrResult['ParseResult[T]'],
61
270
  T,
62
271
  ]
272
+
273
+
274
+
63
275
  @dataclass(frozen=True)
64
276
  class AST(Generic[T]):
65
277
  focus: ParseResult[T]
@@ -68,24 +280,20 @@ class AST(Generic[T]):
68
280
 
69
281
  def bimap(self)->Tuple[Any, Callable[[Any], AST[T]]]:
70
282
  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
283
+ data, invf = self.focus.bimap()
284
+ return data, lambda x: replace(self, focus=invf(x))
77
285
  else:
78
286
  return self.focus, lambda x: replace(self, focus=x)
79
287
 
80
288
  def wrapper(self)-> Callable[[Any], Any]:
81
- if isinstance(self.focus, NamedResult):
82
- focus = cast(NamedResult[Any], self.focus)
83
- return lambda x: NamedResult(name = focus.name, value = x)
289
+ if isinstance(self.focus, MarkedResult):
290
+ focus = cast(MarkedResult[Any], self.focus)
291
+ return lambda x: MarkedResult(name = focus.name, value = x)
84
292
  else:
85
293
  return lambda x: x
86
294
 
87
295
  def is_named(self) -> bool:
88
- return isinstance(self.focus, NamedResult)
296
+ return isinstance(self.focus, MarkedResult)
89
297
 
90
298
  def left(self) -> Optional[AST[T]]:
91
299
  match self.focus:
@@ -114,13 +322,13 @@ class AST(Generic[T]):
114
322
  return replace(self, focus=value, parent=self, pruned=self.pruned)
115
323
  else:
116
324
  raise IndexError(f"Index {index} out of bounds for OrResult")
117
- case NamedResult(value=value):
325
+ case MarkedResult(value=value):
118
326
  return replace(self, focus=value, parent=self, pruned=self.pruned)
119
327
  case _:
120
328
  raise TypeError(f"Invalid focus type({self.focus}) for down traversal")
121
329
 
122
330
  def how_many(self)->int:
123
- focus = self.focus.value if isinstance(self.focus, NamedResult) else self.focus
331
+ focus = self.focus.value if isinstance(self.focus, MarkedResult) else self.focus
124
332
  match focus:
125
333
  case ManyResult(value=children):
126
334
  return len(children)
syncraft/constraint.py ADDED
@@ -0,0 +1,189 @@
1
+ from __future__ import annotations
2
+ from typing import Callable, Generic, Tuple, TypeVar, Optional, Any, Protocol
3
+ from enum import Enum
4
+ from dataclasses import dataclass, field, replace
5
+ import collections.abc
6
+ from collections import defaultdict
7
+ from itertools import product
8
+
9
+ K = TypeVar('K')
10
+ V = TypeVar('V')
11
+ class FrozenDict(collections.abc.Mapping, Generic[K, V]):
12
+ def __init__(self, *args, **kwargs):
13
+ self._data = dict(*args, **kwargs)
14
+ self._hash = None
15
+ def __getitem__(self, key):
16
+ return self._data[key]
17
+
18
+ def __iter__(self):
19
+ return iter(self._data)
20
+
21
+ def __len__(self):
22
+ return len(self._data)
23
+
24
+ def __hash__(self):
25
+ if self._hash is None:
26
+ self._hash = hash(frozenset(self._data.items()))
27
+ return self._hash
28
+
29
+ def __eq__(self, other):
30
+ if isinstance(other, collections.abc.Mapping):
31
+ return self._data == other
32
+ return NotImplemented
33
+
34
+ def __repr__(self):
35
+ return f"{self.__class__.__name__}({self._data})"
36
+
37
+
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class Expr:
42
+ left: Any
43
+ op: str
44
+ right: Any
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class Variable:
49
+ name: Optional[str] = None
50
+ _root: Optional[Variable] = field(default=None, compare=False, repr=False)
51
+ _mapf: Optional[Callable[[Any], Any]] = field(default=None, compare=False, repr=False)
52
+
53
+ def __post_init__(self):
54
+ if self._root is None:
55
+ object.__setattr__(self, '_root', self)
56
+
57
+ def raw(self, b:'BoundVar') -> Tuple[Any, ...]:
58
+ assert self._root is not None, "_rawf can not be None"
59
+ return b.get(self._root, ())
60
+
61
+
62
+ def map(self, f: Callable[[Any], Any]) -> "Variable":
63
+ if self._mapf is None:
64
+ return replace(self, _mapf=f)
65
+ else:
66
+ oldf = self._mapf
67
+ return replace(self, _mapf=lambda a: f(oldf(a)))
68
+
69
+ def get(self, b: 'BoundVar') -> Tuple[Any, ...]:
70
+ vals = self.raw(b)
71
+ if self._mapf is not None:
72
+ return tuple(self._mapf(v) for v in vals)
73
+ else:
74
+ return vals
75
+
76
+ def __call__(self, b:'BoundVar', raw:bool=False) -> Any:
77
+ if raw:
78
+ return self.raw(b)
79
+ else:
80
+ return self.get(b)
81
+
82
+ def __eq__(self, other):
83
+ return Expr(self, '==', other)
84
+ def __ne__(self, other):
85
+ return Expr(self, '!=', other)
86
+ def __lt__(self, other):
87
+ return Expr(self, '<', other)
88
+ def __le__(self, other):
89
+ return Expr(self, '<=', other)
90
+ def __gt__(self, other):
91
+ return Expr(self, '>', other)
92
+ def __ge__(self, other):
93
+ return Expr(self, '>=', other)
94
+
95
+ BoundVar = FrozenDict[Variable, Tuple[Any, ...]]
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class Binding:
100
+ bindings : frozenset[Tuple[Variable, Any]] = frozenset()
101
+ def bind(self, var: Variable, node: Any) -> Binding:
102
+ new_binding = set(self.bindings)
103
+ new_binding.add((var, node))
104
+ return Binding(bindings=frozenset(new_binding))
105
+
106
+ def to_dict(self)->BoundVar:
107
+ ret = defaultdict(list)
108
+ for var, node in self.bindings:
109
+ ret[var].append(node)
110
+ return FrozenDict({k: tuple(vs) for k, vs in ret.items()})
111
+
112
+ class Bindable(Protocol):
113
+ binding: Binding
114
+ def bind(self, var: Variable, node: Any) -> Any: ...
115
+
116
+
117
+ class Quantifier(Enum):
118
+ FORALL = "forall"
119
+ EXISTS = "exists"
120
+
121
+ @dataclass(frozen=True)
122
+ class Constraint:
123
+ run_f: Callable[[BoundVar], bool]
124
+ name: str = ""
125
+ def __call__(self, bound: BoundVar)->bool:
126
+ return self.run_f(bound)
127
+ def __and__(self, other: Constraint) -> Constraint:
128
+ return Constraint(
129
+ run_f=lambda bound: self(bound) and other(bound),
130
+ name=f"({self.name} && {other.name})"
131
+ )
132
+ def __or__(self, other: Constraint) -> Constraint:
133
+ return Constraint(
134
+ run_f=lambda bound: self(bound) or other(bound),
135
+ name=f"({self.name} || {other.name})"
136
+ )
137
+ def __xor__(self, other: Constraint) -> Constraint:
138
+ return Constraint(
139
+ run_f=lambda bound: self(bound) ^ other(bound),
140
+ name=f"({self.name} ^ {other.name})"
141
+ )
142
+ def __invert__(self) -> Constraint:
143
+ return Constraint(
144
+ run_f=lambda bound: not self(bound),
145
+ name=f"!({self.name})"
146
+ )
147
+
148
+ @classmethod
149
+ def predicate(cls, f: Callable[..., bool],*, name: Optional[str] = None, quant: Quantifier = Quantifier.FORALL)->Callable[..., Constraint]:
150
+ def wrapper(*args: Any, **kwargs:Any) -> Constraint:
151
+ arg_list = list(args)
152
+ kw_list = [(k, v) for k, v in kwargs.items()]
153
+ def run_f(bound: BoundVar) -> bool:
154
+ # positional argument values
155
+ pos_values = [
156
+ arg.get(bound) if isinstance(arg, Variable) else (arg,)
157
+ for arg in arg_list
158
+ ]
159
+ # keyword argument values
160
+ kw_keys, kw_values = zip(*[
161
+ (k, v.get(bound) if isinstance(v, Variable) else (v,))
162
+ for k, v in kw_list
163
+ ]) if kw_list else ([], [])
164
+
165
+ # Cartesian product over all argument values
166
+ all_combos = product(*pos_values, *kw_values)
167
+
168
+ # evaluate predicate on each combination
169
+ def eval_combo(combo):
170
+ pos_args = combo[:len(pos_values)]
171
+ kw_args = dict(zip(kw_keys, combo[len(pos_values):]))
172
+ return f(*pos_args, **kw_args)
173
+
174
+ if quant is Quantifier.EXISTS:
175
+ return any(eval_combo(c) for c in all_combos)
176
+ else:
177
+ return all(eval_combo(c) for c in all_combos)
178
+ return cls(run_f=run_f, name = name or f.__name__)
179
+ return wrapper
180
+
181
+ @classmethod
182
+ def forall(cls, f: Callable[..., bool], name: Optional[str] = None) -> Callable[..., Constraint]:
183
+ return cls.predicate(f, name=name, quant=Quantifier.FORALL)
184
+
185
+ @classmethod
186
+ def exists(cls, f: Callable[..., bool], name: Optional[str] = None):
187
+ return cls.predicate(f, name=name, quant=Quantifier.EXISTS)
188
+
189
+
syncraft/diagnostic.py CHANGED
@@ -10,7 +10,7 @@ from sqlglot.expressions import Expression
10
10
 
11
11
 
12
12
  def rich_error(err: Error)->None:
13
- lst = err.to_list(lambda _: True)
13
+ lst = err.to_list()
14
14
  root, leaf = lst[0], lst[-1]
15
15
  tbl = RichTable(title="Parser Error", show_lines=True)
16
16
  tbl.add_column("Root Parser Field", style="blue")
@@ -26,9 +26,9 @@ def rich_error(err: Error)->None:
26
26
  print(tbl)
27
27
 
28
28
 
29
- def rich_parser(p: Any)-> None:
29
+ def rich_parser(p: Syntax)-> None:
30
30
  print("Parser Debug Information:")
31
- print(p._string or repr(p))
31
+ print(p.meta.to_string(lambda _ : True) or repr(p))
32
32
 
33
33
  def rich_debug(this: Algebra[Any, ParserState[Any]],
34
34
  state: ParserState[Any],
@@ -44,8 +44,8 @@ def rich_debug(this: Algebra[Any, ParserState[Any]],
44
44
  return prefix + value.sql()
45
45
  elif isinstance(value, Token):
46
46
  return prefix + f"{value.token_type.name}({value.text})"
47
- elif isinstance(value, (Error, ParserState, Syntax)):
48
- return prefix + (value._string or 'N/A')
47
+ elif isinstance(value, Syntax):
48
+ return prefix + (value.meta.to_string(lambda _ : True) or 'N/A')
49
49
  else:
50
50
  return prefix + str(value)
51
51
 
syncraft/finder.py ADDED
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import (
4
+ Any, Tuple, Optional, Generator as YieldGen
5
+ )
6
+ from dataclasses import dataclass, replace
7
+ from syncraft.algebra import (
8
+ Algebra, Either, Right,
9
+ OrResult, ManyResult, ThenResult, MarkedResult
10
+ )
11
+
12
+ from syncraft.ast import T, ParseResult, AST
13
+ from syncraft.generator import GenState, Generator
14
+ from sqlglot import TokenType
15
+ from syncraft.syntax import Syntax
16
+ import re
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class Finder(Generator[T]):
21
+ def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[ParseResult[T]], GenState[T]]:
22
+ assert at_least > 0, "at_least must be greater than 0"
23
+ assert at_most is None or at_least <= at_most, "at_least must be less than or equal to at_most"
24
+ return self.map_state(lambda s: replace(s, is_pruned = False)).many(at_least=at_least, at_most=at_most)
25
+
26
+
27
+ def or_else(self, # type: ignore
28
+ other: Algebra[ParseResult[T], GenState[T]]
29
+ ) -> Algebra[OrResult[ParseResult[T]], GenState[T]]:
30
+ return self.map_state(lambda s: replace(s, is_pruned = False)).or_else(other)
31
+
32
+
33
+ @classmethod
34
+ def token(cls,
35
+ token_type: Optional[TokenType] = None,
36
+ text: Optional[str] = None,
37
+ case_sensitive: bool = False,
38
+ regex: Optional[re.Pattern[str]] = None
39
+ )-> Algebra[ParseResult[T], GenState[T]]:
40
+ return super().token(token_type=token_type,
41
+ text=text,
42
+ case_sensitive=case_sensitive,
43
+ regex=regex).map_state(lambda s: replace(s, is_pruned = False)) # type: ignore
44
+
45
+
46
+ @classmethod
47
+ def anything(cls)->Algebra[ParseResult[T], GenState[T]]:
48
+ def anything_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ParseResult[T], GenState[T]]]:
49
+ wrapper = input.wrapper()
50
+ return Right((wrapper(input.focus), input))
51
+ return cls(anything_run, name=cls.__name__ + '.anything()')
52
+
53
+
54
+
55
+ anything = Syntax(lambda cls: cls.factory('anything')).describe(name="anything", fixity='infix')
56
+
57
+ def matches(syntax: Syntax[Any, Any], data: AST[Any])-> bool:
58
+ gen = syntax(Finder)
59
+ state = GenState.from_ast(data)
60
+ result = gen.run(state, use_cache=True)
61
+ return isinstance(result, Right)
62
+
63
+
64
+ def find(syntax: Syntax[Any, Any], data: AST[Any]) -> YieldGen[AST[Any], None, None]:
65
+ if matches(syntax, data):
66
+ yield data
67
+ match data.focus:
68
+ case ThenResult(left = left, right=right):
69
+ yield from find(syntax, AST(left))
70
+ yield from find(syntax, AST(right))
71
+ case ManyResult(value = value):
72
+ for e in value:
73
+ yield from find(syntax, AST(e))
74
+ case MarkedResult(value=value):
75
+ yield from find(syntax, AST(value))
76
+ case OrResult(value=value):
77
+ yield from find(syntax, AST(value))
78
+ case _:
79
+ pass
syncraft/generator.py CHANGED
@@ -1,17 +1,18 @@
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,
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, Binding, Variable, Bindable
15
+
15
16
  from syncraft.syntax import Syntax
16
17
  from sqlglot import TokenType
17
18
  import re
@@ -20,20 +21,16 @@ from functools import lru_cache
20
21
  import random
21
22
 
22
23
  B = TypeVar('B')
23
- T = TypeVar('T', bound=TokenProtocol)
24
-
25
- GenResult = Union[
26
- ThenResult['GenResult[T]', 'GenResult[T]'],
27
- ManyResult['GenResult[T]'],
28
- OrResult['GenResult[T]'],
29
24
 
30
- T
31
- ]
32
25
 
33
26
  @dataclass(frozen=True)
34
- class GenState(Generic[T]):
27
+ class GenState(Bindable, Generic[T]):
35
28
  ast: Optional[AST[T]]
36
29
  seed: int
30
+ is_pruned: Optional[bool] = None
31
+ binding: Binding = Binding()
32
+ def bind(self, var: Variable, node:ParseResult[T])->GenState[T]:
33
+ return replace(self, binding=self.binding.bind(var, node))
37
34
 
38
35
  def fork(self, tag: Any) -> GenState[T]:
39
36
  return replace(self, seed=hash((self.seed, tag)))
@@ -46,7 +43,10 @@ class GenState(Generic[T]):
46
43
 
47
44
  @cached_property
48
45
  def pruned(self)->bool:
49
- return self.ast is None or self.ast.pruned
46
+ if self.is_pruned is None:
47
+ return self.ast is None or self.ast.pruned
48
+ else:
49
+ return self.is_pruned
50
50
 
51
51
 
52
52
  @property
@@ -150,8 +150,8 @@ class TokenGen(TokenSpec):
150
150
 
151
151
 
152
152
  @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]]:
153
+ class Generator(Algebra[ParseResult[T], GenState[T]]):
154
+ def flat_map(self, f: Callable[[ParseResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
155
155
  def flat_map_run(original: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
156
156
  wrapper = original.wrapper()
157
157
  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
@@ -175,13 +175,13 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
175
175
  state=original,
176
176
  error=e
177
177
  ))
178
- return Generator(run_f = flat_map_run, name=self.name) # type: ignore
178
+ return self.__class__(run_f = flat_map_run, name=self.name) # type: ignore
179
179
 
180
180
 
181
- def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[GenResult[T]], GenState[T]]:
181
+ def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[ParseResult[T]], GenState[T]]:
182
182
  assert at_least > 0, "at_least must be greater than 0"
183
183
  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]]]:
184
+ def many_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ManyResult[ParseResult[T]], GenState[T]]]:
185
185
  wrapper = input.wrapper()
186
186
  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
187
  if input.pruned:
@@ -221,9 +221,9 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
221
221
 
222
222
 
223
223
  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]]]:
224
+ other: Algebra[ParseResult[T], GenState[T]]
225
+ ) -> Algebra[OrResult[ParseResult[T]], GenState[T]]:
226
+ def or_else_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[OrResult[ParseResult[T]], GenState[T]]]:
227
227
  wrapper = input.wrapper()
228
228
  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
229
  if input.pruned:
@@ -252,10 +252,10 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
252
252
  text: Optional[str] = None,
253
253
  case_sensitive: bool = False,
254
254
  regex: Optional[re.Pattern[str]] = None
255
- )-> Algebra[GenResult[T], GenState[T]]:
255
+ )-> Algebra[ParseResult[T], GenState[T]]:
256
256
  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]]]:
257
+ lazy_self: Algebra[ParseResult[T], GenState[T]]
258
+ def token_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[ParseResult[Token], GenState[T]]]:
259
259
  wrapper = input.wrapper()
260
260
  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
261
  if input.pruned:
@@ -282,27 +282,4 @@ def generate(syntax: Syntax[Any, Any], data: Optional[AST[Any]] = None, seed: in
282
282
  return result.value
283
283
 
284
284
 
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
285
 
syncraft/parser.py CHANGED
@@ -3,7 +3,7 @@ import re
3
3
  from sqlglot import tokenize, TokenType, Parser as GlotParser, exp
4
4
  from typing import (
5
5
  Optional, List, Any, Tuple,
6
- Generic, Callable
6
+ Generic, Callable, Hashable
7
7
  )
8
8
  from syncraft.algebra import (
9
9
  Either, Left, Right, Error, Algebra
@@ -13,20 +13,16 @@ from enum import Enum
13
13
  from functools import reduce
14
14
  from syncraft.syntax import Syntax
15
15
 
16
- from syncraft.ast import Token, TokenSpec, AST, T
17
-
18
-
19
-
20
-
21
-
22
-
23
-
16
+ from syncraft.ast import Token, TokenSpec, AST, T, ParseResult, Binding, Variable, Bindable
24
17
 
25
18
 
26
19
  @dataclass(frozen=True)
27
- class ParserState(Generic[T]):
20
+ class ParserState(Bindable, Generic[T]):
28
21
  input: Tuple[T, ...] = field(default_factory=tuple)
29
22
  index: int = 0
23
+ binding: Binding[ParseResult[T]] = Binding()
24
+ def bind(self, var: Variable, node:ParseResult[T])->ParserState[T]:
25
+ return replace(self, binding=self.binding.bind(var, node))
30
26
 
31
27
  def token_sample_string(self)-> str:
32
28
  def encode_tokens(*tokens:T) -> str:
syncraft/syntax.py CHANGED
@@ -6,7 +6,9 @@ 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, ThenResult, ManyResult, ThenKind, NamedResult
9
+ from syncraft.algebra import Algebra, Error, Either, Right
10
+ from syncraft.constraint import Variable, Bindable
11
+ from syncraft.ast import ThenResult, ManyResult, ThenKind, MarkedResult
10
12
  from types import MethodType, FunctionType
11
13
 
12
14
 
@@ -15,7 +17,10 @@ from types import MethodType, FunctionType
15
17
  A = TypeVar('A') # Result type
16
18
  B = TypeVar('B') # Result type for mapping
17
19
  C = TypeVar('C') # Result type for else branch
18
- S = TypeVar('S') # State type
20
+ S = TypeVar('S', bound=Bindable) # State type
21
+
22
+
23
+
19
24
 
20
25
  @dataclass(frozen=True)
21
26
  class Description:
@@ -98,8 +103,6 @@ class Syntax(Generic[A, S]):
98
103
  return self.__class__(alg=algebra_run, meta=self.meta)
99
104
 
100
105
 
101
-
102
-
103
106
  def as_(self, typ: Type[B])->B:
104
107
  return cast(typ, self) # type: ignore
105
108
 
@@ -132,11 +135,15 @@ class Syntax(Generic[A, S]):
132
135
  def map(self, f: Callable[[A], B]) -> Syntax[B, S]:
133
136
  return self.__class__(lambda cls: self.alg(cls).map(f), meta = self.meta) # type: ignore
134
137
 
138
+ def map_all(self, f: Callable[[Either[Any, Tuple[A, S]]], Either[Any, Tuple[B, S]]]) -> Syntax[B, S]:
139
+ return self.__class__(lambda cls: self.alg(cls).map_all(f), meta=self.meta) # type: ignore
140
+
135
141
  def map_error(self, f: Callable[[Optional[Any]], Any]) -> Syntax[A, S]:
136
142
  return self.__class__(lambda cls: self.alg(cls).map_error(f), meta=self.meta)
137
143
 
138
144
  def map_state(self, f: Callable[[S], S]) -> Syntax[A, S]:
139
145
  return self.__class__(lambda cls: self.alg(cls).map_state(f), meta=self.meta)
146
+
140
147
 
141
148
  def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
142
149
  return self.__class__(lambda cls: self.alg(cls).flat_map(f)) # type: ignore
@@ -226,13 +233,22 @@ class Syntax(Generic[A, S]):
226
233
 
227
234
 
228
235
  ######################################################################## data processing combinators #########################################################
229
- def bind(self, name: str) -> Syntax[NamedResult[A], S]:
230
- def bind_f(value: A) -> NamedResult[A]:
231
- if isinstance(value, NamedResult):
232
- return replace(value, name=name)
236
+ def bind(self, var: Variable) -> Syntax[A, S]:
237
+ def bind_v(result: Either[Any, Tuple[A, S]])->Either[Any, Tuple[A, S]]:
238
+ if isinstance(result, Right):
239
+ value, state = result.value
240
+ return Right((value, state.bind(var, value)))
241
+ return result
242
+ return self.map_all(bind_v).describe(name=f'bind({var.name})', fixity='postfix', parameter=[self])
243
+
244
+ def mark(self, var: str) -> Syntax[MarkedResult[A], S]:
245
+ def bind_s(value: A) -> MarkedResult[A]:
246
+ if isinstance(value, MarkedResult):
247
+ return replace(value, name=var)
233
248
  else:
234
- return NamedResult(name=name, value=value)
235
- return self.map(bind_f).describe(name=f'bind("{name}")', fixity='postfix', parameter=[self])
249
+ return MarkedResult(name=var, value=value)
250
+ return self.map(bind_s).describe(name=f'bind("{var}")', fixity='postfix', parameter=[self])
251
+
236
252
 
237
253
 
238
254
  def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> Syntax[A, S]:
@@ -263,12 +279,6 @@ def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
263
279
  return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(None)
264
280
 
265
281
 
266
-
267
-
268
-
269
-
270
-
271
-
272
282
  def all(*parsers: Syntax[Any, S]) -> Syntax[ThenResult[Any, Any], S]:
273
283
  return reduce(lambda a, b: a + b, parsers) if len(parsers) > 0 else success(None)
274
284
 
@@ -278,13 +288,18 @@ def first(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
278
288
  def last(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
279
289
  return reduce(lambda a, b: a >> b, parsers) if len(parsers) > 0 else success(None)
280
290
 
281
- def named(* parsers: Syntax[Any, S] | Tuple[str, Syntax[Any, S]]) -> Syntax[Any, S]:
291
+ def bound(* parsers: Syntax[Any, S] | Tuple[str|Variable, Syntax[Any, S]]) -> Syntax[Any, S]:
282
292
  def is_named_parser(x: Any) -> bool:
283
- return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax)
293
+ return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], (str, Variable)) and isinstance(x[1], Syntax)
284
294
 
285
- def to_parser(x: Syntax[Any, S] | Tuple[str, Syntax[Any, S]])->Syntax[Any, S]:
286
- if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax):
287
- return x[1].bind(x[0])
295
+ def to_parser(x: Syntax[Any, S] | Tuple[str|Variable, Syntax[Any, S]])->Syntax[Any, S]:
296
+ if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], (str, Variable)) and isinstance(x[1], Syntax):
297
+ if isinstance(x[0], str):
298
+ return x[1].mark(x[0])
299
+ elif isinstance(x[0], Variable):
300
+ return x[1].bind(x[0])
301
+ else:
302
+ raise ValueError(f"Invalid variable type(must be str | Variable): {x[0]}", x)
288
303
  elif isinstance(x, Syntax):
289
304
  return x
290
305
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.20
3
+ Version: 0.1.22
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -0,0 +1,16 @@
1
+ syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ syncraft/algebra.py,sha256=Um8iMgVwlWcseQDkB_GqY-xmo0hR7zSw5j7oRRxrZhE,14122
3
+ syncraft/ast.py,sha256=TM18FkdFIBbxaaKgEvTvellrcXdkzMLLjLm7sRuC7FY,12985
4
+ syncraft/constraint.py,sha256=4io1HM_2y5SJzMoeVowAr0a-SDE2jR-7uhCcZpa_FlM,6219
5
+ syncraft/diagnostic.py,sha256=cgwcQnCcgrCRX3h-oGTDb5rcJAtitPV3LfH9eLvO93E,2837
6
+ syncraft/finder.py,sha256=QySZcIjWZFMbRxGJVlJDxK9cmIuNA5vnoE-vYwLJiqA,3005
7
+ syncraft/generator.py,sha256=6jfkSODx952OiN-oOJdNubvU2TzYhLzh3wibZV-ImbI,11625
8
+ syncraft/parser.py,sha256=nSQInixB4j7H22HV_9YdOXfRB5aCPnhQYvMC_N5jnlo,11538
9
+ syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ syncraft/sqlite3.py,sha256=Pq09IHZOwuWg5W82l9D1flzd36QV0TOHQpTJ5U02V8g,34701
11
+ syncraft/syntax.py,sha256=wwMPAfae5mH4ip3Q2JOkvFkccPfoVDkb-Tk5o0U-ecI,14843
12
+ syncraft-0.1.22.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
13
+ syncraft-0.1.22.dist-info/METADATA,sha256=C_pkVYRVDWfpj30cGXa3N4Izxw2kL-bYKf7F_s6MCAE,1381
14
+ syncraft-0.1.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ syncraft-0.1.22.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
16
+ syncraft-0.1.22.dist-info/RECORD,,
syncraft/cmd.py DELETED
@@ -1,61 +0,0 @@
1
- import subprocess
2
- import tempfile
3
- from typing import List, Tuple
4
- from dataclasses import dataclass, field
5
-
6
- @dataclass(frozen=True)
7
- class Sqlite3Result:
8
- stdout: Tuple[str, ...] = field(default_factory=tuple)
9
- stderr: Tuple[str, ...] = field(default_factory=tuple)
10
- schema: Tuple[str, ...] = field(default_factory=tuple)
11
-
12
-
13
- class SQLite3:
14
- def __init__(self, cmd: str = 'sqlite3'):
15
- self.cmd = cmd
16
-
17
-
18
- def exec(self, sql: str)->Sqlite3Result:
19
- with tempfile.NamedTemporaryFile(suffix=".db") as temp_db:
20
- process = subprocess.run(
21
- [self.cmd, temp_db.name],
22
- input=sql.encode(),
23
- stdout=subprocess.PIPE,
24
- stderr=subprocess.PIPE
25
- )
26
- stdout = process.stdout.decode('utf-8').strip().splitlines()
27
- stderr = process.stderr.decode('utf-8').strip().splitlines()
28
- schema_process = subprocess.run(
29
- [self.cmd, temp_db.name, '.schema'],
30
- stdout=subprocess.PIPE,
31
- stderr=subprocess.PIPE
32
- )
33
- schema = schema_process.stdout.decode('utf-8').strip().splitlines()
34
- return Sqlite3Result(
35
- stdout=tuple(stdout),
36
- stderr=tuple(stderr),
37
- schema=tuple(schema)
38
- )
39
-
40
-
41
- if __name__ == "__main__":
42
- sql = """
43
- DROP TABLE IF EXISTS test;
44
- DROP TABLE IF EXISTS error;
45
- DROP INDEX IF EXISTS idx_name;
46
- CREATE TABLE d.x (id INTEGER PRIMARY KEY, name TEXT);
47
-
48
- CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT);
49
- CREATE TABLE IF NOT EXISTS error (id INTEGER PRIMARY KEY, name TEXT);
50
- CREATE INDEX IF NOT EXISTS idx_name ON test(name);
51
- CREATE TRIGGER IF NOT EXISTS trg_test
52
- AFTER INSERT ON test
53
- BEGIN
54
- INSERT INTO error (name) VALUES ('Trigger Error');
55
- END;
56
- """
57
- result = SQLite3().exec(sql)
58
- print("STDOUT:", result.stdout)
59
- print("STDERR:", result.stderr)
60
- print("SCHEMA:", result.schema)
61
-
@@ -1,15 +0,0 @@
1
- syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- syncraft/algebra.py,sha256=utnhCbrF5GFG6P2Oef_Ru6YVgVCgsGccMh6wx80ok5E,20519
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/generator.py,sha256=ygt5HXfKG5z1LcTxhU8adqEePegqmokLjbP6pi34hGA,12355
7
- syncraft/parser.py,sha256=hk67kvOTBToOB2GhAh4weJQHR9xv73YPNQnqrW_qYv8,11295
8
- syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- syncraft/sqlite3.py,sha256=Pq09IHZOwuWg5W82l9D1flzd36QV0TOHQpTJ5U02V8g,34701
10
- syncraft/syntax.py,sha256=NtxkfPvSNsmpbRcESxEgRAW9d04ZHukWQoSJqIHsHNk,13842
11
- syncraft-0.1.20.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
12
- syncraft-0.1.20.dist-info/METADATA,sha256=j3Ylao4YeT_HxO4Gv95chaRvpoZLBallacoeoaoLQjk,1381
13
- syncraft-0.1.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- syncraft-0.1.20.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
15
- syncraft-0.1.20.dist-info/RECORD,,