syncraft 0.1.37__py3-none-any.whl → 0.2.0__py3-none-any.whl

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

Potentially problematic release.


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

syncraft/constraint.py CHANGED
@@ -5,6 +5,7 @@ from dataclasses import dataclass, field, replace
5
5
  import collections.abc
6
6
  from collections import defaultdict
7
7
  from itertools import product
8
+ import inspect
8
9
 
9
10
  K = TypeVar('K')
10
11
  V = TypeVar('V')
@@ -33,85 +34,23 @@ class FrozenDict(collections.abc.Mapping, Generic[K, V]):
33
34
 
34
35
  def __repr__(self):
35
36
  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
- if self._root is None:
59
- raise ValueError("_rawf can not be None")
60
- return b.get(self._root, ())
61
-
62
-
63
- def map(self, f: Callable[[Any], Any]) -> "Variable":
64
- if self._mapf is None:
65
- return replace(self, _mapf=f)
66
- else:
67
- oldf = self._mapf
68
- return replace(self, _mapf=lambda a: f(oldf(a)))
69
37
 
70
- def get(self, b: 'BoundVar') -> Tuple[Any, ...]:
71
- vals = self.raw(b)
72
- if self._mapf is not None:
73
- return tuple(self._mapf(v) for v in vals)
74
- else:
75
- return vals
76
-
77
- def __call__(self, b:'BoundVar', raw:bool=False) -> Any:
78
- if raw:
79
- return self.raw(b)
80
- else:
81
- return self.get(b)
82
-
83
- def __eq__(self, other):
84
- return Expr(self, '==', other)
85
- def __ne__(self, other):
86
- return Expr(self, '!=', other)
87
- def __lt__(self, other):
88
- return Expr(self, '<', other)
89
- def __le__(self, other):
90
- return Expr(self, '<=', other)
91
- def __gt__(self, other):
92
- return Expr(self, '>', other)
93
- def __ge__(self, other):
94
- return Expr(self, '>=', other)
95
-
96
- BoundVar = FrozenDict[Variable, Tuple[Any, ...]]
97
-
98
-
99
38
  @dataclass(frozen=True)
100
39
  class Binding:
101
- bindings : frozenset[Tuple[Variable, Any]] = frozenset()
102
- def bind(self, var: Variable, node: Any) -> Binding:
40
+ bindings : frozenset[Tuple[str, Any]] = frozenset()
41
+ def bind(self, name: str, node: Any) -> Binding:
103
42
  new_binding = set(self.bindings)
104
- new_binding.add((var, node))
43
+ new_binding.add((name, node))
105
44
  return Binding(bindings=frozenset(new_binding))
106
45
 
107
- def to_dict(self)->BoundVar:
46
+ def bound(self)->FrozenDict[str, Tuple[Any, ...]]:
108
47
  ret = defaultdict(list)
109
- for var, node in self.bindings:
110
- ret[var].append(node)
48
+ for name, node in self.bindings:
49
+ ret[name].append(node)
111
50
  return FrozenDict({k: tuple(vs) for k, vs in ret.items()})
112
51
 
113
52
 
114
- A = TypeVar('A')
53
+
115
54
  @dataclass(frozen=True)
116
55
  class Bindable:
117
56
  binding: Binding = field(default_factory=Binding)
@@ -119,8 +58,8 @@ class Bindable:
119
58
  def map(self, f: Callable[[Any], Any])->Self:
120
59
  return self
121
60
 
122
- def bind(self, var: Variable, node:Any)->Self:
123
- return replace(self, binding=self.binding.bind(var, node))
61
+ def bind(self, name: str, node:Any)->Self:
62
+ return replace(self, binding=self.binding.bind(name, node))
124
63
 
125
64
 
126
65
  class Quantifier(Enum):
@@ -128,71 +67,105 @@ class Quantifier(Enum):
128
67
  EXISTS = "exists"
129
68
 
130
69
  @dataclass(frozen=True)
70
+ class ConstraintResult:
71
+ result: bool
72
+ unbound: frozenset[str] = frozenset()
73
+ @dataclass(frozen=True)
131
74
  class Constraint:
132
- run_f: Callable[[BoundVar], bool]
75
+ run_f: Callable[[FrozenDict[str, Tuple[Any, ...]]], ConstraintResult]
133
76
  name: str = ""
134
- def __call__(self, bound: BoundVar)->bool:
77
+ def __call__(self, bound: FrozenDict[str, Tuple[Any, ...]])->ConstraintResult:
135
78
  return self.run_f(bound)
136
79
  def __and__(self, other: Constraint) -> Constraint:
80
+ def and_run(bound: FrozenDict[str, Tuple[Any, ...]]) -> ConstraintResult:
81
+ res1 = self(bound)
82
+ res2 = other(bound)
83
+ combined_result = res1.result and res2.result
84
+ combined_unbound = res1.unbound.union(res2.unbound)
85
+ return ConstraintResult(result=combined_result, unbound=combined_unbound)
137
86
  return Constraint(
138
- run_f=lambda bound: self(bound) and other(bound),
87
+ run_f=and_run,
139
88
  name=f"({self.name} && {other.name})"
140
89
  )
141
90
  def __or__(self, other: Constraint) -> Constraint:
91
+ def or_run(bound: FrozenDict[str, Tuple[Any, ...]]) -> ConstraintResult:
92
+ res1 = self(bound)
93
+ res2 = other(bound)
94
+ combined_result = res1.result or res2.result
95
+ combined_unbound = res1.unbound.union(res2.unbound)
96
+ return ConstraintResult(result=combined_result, unbound=combined_unbound)
142
97
  return Constraint(
143
- run_f=lambda bound: self(bound) or other(bound),
98
+ run_f=or_run,
144
99
  name=f"({self.name} || {other.name})"
145
100
  )
146
101
  def __xor__(self, other: Constraint) -> Constraint:
102
+ def xor_run(bound: FrozenDict[str, Tuple[Any, ...]]) -> ConstraintResult:
103
+ res1 = self(bound)
104
+ res2 = other(bound)
105
+ combined_result = res1.result ^ res2.result
106
+ combined_unbound = res1.unbound.union(res2.unbound)
107
+ return ConstraintResult(result=combined_result, unbound=combined_unbound)
147
108
  return Constraint(
148
- run_f=lambda bound: self(bound) ^ other(bound),
109
+ run_f=xor_run,
149
110
  name=f"({self.name} ^ {other.name})"
150
111
  )
151
112
  def __invert__(self) -> Constraint:
113
+ def invert_run(bound: FrozenDict[str, Tuple[Any, ...]]) -> ConstraintResult:
114
+ res = self(bound)
115
+ return ConstraintResult(result=not res.result, unbound=res.unbound)
152
116
  return Constraint(
153
- run_f=lambda bound: not self(bound),
117
+ run_f=invert_run,
154
118
  name=f"!({self.name})"
155
119
  )
156
120
 
157
121
  @classmethod
158
- def predicate(cls, f: Callable[..., bool],*, name: Optional[str] = None, quant: Quantifier = Quantifier.FORALL)->Callable[..., Constraint]:
159
- def wrapper(*args: Any, **kwargs:Any) -> Constraint:
160
- arg_list = list(args)
161
- kw_list = [(k, v) for k, v in kwargs.items()]
162
- def run_f(bound: BoundVar) -> bool:
163
- # positional argument values
164
- pos_values = [
165
- arg.get(bound) if isinstance(arg, Variable) else (arg,)
166
- for arg in arg_list
167
- ]
168
- # keyword argument values
169
- kw_keys, kw_values = zip(*[
170
- (k, v.get(bound) if isinstance(v, Variable) else (v,))
171
- for k, v in kw_list
172
- ]) if kw_list else ([], [])
173
-
174
- # Cartesian product over all argument values
175
- all_combos = product(*pos_values, *kw_values)
176
-
177
- # evaluate predicate on each combination
178
- def eval_combo(combo):
179
- pos_args = combo[:len(pos_values)]
180
- kw_args = dict(zip(kw_keys, combo[len(pos_values):]))
181
- return f(*pos_args, **kw_args)
182
-
183
- if quant is Quantifier.EXISTS:
184
- return any(eval_combo(c) for c in all_combos)
185
- else:
186
- return all(eval_combo(c) for c in all_combos)
187
- return cls(run_f=run_f, name = name or f.__name__)
188
- return wrapper
189
-
190
- @classmethod
191
- def forall(cls, f: Callable[..., bool], name: Optional[str] = None) -> Callable[..., Constraint]:
192
- return cls.predicate(f, name=name, quant=Quantifier.FORALL)
122
+ def predicate(cls,
123
+ f: Callable[..., bool],
124
+ *,
125
+ name: Optional[str] = None,
126
+ quant: Quantifier = Quantifier.FORALL)->Constraint:
127
+ sig = inspect.signature(f)
128
+ pos_params = []
129
+ kw_params = []
130
+ for pname, param in sig.parameters.items():
131
+ if param.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD):
132
+ pos_params.append(pname)
133
+ elif param.kind == inspect.Parameter.KEYWORD_ONLY:
134
+ kw_params.append(pname)
135
+ else:
136
+ raise TypeError(f"Unsupported parameter kind: {param.kind}")
137
+ def run_f(bound: FrozenDict[str, Tuple[Any, ...]]) -> ConstraintResult:
138
+ # positional argument values
139
+ pos_values = [bound.get(pname, ()) for pname in pos_params]
140
+ # keyword argument values
141
+ kw_values = [bound.get(pname, ()) for pname in kw_params]
142
+
143
+ # If any param is unbound, fail
144
+ all_params = pos_params + kw_params
145
+ all_values = pos_values + kw_values
146
+ unbound_args = [p for p, vs in zip(all_params, all_values) if not vs]
147
+ if unbound_args:
148
+ return ConstraintResult(result=quant is Quantifier.FORALL, unbound=frozenset(unbound_args))
149
+
150
+ # Cartesian product
151
+ all_combos = product(*pos_values, *kw_values)
152
+
153
+ def eval_combo(combo):
154
+ pos_args = combo[: len(pos_values)]
155
+ kw_args = dict(zip(kw_params, combo[len(pos_values) :]))
156
+ return f(*pos_args, **kw_args)
157
+
158
+ if quant is Quantifier.EXISTS:
159
+ return ConstraintResult(result = any(eval_combo(c) for c in all_combos), unbound=frozenset())
160
+ else:
161
+ return ConstraintResult(result = all(eval_combo(c) for c in all_combos), unbound=frozenset())
162
+
163
+ return cls(run_f=run_f, name=name or f.__name__)
164
+
165
+ def forall(f: Callable[..., bool], name: Optional[str] = None) -> Constraint:
166
+ return Constraint.predicate(f, name=name, quant=Quantifier.FORALL)
193
167
 
194
- @classmethod
195
- def exists(cls, f: Callable[..., bool], name: Optional[str] = None):
196
- return cls.predicate(f, name=name, quant=Quantifier.EXISTS)
168
+ def exists(f: Callable[..., bool], name: Optional[str] = None) -> Constraint:
169
+ return Constraint.predicate(f, name=name, quant=Quantifier.EXISTS)
197
170
 
198
171
 
syncraft/syntax.py CHANGED
@@ -2,17 +2,22 @@ from __future__ import annotations
2
2
 
3
3
  from typing import (
4
4
  Optional, Any, TypeVar, Generic, Callable, Tuple, cast,
5
- Type, Literal, List
5
+ Type, Literal, List, overload
6
6
  )
7
7
  from dataclasses import dataclass, field, replace
8
8
  from functools import reduce
9
9
  from syncraft.algebra import Algebra, Error, Either, Right
10
- from syncraft.constraint import Variable, Bindable
10
+ from syncraft.constraint import Bindable
11
11
  from syncraft.ast import Then, ThenKind, Marked, Choice, Many, ChoiceKind, Nothing, Collect, E, Collector
12
12
  from types import MethodType, FunctionType
13
+ import keyword
13
14
 
14
15
  from rich import print
15
16
 
17
+ def valid_name(name: str) -> bool:
18
+ return (name.isidentifier()
19
+ and not keyword.iskeyword(name)
20
+ and not (name.startswith('__') and name.endswith('__')))
16
21
 
17
22
  A = TypeVar('A') # Result type
18
23
  B = TypeVar('B') # Result type for mapping
@@ -249,21 +254,18 @@ class Syntax(Generic[A, S]):
249
254
  return self.optional()
250
255
 
251
256
 
252
- ######################################################################## data processing combinators #########################################################
253
- def bind(self,
254
- var: Variable,
255
- collector: Optional[Type[E]]=None) -> Syntax[A |
256
- Marked[A] |
257
- Marked[Collect[A, E]] |
258
- Collect[A, E], S]:
259
- def bind_v(v: A | Marked[A] | Marked[Collect[A, E]] | Collect[A, E],
260
- s: S)->Tuple[A | Marked[A] | Marked[Collect[A, E]] | Collect[A, E], S]:
261
- return v, s.bind(var, v)
262
- if callable(collector):
263
- ret = self.to(collector).mark(var.name).map_all(bind_v) if var.name else self.to(collector).map_all(bind_v)
264
- else:
265
- ret = self.mark(var.name).map_all(bind_v) if var.name else self.map_all(bind_v)
266
- return ret.describe(name=f'bind({var.name})', fixity='postfix', parameter=(self,))
257
+ ######################################################################## data processing combinators #########################################################
258
+ def bind(self, name: Optional[str] = None) -> Syntax[A, S]:
259
+ if name:
260
+ assert valid_name(name), f"Invalid mark name: {name}"
261
+ def bind_v(v: Any, s: S)->Tuple[Any, S]:
262
+ if name:
263
+ return v, s.bind(name, v)
264
+ elif isinstance(v, Marked):
265
+ return v.value, s.bind(v.name, v.value)
266
+ else:
267
+ return v, s
268
+ return self.map_all(bind_v).describe(name=f'bind({name})', fixity='postfix', parameter=(self,))
267
269
 
268
270
  def to(self, f: Collector[E])-> Syntax[Collect[A, E], S]:
269
271
  def to_f(v: A) -> Collect[A, E]:
@@ -277,15 +279,16 @@ class Syntax(Generic[A, S]):
277
279
 
278
280
 
279
281
  def mark(self, name: str) -> Syntax[Marked[A], S]:
280
- def bind_s(value: A) -> Marked[A]:
282
+ assert valid_name(name), f"Invalid mark name: {name}"
283
+ def mark_s(value: A) -> Marked[A]:
281
284
  if isinstance(value, Marked):
282
285
  return replace(value, name=name)
283
286
  else:
284
287
  return Marked(name=name, value=value)
285
- def ibind_s(m : Marked[A]) -> A:
288
+ def imark_s(m : Marked[A]) -> A:
286
289
  return m.value if isinstance(m, Marked) else m
287
290
 
288
- return self.bimap(bind_s, ibind_s).describe(name=f'bind("{name}")', fixity='postfix', parameter=(self,))
291
+ return self.bimap(mark_s, imark_s).describe(name=f'mark("{name}")', fixity='postfix', parameter=(self,))
289
292
 
290
293
 
291
294
 
@@ -326,15 +329,14 @@ def first(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
326
329
  def last(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
327
330
  return reduce(lambda a, b: a >> b, parsers) if len(parsers) > 0 else success(Nothing())
328
331
 
329
- def bound(* parsers: Syntax[Any, S] | Tuple[str|Variable, Syntax[Any, S]]) -> Syntax[Any, S]:
332
+ def bind(* parsers: Syntax[Any, S] | Tuple[str, Syntax[Any, S]]) -> Syntax[Any, S]:
330
333
  def is_named_parser(x: Any) -> bool:
331
- return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], (str, Variable)) and isinstance(x[1], Syntax)
334
+ return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax)
332
335
 
333
- def to_parser(x: Syntax[Any, S] | Tuple[str|Variable, Syntax[Any, S]])->Syntax[Any, S]:
334
- if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], (str, Variable)) and isinstance(x[1], Syntax):
336
+ def to_parser(x: Syntax[Any, S] | Tuple[str, Syntax[Any, S]])->Syntax[Any, S]:
337
+ if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax):
335
338
  if isinstance(x[0], str):
336
- return x[1].mark(x[0])
337
- elif isinstance(x[0], Variable):
339
+
338
340
  return x[1].bind(x[0])
339
341
  else:
340
342
  raise ValueError(f"Invalid variable type(must be str | Variable): {x[0]}", x)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.37
3
+ Version: 0.2.0
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -1,16 +1,16 @@
1
1
  syncraft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  syncraft/algebra.py,sha256=U28UIKK4oh2yIuvOqKYeo4_lDkX3Sy_nHSaHnIabcbk,15769
3
3
  syncraft/ast.py,sha256=TAHj8IOgx_QtlI4zxFr2DRGZ4dwNGsb6TiH0TH9adIg,14556
4
- syncraft/constraint.py,sha256=eTXINghioXmJzAtW3jM-Q_TW2LYXu6mUDq34KlI_x-w,6440
4
+ syncraft/constraint.py,sha256=9VvnZtL3Q63qnU-j3cgTQm8cMrIXdgSgl4qIEF-YCx4,6509
5
5
  syncraft/diagnostic.py,sha256=cgwcQnCcgrCRX3h-oGTDb5rcJAtitPV3LfH9eLvO93E,2837
6
6
  syncraft/finder.py,sha256=Wr7wiBuO9IaXBmYBA4DNXmoeEWteRIp-UetnuRScapM,1920
7
7
  syncraft/generator.py,sha256=C0vpJotp982anrSXq1-EdjQU_OMI_vDCa7uTdJK8M5M,12987
8
8
  syncraft/parser.py,sha256=RkQwFv00rOI-n4kvG4baGVTdK46T-_Hw7OJ0FB7g72g,11379
9
9
  syncraft/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  syncraft/sqlite3.py,sha256=Pq09IHZOwuWg5W82l9D1flzd36QV0TOHQpTJ5U02V8g,34701
11
- syncraft/syntax.py,sha256=kZceLSgq2E2ViTcJ6Q6yp6nJ7bSKUFukshTbpmnzcCs,17762
12
- syncraft-0.1.37.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
13
- syncraft-0.1.37.dist-info/METADATA,sha256=qc8PAP9GH5LAMCc4h5g5qHFPVHpnJV3aB9MqDoDZuwU,989
14
- syncraft-0.1.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- syncraft-0.1.37.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
16
- syncraft-0.1.37.dist-info/RECORD,,
11
+ syncraft/syntax.py,sha256=yuU1_c160RWhxGLZUo2jjgba99CU84VaWCP8Cy86I_I,17487
12
+ syncraft-0.2.0.dist-info/licenses/LICENSE,sha256=wHSV424U5csa3339dy1AZbsz2xsd0hrkMx2QK48CcUk,1062
13
+ syncraft-0.2.0.dist-info/METADATA,sha256=vzYdcSzl69EHcv8-3s3Od-OdlA1EDI8yqlU8l6zESmo,988
14
+ syncraft-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ syncraft-0.2.0.dist-info/top_level.txt,sha256=Kq3t8ESXB2xW1Xt3uPmkENFc-c4f2pamNmaURBk7zc8,9
16
+ syncraft-0.2.0.dist-info/RECORD,,