syncraft 0.1.21__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,200 +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')
19
-
20
-
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
-
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
11
+ from syncraft.ast import ThenKind, ThenResult, ManyResult, OrResult, S
50
12
 
13
+ A = TypeVar('A') # Result type
14
+ B = TypeVar('B') # Mapped result type
15
+
51
16
 
52
-
53
- class StructuralResult:
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()
59
-
60
- @dataclass(frozen=True)
61
- class NamedResult(Generic[A], StructuralResult):
62
- name: str
63
- value: A
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)
83
- @dataclass(eq=True, frozen=True)
84
- class ManyResult(Generic[A], StructuralResult):
85
- value: Tuple[A, ...]
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))
106
-
107
- ret = Bimap(
108
- forward=fwd,
109
- inverse=inv
110
- )
111
- return before(self) >> ret >> after(self)
112
-
113
- @dataclass(eq=True, frozen=True)
114
- class OrResult(Generic[A], StructuralResult):
115
- value: A
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)
134
- class ThenKind(Enum):
135
- BOTH = '+'
136
- LEFT = '//'
137
- RIGHT = '>>'
138
-
139
- @dataclass(eq=True, frozen=True)
140
- class ThenResult(Generic[A, B], StructuralResult):
141
- kind: ThenKind
142
- left: A
143
- right: B
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
-
202
17
  InProgress = object() # Marker for in-progress state, used to prevent re-entrance in recursive calls
203
18
  L = TypeVar('L') # Left type for combined results
204
19
  R = TypeVar('R') # Right type for combined results
@@ -242,7 +57,14 @@ class Error:
242
57
  state=state,
243
58
  previous=self
244
59
  )
245
-
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
246
68
 
247
69
 
248
70
  @dataclass(frozen=True)
@@ -423,6 +245,12 @@ class Algebra(ABC, Generic[A, S]):
423
245
  return cast(Either[Any, Tuple[B, S]], parsed)
424
246
  return self.__class__(map_run, name=self.name) # type: ignore
425
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
+
426
254
  def map_error(self, f: Callable[[Optional[Any]], Any]) -> Algebra[A, S]:
427
255
  def map_error_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
428
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 CHANGED
@@ -1,96 +1,34 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from typing import (
4
- Any, Tuple, Optional, Callable,
5
- List, Generator as YieldGen
4
+ Any, Tuple, Optional, Generator as YieldGen
6
5
  )
7
- from dataclasses import dataclass
6
+ from dataclasses import dataclass, replace
8
7
  from syncraft.algebra import (
9
- Algebra, Either, Left, Right, Error,
10
- OrResult, ManyResult, ThenResult, NamedResult
8
+ Algebra, Either, Right,
9
+ OrResult, ManyResult, ThenResult, MarkedResult
11
10
  )
12
11
 
13
- from syncraft.ast import T, ParseResult, Token, AST
14
- from syncraft.generator import GenState, TokenGen, B
12
+ from syncraft.ast import T, ParseResult, AST
13
+ from syncraft.generator import GenState, Generator
15
14
  from sqlglot import TokenType
16
15
  from syncraft.syntax import Syntax
17
16
  import re
18
17
 
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
18
 
19
+ @dataclass(frozen=True)
20
+ class Finder(Generator[T]):
48
21
  def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[ParseResult[T]], GenState[T]]:
49
22
  assert at_least > 0, "at_least must be greater than 0"
50
23
  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
24
+ return self.map_state(lambda s: replace(s, is_pruned = False)).many(at_least=at_least, at_most=at_most)
75
25
 
76
26
 
77
27
  def or_else(self, # type: ignore
78
28
  other: Algebra[ParseResult[T], GenState[T]]
79
29
  ) -> 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
30
+ return self.map_state(lambda s: replace(s, is_pruned = False)).or_else(other)
31
+
94
32
 
95
33
  @classmethod
96
34
  def token(cls,
@@ -98,20 +36,12 @@ class Finder(Algebra[ParseResult[T], GenState[T]]):
98
36
  text: Optional[str] = None,
99
37
  case_sensitive: bool = False,
100
38
  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
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
+
115
45
 
116
46
  @classmethod
117
47
  def anything(cls)->Algebra[ParseResult[T], GenState[T]]:
@@ -141,7 +71,7 @@ def find(syntax: Syntax[Any, Any], data: AST[Any]) -> YieldGen[AST[Any], None, N
141
71
  case ManyResult(value = value):
142
72
  for e in value:
143
73
  yield from find(syntax, AST(e))
144
- case NamedResult(value=value):
74
+ case MarkedResult(value=value):
145
75
  yield from find(syntax, AST(value))
146
76
  case OrResult(value=value):
147
77
  yield from find(syntax, AST(value))
syncraft/generator.py CHANGED
@@ -11,7 +11,8 @@ from syncraft.algebra import (
11
11
  OrResult, ManyResult
12
12
  )
13
13
 
14
- from syncraft.ast import T, 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
@@ -23,9 +24,13 @@ B = TypeVar('B')
23
24
 
24
25
 
25
26
  @dataclass(frozen=True)
26
- class GenState(Generic[T]):
27
+ class GenState(Bindable, Generic[T]):
27
28
  ast: Optional[AST[T]]
28
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))
29
34
 
30
35
  def fork(self, tag: Any) -> GenState[T]:
31
36
  return replace(self, seed=hash((self.seed, tag)))
@@ -38,7 +43,10 @@ class GenState(Generic[T]):
38
43
 
39
44
  @cached_property
40
45
  def pruned(self)->bool:
41
- 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
42
50
 
43
51
 
44
52
  @property
@@ -167,7 +175,7 @@ class Generator(Algebra[ParseResult[T], GenState[T]]):
167
175
  state=original,
168
176
  error=e
169
177
  ))
170
- 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
171
179
 
172
180
 
173
181
  def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[ParseResult[T]], GenState[T]]:
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,13 +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
16
+ from syncraft.ast import Token, TokenSpec, AST, T, ParseResult, Binding, Variable, Bindable
17
17
 
18
18
 
19
19
  @dataclass(frozen=True)
20
- class ParserState(Generic[T]):
20
+ class ParserState(Bindable, Generic[T]):
21
21
  input: Tuple[T, ...] = field(default_factory=tuple)
22
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))
23
26
 
24
27
  def token_sample_string(self)-> str:
25
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,33 +17,9 @@ 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
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
20
+ S = TypeVar('S', bound=Bindable) # State type
21
+
22
+
45
23
 
46
24
 
47
25
  @dataclass(frozen=True)
@@ -157,11 +135,15 @@ class Syntax(Generic[A, S]):
157
135
  def map(self, f: Callable[[A], B]) -> Syntax[B, S]:
158
136
  return self.__class__(lambda cls: self.alg(cls).map(f), meta = self.meta) # type: ignore
159
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
+
160
141
  def map_error(self, f: Callable[[Optional[Any]], Any]) -> Syntax[A, S]:
161
142
  return self.__class__(lambda cls: self.alg(cls).map_error(f), meta=self.meta)
162
143
 
163
144
  def map_state(self, f: Callable[[S], S]) -> Syntax[A, S]:
164
145
  return self.__class__(lambda cls: self.alg(cls).map_state(f), meta=self.meta)
146
+
165
147
 
166
148
  def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
167
149
  return self.__class__(lambda cls: self.alg(cls).flat_map(f)) # type: ignore
@@ -251,16 +233,23 @@ class Syntax(Generic[A, S]):
251
233
 
252
234
 
253
235
  ######################################################################## data processing combinators #########################################################
254
- def bind(self, name: str) -> Syntax[NamedResult[A], S]:
255
- def bind_f(value: A) -> NamedResult[A]:
256
- if isinstance(value, NamedResult):
257
- 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)
258
248
  else:
259
- return NamedResult(name=name, value=value)
260
- 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
+
261
252
 
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])
264
253
 
265
254
  def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> Syntax[A, S]:
266
255
  def dump_error_run(err: Any)->Any:
@@ -299,13 +288,18 @@ def first(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
299
288
  def last(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
300
289
  return reduce(lambda a, b: a >> b, parsers) if len(parsers) > 0 else success(None)
301
290
 
302
- 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]:
303
292
  def is_named_parser(x: Any) -> bool:
304
- 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)
305
294
 
306
- def to_parser(x: Syntax[Any, S] | Tuple[str, Syntax[Any, S]])->Syntax[Any, S]:
307
- if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax):
308
- 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)
309
303
  elif isinstance(x, Syntax):
310
304
  return x
311
305
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.21
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,16 +0,0 @@
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,,