syncraft 0.1.29__tar.gz → 0.1.31__tar.gz

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.

Files changed (24) hide show
  1. {syncraft-0.1.29 → syncraft-0.1.31}/PKG-INFO +1 -3
  2. {syncraft-0.1.29 → syncraft-0.1.31}/README.md +0 -2
  3. {syncraft-0.1.29 → syncraft-0.1.31}/pyproject.toml +1 -1
  4. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/algebra.py +6 -3
  5. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/ast.py +43 -32
  6. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/syntax.py +22 -13
  7. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft.egg-info/PKG-INFO +1 -3
  8. {syncraft-0.1.29 → syncraft-0.1.31}/tests/test_bimap.py +34 -48
  9. {syncraft-0.1.29 → syncraft-0.1.31}/tests/test_parse.py +6 -0
  10. {syncraft-0.1.29 → syncraft-0.1.31}/LICENSE +0 -0
  11. {syncraft-0.1.29 → syncraft-0.1.31}/setup.cfg +0 -0
  12. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/__init__.py +0 -0
  13. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/constraint.py +0 -0
  14. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/diagnostic.py +0 -0
  15. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/finder.py +0 -0
  16. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/generator.py +0 -0
  17. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/parser.py +0 -0
  18. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/py.typed +0 -0
  19. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft/sqlite3.py +0 -0
  20. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft.egg-info/SOURCES.txt +0 -0
  21. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft.egg-info/dependency_links.txt +0 -0
  22. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft.egg-info/requires.txt +0 -0
  23. {syncraft-0.1.29 → syncraft-0.1.31}/syncraft.egg-info/top_level.txt +0 -0
  24. {syncraft-0.1.29 → syncraft-0.1.31}/tests/test_until.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.29
3
+ Version: 0.1.31
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -29,8 +29,6 @@ pip install syncraft
29
29
 
30
30
 
31
31
  ## TODO
32
- - [ ] simplify the result of then_left and then_right by bimap the result in syntax.
33
- - [ ] simplify the result of sep_by and between by bimap the result in syntax
34
32
  - [ ] convert to dict/dataclass via bimap in syntax
35
33
  - [ ] define DSL over Variable to construct predicates
36
34
  - [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
@@ -14,8 +14,6 @@ pip install syncraft
14
14
 
15
15
 
16
16
  ## TODO
17
- - [ ] simplify the result of then_left and then_right by bimap the result in syntax.
18
- - [ ] simplify the result of sep_by and between by bimap the result in syntax
19
17
  - [ ] convert to dict/dataclass via bimap in syntax
20
18
  - [ ] define DSL over Variable to construct predicates
21
19
  - [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "syncraft"
3
- version = "0.1.29"
3
+ version = "0.1.31"
4
4
  description = "Parser combinator library"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE"]
@@ -5,12 +5,15 @@ from typing import (
5
5
  )
6
6
 
7
7
  import traceback
8
- from dataclasses import dataclass, replace, asdict
8
+ from dataclasses import dataclass, replace
9
9
  from weakref import WeakKeyDictionary
10
10
  from abc import ABC
11
- from syncraft.ast import ThenKind, Then, Choice, Many, ChoiceKind
11
+ from syncraft.ast import ThenKind, Then, Choice, Many, ChoiceKind, shallow_dict
12
12
  from syncraft.constraint import Bindable
13
13
 
14
+
15
+
16
+
14
17
  S = TypeVar('S', bound=Bindable)
15
18
 
16
19
  A = TypeVar('A') # Result type
@@ -64,7 +67,7 @@ class Error:
64
67
  lst = []
65
68
  current: Optional[Error] = self
66
69
  while current is not None:
67
- d = asdict(current)
70
+ d = shallow_dict(current)
68
71
  lst.append({k:v for k,v in d.items() if v is not None and k != 'previous'})
69
72
  current = current.previous
70
73
  return lst
@@ -4,14 +4,20 @@ from __future__ import annotations
4
4
  import re
5
5
  from typing import (
6
6
  Optional, Any, TypeVar, Tuple, runtime_checkable, cast,
7
- Generic, Callable, Union, Protocol, Type, List, ClassVar
7
+ Generic, Callable, Union, Protocol, Type, List, ClassVar,
8
+ Dict
8
9
  )
9
10
 
10
11
 
11
- from dataclasses import dataclass, replace, is_dataclass, asdict, fields
12
+ from dataclasses import dataclass, replace, is_dataclass, fields
12
13
  from enum import Enum
13
14
 
14
15
 
16
+ def shallow_dict(a: Any)->Dict[str, Any]:
17
+ assert is_dataclass(a), f"Expected dataclass instance for collector inverse, got {type(a)}"
18
+ return {f.name: getattr(a, f.name) for f in fields(a)}
19
+
20
+
15
21
 
16
22
 
17
23
  A = TypeVar('A')
@@ -287,37 +293,42 @@ class Collect(Generic[A, E], AST):
287
293
  collector: Collector
288
294
  value: A
289
295
  def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[B | E, Callable[[B | E], Collect[A, E]]]:
290
- b, inner_f = r(self.value)
291
- if isinstance(self.value, Then) and self.value.kind == ThenKind.BOTH:
292
- assert isinstance(b, tuple), f"Expected tuple from Then.BOTH combinator, got {type(b)}"
293
- index: List[str | int] = []
294
- named_count = 0
295
- for i, v in enumerate(b):
296
- if isinstance(v, Marked):
297
- index.append(v.name)
298
- named_count += 1
299
- else:
300
- index.append(i - named_count)
301
- named = {v.name: v.value for v in b if isinstance(v, Marked)}
302
- unnamed = [v for v in b if not isinstance(v, Marked)]
303
- ret: E = self.collector(*unnamed, **named)
304
- def invf(e: E) -> Tuple[Any, ...]:
305
- assert is_dataclass(e), f"Expected dataclass instance for collector inverse, got {type(e)}"
306
- named_dict = asdict(e)
307
- unnamed = []
308
- for f in fields(e):
309
- if f.name not in named:
310
- unnamed.append(named_dict[f.name])
311
- tmp = []
312
- for x in index:
313
- if isinstance(x, str):
314
- tmp.append(Marked(name=x, value=named_dict[x]))
296
+
297
+ def inv_one_positional(e: E) -> B:
298
+ assert is_dataclass(e), f"Expected dataclass instance for collector inverse, got {type(e)}"
299
+ named_dict = shallow_dict(e)
300
+ return named_dict[fields(e)[0].name]
301
+
302
+ b, inner_f = self.value.bimap(r) if isinstance(self.value, AST) else r(self.value)
303
+ if isinstance(self.value, Then):
304
+ if isinstance(b, tuple):
305
+ index: List[str | int] = []
306
+ named_count = 0
307
+ for i, v in enumerate(b):
308
+ if isinstance(v, Marked):
309
+ index.append(v.name)
310
+ named_count += 1
315
311
  else:
316
- tmp.append(unnamed[x])
317
- return tuple(tmp)
318
- return ret, lambda e: replace(self, value=inner_f(invf(e))) # type: ignore
319
- else:
320
- return b, lambda e: replace(self, value=inner_f(e)) # type: ignore
312
+ index.append(i - named_count)
313
+ named = {v.name: v.value for v in b if isinstance(v, Marked)}
314
+ unnamed = [v for v in b if not isinstance(v, Marked)]
315
+ ret: E = self.collector(*unnamed, **named)
316
+ def invf(e: E) -> Tuple[Any, ...]:
317
+ assert is_dataclass(e), f"Expected dataclass instance for collector inverse, got {type(e)}"
318
+ named_dict = shallow_dict(e)
319
+ unnamed = []
320
+ for f in fields(e):
321
+ if f.name not in named:
322
+ unnamed.append(named_dict[f.name])
323
+ tmp = []
324
+ for x in index:
325
+ if isinstance(x, str):
326
+ tmp.append(Marked(name=x, value=named_dict[x]))
327
+ else:
328
+ tmp.append(unnamed[x])
329
+ return tuple(tmp)
330
+ return ret, lambda e: replace(self, value=inner_f(invf(e))) # type: ignore
331
+ return self.collector(b), lambda e: replace(self, value=inner_f(inv_one_positional(e))) # type: ignore
321
332
 
322
333
  #########################################################################################################################
323
334
  @dataclass(frozen=True)
@@ -165,9 +165,24 @@ class Syntax(Generic[A, S]):
165
165
  def between(self, left: Syntax[B, S], right: Syntax[C, S]) -> Syntax[Then[B, Then[A, C]], S]:
166
166
  return left >> self // right
167
167
 
168
- def sep_by(self, sep: Syntax[B, S]) -> Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S]:
168
+ def sep_by(self,
169
+ sep: Syntax[B, S]) -> Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S]:
169
170
  ret: Syntax[Then[A, Choice[Many[Then[B, A]], Optional[Nothing]]], S] = (self + (sep >> self).many().optional())
170
- return ret.describe(
171
+ def f(a: Then[A, Choice[Many[A], Optional[Nothing]]]) -> Many[A]:
172
+ if a.right.kind == ChoiceKind.LEFT and isinstance(a.right.value, Many):
173
+ if len(a.right.value.value) == 0:
174
+ return Many(value = (a.left,))
175
+ else:
176
+ return Many(value = (a.left,) + a.right.value.value)
177
+ else:
178
+ return Many(value = (a.left,))
179
+ def i(a: Many[A]) -> Then[A, Choice[Many[A], Optional[Nothing]]]:
180
+ assert len(a.value) >= 1, f"sep_by expect at least one element, got {len(a.value)}. {a}"
181
+ if len(a.value) == 1:
182
+ return Then(kind=ThenKind.BOTH, left=a.value[0], right=Choice(kind=ChoiceKind.RIGHT, value=Nothing()))
183
+ else:
184
+ return Then(kind= ThenKind.BOTH, left=a.value[0], right=Choice(kind=ChoiceKind.LEFT, value=Many(value=a.value[1:])))
185
+ return ret.bimap(f,i).describe( # type: ignore
171
186
  name='sep_by',
172
187
  fixity='prefix',
173
188
  parameter=(self, sep))
@@ -191,17 +206,11 @@ class Syntax(Generic[A, S]):
191
206
  other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
192
207
  ret: Syntax[Then[A, B], S] = self.__class__(lambda cls: self.alg(cls).then_left(other.alg(cls))) # type: ignore
193
208
  return ret.describe(name=ThenKind.LEFT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
194
-
195
- def __lshift__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
196
- return self.__floordiv__(other)
197
-
209
+
198
210
  def __rfloordiv__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
199
211
  other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
200
212
  return other.__floordiv__(self)
201
213
 
202
- def __rlshift__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
203
- return self.__rfloordiv__(other)
204
-
205
214
  def __add__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
206
215
  other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
207
216
  ret: Syntax[Then[A, B], S] = self.__class__(lambda cls: self.alg(cls).then_both(other.alg(cls))) # type: ignore
@@ -260,16 +269,16 @@ class Syntax(Generic[A, S]):
260
269
  return self.bimap(to_f, ito_f).describe(name=f'to({f})', fixity='postfix', parameter=(self,))
261
270
 
262
271
 
263
- def mark(self, var: str) -> Syntax[Marked[A], S]:
272
+ def mark(self, name: str) -> Syntax[Marked[A], S]:
264
273
  def bind_s(value: A) -> Marked[A]:
265
274
  if isinstance(value, Marked):
266
- return replace(value, name=var)
275
+ return replace(value, name=name)
267
276
  else:
268
- return Marked(name=var, value=value)
277
+ return Marked(name=name, value=value)
269
278
  def ibind_s(m : Marked[A]) -> A:
270
279
  return m.value if isinstance(m, Marked) else m
271
280
 
272
- return self.bimap(bind_s, ibind_s).describe(name=f'bind("{var}")', fixity='postfix', parameter=(self,))
281
+ return self.bimap(bind_s, ibind_s).describe(name=f'bind("{name}")', fixity='postfix', parameter=(self,))
273
282
 
274
283
 
275
284
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.29
3
+ Version: 0.1.31
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -29,8 +29,6 @@ pip install syncraft
29
29
 
30
30
 
31
31
  ## TODO
32
- - [ ] simplify the result of then_left and then_right by bimap the result in syntax.
33
- - [ ] simplify the result of sep_by and between by bimap the result in syntax
34
32
  - [ ] convert to dict/dataclass via bimap in syntax
35
33
  - [ ] define DSL over Variable to construct predicates
36
34
  - [ ] Try the parsing, generation, and data processing machinery on SQLite3 syntax. So that I can have direct feedback on the usability of this library and a fully functional SQLite3 library.
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from syncraft.ast import Then, ThenKind, Many, Choice, ChoiceKind, Token
3
+ from syncraft.ast import Then, ThenKind, Many, Choice, ChoiceKind, Token, Marked, Nothing
4
4
  from syncraft.algebra import Error
5
5
  from syncraft.parser import literal, parse
6
6
  import syncraft.generator as gen
@@ -19,9 +19,9 @@ def test1_simple_then() -> None:
19
19
  print("---" * 40)
20
20
  print(generated)
21
21
  assert ast == generated
22
- # value, bmap = generated.bimap()
23
- # print(value)
24
- # assert bmap(value) == generated
22
+ value, bmap = generated.bimap()
23
+ print(value)
24
+ assert gen.generate(syntax, bmap(value)) == generated
25
25
 
26
26
 
27
27
  def test2_named_results() -> None:
@@ -35,10 +35,9 @@ def test2_named_results() -> None:
35
35
  print("---" * 40)
36
36
  print(generated)
37
37
  assert ast == generated
38
- # value, bmap = generated.bimap()
39
- # print(value)
40
- # print(bmap(value))
41
- # assert bmap(value) == generated
38
+ value, bmap = generated.bimap()
39
+ assert gen.generate(syntax, bmap(value)) == generated
40
+
42
41
 
43
42
 
44
43
  def test3_many_literals() -> None:
@@ -52,9 +51,8 @@ def test3_many_literals() -> None:
52
51
  print("---" * 40)
53
52
  print(generated)
54
53
  assert ast == generated
55
- # value, bmap = generated.bimap()
56
- # print(value)
57
- # assert bmap(value) == generated
54
+ value, bmap = generated.bimap()
55
+ assert gen.generate(syntax, bmap(value)) == generated
58
56
 
59
57
 
60
58
  def test4_mixed_many_named() -> None:
@@ -69,9 +67,8 @@ def test4_mixed_many_named() -> None:
69
67
  print("---" * 40)
70
68
  print(generated)
71
69
  assert ast == generated
72
- # value, bmap = generated.bimap()
73
- # print(value)
74
- # assert bmap(value) == generated
70
+ value, bmap = generated.bimap()
71
+ assert gen.generate(syntax, bmap(value)) == generated
75
72
 
76
73
 
77
74
  def test5_nested_then_many() -> None:
@@ -85,9 +82,8 @@ def test5_nested_then_many() -> None:
85
82
  print("---" * 40)
86
83
  print(generated)
87
84
  assert ast == generated
88
- # value, bmap = generated.bimap()
89
- # print(value)
90
- # assert bmap(value) == generated
85
+ value, bmap = generated.bimap()
86
+ assert gen.generate(syntax, bmap(value), restore_pruned=True) == generated
91
87
 
92
88
 
93
89
 
@@ -99,8 +95,8 @@ def test_then_flatten():
99
95
  print(ast)
100
96
  generated = gen.generate(syntax, ast)
101
97
  assert ast == generated
102
- # value, bmap = ast.bimap()
103
- # assert bmap(value) == ast
98
+ value, bmap = generated.bimap()
99
+ assert gen.generate(syntax, bmap(value)) == generated
104
100
 
105
101
 
106
102
 
@@ -114,11 +110,8 @@ def test_named_in_then():
114
110
  print(ast)
115
111
  generated = gen.generate(syntax, ast)
116
112
  assert ast == generated
117
- # value, bmap = ast.bimap()
118
- # assert isinstance(value, tuple)
119
- # print(value)
120
- # assert set(x.name for x in value if isinstance(x, Marked)) == {"first", "second", "third"}
121
- # assert bmap(value) == ast
113
+ value, bmap = generated.bimap()
114
+ assert gen.generate(syntax, bmap(value)) == generated
122
115
 
123
116
 
124
117
  def test_named_in_many():
@@ -129,10 +122,8 @@ def test_named_in_many():
129
122
  print(ast)
130
123
  generated = gen.generate(syntax, ast)
131
124
  assert ast == generated
132
- # value, bmap = ast.bimap()
133
- # assert isinstance(value, list)
134
- # assert all(isinstance(v, Marked) for v in value if isinstance(v, Marked))
135
- # assert bmap(value) == ast
125
+ value, bmap = generated.bimap()
126
+ assert gen.generate(syntax, bmap(value)) == generated
136
127
 
137
128
 
138
129
  def test_named_in_or():
@@ -144,10 +135,8 @@ def test_named_in_or():
144
135
  print(ast)
145
136
  generated = gen.generate(syntax, ast)
146
137
  assert ast == generated
147
- # value, bmap = ast.bimap()
148
- # assert isinstance(value, Marked)
149
- # assert value.name == "b"
150
- # assert bmap(value) == ast
138
+ value, bmap = generated.bimap()
139
+ assert gen.generate(syntax, bmap(value)) == generated
151
140
 
152
141
 
153
142
 
@@ -165,8 +154,8 @@ def test_deep_mix():
165
154
  print('---' * 40)
166
155
  print(generated)
167
156
  assert ast == generated
168
- # value, bmap = ast.bimap()
169
- # assert bmap(value) == ast
157
+ value, bmap = generated.bimap()
158
+ assert gen.generate(syntax, bmap(value)) == generated
170
159
 
171
160
 
172
161
  def test_empty_many() -> None:
@@ -183,8 +172,8 @@ def test_backtracking_many() -> None:
183
172
  syntax = (A.many() + B) # must not eat the final "a" needed for B
184
173
  sql = "a a a a b"
185
174
  ast = parse(syntax, sql, dialect="sqlite")
186
- # value, bmap = ast.bimap()
187
- # assert value[-1] == TokenGen.from_string("b")
175
+ value, bmap = ast.bimap()
176
+ assert gen.generate(syntax, bmap(value)) == ast
188
177
 
189
178
  def test_deep_nesting() -> None:
190
179
  A = literal("a")
@@ -209,9 +198,8 @@ def test_named_many() -> None:
209
198
  syntax = A.many()
210
199
  sql = "a a"
211
200
  ast = parse(syntax, sql, dialect="sqlite")
212
- # Expect [Marked("alpha", "a"), Marked("alpha", "a")]
213
- # flattened, _ = ast.bimap()
214
- # assert all(isinstance(x, Marked) for x in flattened)
201
+ value, bmap = ast.bimap()
202
+ assert gen.generate(syntax, bmap(value)) == ast
215
203
 
216
204
 
217
205
  def test_or_named() -> None:
@@ -220,10 +208,8 @@ def test_or_named() -> None:
220
208
  syntax = A | B
221
209
  sql = "b"
222
210
  ast = parse(syntax, sql, dialect="sqlite")
223
- # Either Marked("y", "b") or just "b", depending on your design
224
- # assert isinstance(ast, Choice)
225
- # value, _ = ast.bimap()
226
- # assert value == Marked(name="y", value=TokenGen.from_string("b"))
211
+ value, bmap = ast.bimap()
212
+ assert gen.generate(syntax, bmap(value)) == ast
227
213
 
228
214
 
229
215
  def test_then_associativity() -> None:
@@ -269,11 +255,11 @@ def test_optional():
269
255
  A = literal("a").mark("a")
270
256
  syntax = A.optional()
271
257
  ast1 = parse(syntax, "", dialect="sqlite")
272
- # v1, _ = ast1.bimap()
273
- # assert v1 is None
274
- # ast2 = parse(syntax, "a", dialect="sqlite")
275
- # v2, _ = ast2.bimap()
276
- # assert v2 == Marked(name='a', value=TokenGen.from_string('a'))
258
+ v1, _ = ast1.bimap()
259
+ assert isinstance(v1, Nothing)
260
+ ast2 = parse(syntax, "a", dialect="sqlite")
261
+ v2, _ = ast2.bimap()
262
+ assert v2 == Marked(name='a', value=TokenGen.from_string('a'))
277
263
 
278
264
 
279
265
 
@@ -16,6 +16,8 @@ def test_between()->None:
16
16
  ast:AST = parse(syntax, sql, dialect='sqlite')
17
17
  generated = gen.generate(syntax, ast)
18
18
  assert ast == generated, "Parsed and generated results do not match."
19
+ x, f = generated.bimap()
20
+ assert gen.generate(syntax, f(x)) == ast
19
21
 
20
22
 
21
23
  def test_sep_by()->None:
@@ -24,6 +26,8 @@ def test_sep_by()->None:
24
26
  ast:AST = parse(syntax, sql, dialect='sqlite')
25
27
  generated = gen.generate(syntax, ast)
26
28
  assert ast == generated, "Parsed and generated results do not match."
29
+ x, f = generated.bimap()
30
+ assert gen.generate(syntax, f(x)) == ast
27
31
 
28
32
  def test_many_or()->None:
29
33
  IF = literal("if")
@@ -34,3 +38,5 @@ def test_many_or()->None:
34
38
  ast:AST = parse(syntax, sql, dialect='sqlite')
35
39
  generated = gen.generate(syntax, ast)
36
40
  assert ast == generated, "Parsed and generated results do not match."
41
+ x, f = generated.bimap()
42
+ assert gen.generate(syntax, f(x)) == ast
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes