syncraft 0.1.38__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 +88 -115
- syncraft/syntax.py +26 -30
- {syncraft-0.1.38.dist-info → syncraft-0.2.0.dist-info}/METADATA +1 -1
- {syncraft-0.1.38.dist-info → syncraft-0.2.0.dist-info}/RECORD +7 -7
- {syncraft-0.1.38.dist-info → syncraft-0.2.0.dist-info}/WHEEL +0 -0
- {syncraft-0.1.38.dist-info → syncraft-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {syncraft-0.1.38.dist-info → syncraft-0.2.0.dist-info}/top_level.txt +0 -0
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[
|
|
102
|
-
def bind(self,
|
|
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((
|
|
43
|
+
new_binding.add((name, node))
|
|
105
44
|
return Binding(bindings=frozenset(new_binding))
|
|
106
45
|
|
|
107
|
-
def
|
|
46
|
+
def bound(self)->FrozenDict[str, Tuple[Any, ...]]:
|
|
108
47
|
ret = defaultdict(list)
|
|
109
|
-
for
|
|
110
|
-
ret[
|
|
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
|
-
|
|
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,
|
|
123
|
-
return replace(self, binding=self.binding.bind(
|
|
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[[
|
|
75
|
+
run_f: Callable[[FrozenDict[str, Tuple[Any, ...]]], ConstraintResult]
|
|
133
76
|
name: str = ""
|
|
134
|
-
def __call__(self, bound:
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
117
|
+
run_f=invert_run,
|
|
154
118
|
name=f"!({self.name})"
|
|
155
119
|
)
|
|
156
120
|
|
|
157
121
|
@classmethod
|
|
158
|
-
def predicate(cls,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
@@ -7,12 +7,17 @@ from typing import (
|
|
|
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
|
|
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,27 +254,18 @@ class Syntax(Generic[A, S]):
|
|
|
249
254
|
return self.optional()
|
|
250
255
|
|
|
251
256
|
|
|
252
|
-
######################################################################## data processing combinators #########################################################
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
collector: None = None)-> Syntax[A | Marked[A], S]: ...
|
|
257
|
-
|
|
258
|
-
@overload
|
|
259
|
-
def bind(self,
|
|
260
|
-
var: Variable,
|
|
261
|
-
collector: Type[E]) -> Syntax[Collect[A, E] | Marked[Collect[A, E]], S]: ...
|
|
262
|
-
|
|
263
|
-
def bind(self,
|
|
264
|
-
var: Variable,
|
|
265
|
-
collector: Optional[Type[E]]=None) -> Syntax[Any, S]:
|
|
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}"
|
|
266
261
|
def bind_v(v: Any, s: S)->Tuple[Any, S]:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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,))
|
|
273
269
|
|
|
274
270
|
def to(self, f: Collector[E])-> Syntax[Collect[A, E], S]:
|
|
275
271
|
def to_f(v: A) -> Collect[A, E]:
|
|
@@ -283,15 +279,16 @@ class Syntax(Generic[A, S]):
|
|
|
283
279
|
|
|
284
280
|
|
|
285
281
|
def mark(self, name: str) -> Syntax[Marked[A], S]:
|
|
286
|
-
|
|
282
|
+
assert valid_name(name), f"Invalid mark name: {name}"
|
|
283
|
+
def mark_s(value: A) -> Marked[A]:
|
|
287
284
|
if isinstance(value, Marked):
|
|
288
285
|
return replace(value, name=name)
|
|
289
286
|
else:
|
|
290
287
|
return Marked(name=name, value=value)
|
|
291
|
-
def
|
|
288
|
+
def imark_s(m : Marked[A]) -> A:
|
|
292
289
|
return m.value if isinstance(m, Marked) else m
|
|
293
290
|
|
|
294
|
-
return self.bimap(
|
|
291
|
+
return self.bimap(mark_s, imark_s).describe(name=f'mark("{name}")', fixity='postfix', parameter=(self,))
|
|
295
292
|
|
|
296
293
|
|
|
297
294
|
|
|
@@ -332,15 +329,14 @@ def first(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
|
332
329
|
def last(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
|
|
333
330
|
return reduce(lambda a, b: a >> b, parsers) if len(parsers) > 0 else success(Nothing())
|
|
334
331
|
|
|
335
|
-
def
|
|
332
|
+
def bind(* parsers: Syntax[Any, S] | Tuple[str, Syntax[Any, S]]) -> Syntax[Any, S]:
|
|
336
333
|
def is_named_parser(x: Any) -> bool:
|
|
337
|
-
return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0],
|
|
334
|
+
return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax)
|
|
338
335
|
|
|
339
|
-
def to_parser(x: Syntax[Any, S] | Tuple[str
|
|
340
|
-
if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0],
|
|
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):
|
|
341
338
|
if isinstance(x[0], str):
|
|
342
|
-
|
|
343
|
-
elif isinstance(x[0], Variable):
|
|
339
|
+
|
|
344
340
|
return x[1].bind(x[0])
|
|
345
341
|
else:
|
|
346
342
|
raise ValueError(f"Invalid variable type(must be str | Variable): {x[0]}", x)
|
|
@@ -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=
|
|
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=
|
|
12
|
-
syncraft-0.
|
|
13
|
-
syncraft-0.
|
|
14
|
-
syncraft-0.
|
|
15
|
-
syncraft-0.
|
|
16
|
-
syncraft-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|