syncraft 0.1.15__tar.gz → 0.1.17__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.15 → syncraft-0.1.17}/PKG-INFO +1 -1
  2. {syncraft-0.1.15 → syncraft-0.1.17}/pyproject.toml +1 -1
  3. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/algebra.py +1 -11
  4. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/ast.py +2 -0
  5. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/generator.py +29 -19
  6. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft.egg-info/PKG-INFO +1 -1
  7. syncraft-0.1.17/tests/test_bimap.py +261 -0
  8. syncraft-0.1.15/tests/test_bimap.py +0 -88
  9. {syncraft-0.1.15 → syncraft-0.1.17}/LICENSE +0 -0
  10. {syncraft-0.1.15 → syncraft-0.1.17}/README.md +0 -0
  11. {syncraft-0.1.15 → syncraft-0.1.17}/setup.cfg +0 -0
  12. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/__init__.py +0 -0
  13. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/cmd.py +0 -0
  14. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/diagnostic.py +0 -0
  15. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/dsl.py +0 -0
  16. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/parser.py +0 -0
  17. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/py.typed +0 -0
  18. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft/sqlite3.py +0 -0
  19. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft.egg-info/SOURCES.txt +0 -0
  20. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft.egg-info/dependency_links.txt +0 -0
  21. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft.egg-info/requires.txt +0 -0
  22. {syncraft-0.1.15 → syncraft-0.1.17}/syncraft.egg-info/top_level.txt +0 -0
  23. {syncraft-0.1.15 → syncraft-0.1.17}/tests/test_parse.py +0 -0
  24. {syncraft-0.1.15 → syncraft-0.1.17}/tests/test_until.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "syncraft"
3
- version = "0.1.15"
3
+ version = "0.1.17"
4
4
  description = "Parser combinator library"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE"]
@@ -170,17 +170,7 @@ class ThenResult(Generic[A, B], StructuralResult):
170
170
  right: B
171
171
  def bimap(self, ctx: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
172
172
  def branch(b: Any) -> Tuple[Any, Callable[[Any], StructuralResult]]:
173
- if isinstance(b, ThenResult):
174
- value, backward = b.bimap(ctx)
175
- if isinstance(value, tuple):
176
- x, y = ThenResult.flat(value)
177
- return x, lambda data: ThenResult(self.kind, y(data), self.right)
178
- else:
179
- return value, backward
180
- elif isinstance(b, StructuralResult):
181
- return b.bimap(ctx)
182
- else:
183
- return b, lambda x: x
173
+ return b.bimap(ctx) if isinstance(b, StructuralResult) else (b, lambda x: x)
184
174
  match self.kind:
185
175
  case ThenKind.BOTH:
186
176
  left_value, left_bmap = branch(self.left)
@@ -32,6 +32,8 @@ class Token:
32
32
  def __repr__(self) -> str:
33
33
  return self.__str__()
34
34
 
35
+
36
+
35
37
  @dataclass(frozen=True)
36
38
  class TokenSpec:
37
39
  token_type: Optional[Enum] = None
@@ -111,6 +111,7 @@ def token_type_from_string(token_type: Optional[TokenType], text: str, case_sens
111
111
 
112
112
  @dataclass(frozen=True)
113
113
  class TokenGen(TokenSpec):
114
+
114
115
  def __str__(self) -> str:
115
116
  tt = self.token_type.name if self.token_type else ""
116
117
  txt = self.text if self.text else ""
@@ -141,30 +142,39 @@ class TokenGen(TokenSpec):
141
142
  self.case_sensitive),
142
143
  text=text)
143
144
 
144
-
145
+ @staticmethod
146
+ def from_string(string: str)->Token:
147
+ return Token(token_type=token_type_from_string(None, string, case_sensitive=False), text=string)
145
148
 
146
149
 
147
150
  @dataclass(frozen=True)
148
151
  class Generator(Algebra[GenResult[T], GenState[T]]):
149
152
  def flat_map(self, f: Callable[[GenResult[T]], Algebra[B, GenState[T]]]) -> Algebra[B, GenState[T]]:
150
- def flat_map_run(input: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
151
- wrapper = input.wrapper()
152
- 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
153
- lft = input.left()
154
- match self.run(lft, use_cache=use_cache):
155
- case Left(error):
156
- return Left(error)
157
- case Right((value, next_input)):
158
- r = input.right()
159
- match f(value).run(r, use_cache):
160
- case Left(e):
161
- return Left(e)
162
- case Right((result, next_input)):
163
- return Right((wrapper(result), next_input))
164
- raise ValueError("flat_map should always return a value or an error.")
165
- return Generator(run_f = flat_map_run, name=self.name) # type: ignore
166
-
167
-
153
+ def flat_map_run(original: GenState[T], use_cache:bool) -> Either[Any, Tuple[B, GenState[T]]]:
154
+ wrapper = original.wrapper()
155
+ 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
156
+ try:
157
+ lft = input.left()
158
+ match self.run(lft, use_cache=use_cache):
159
+ case Left(error):
160
+ return Left(error)
161
+ case Right((value, next_input)):
162
+ r = input.right()
163
+ match f(value).run(r, use_cache):
164
+ case Left(e):
165
+ return Left(e)
166
+ case Right((result, next_input)):
167
+ return Right((wrapper(result), next_input))
168
+ raise ValueError("flat_map should always return a value or an error.")
169
+ except Exception as e:
170
+ return Left(Error(
171
+ message=str(e),
172
+ this=self,
173
+ state=original,
174
+ error=e
175
+ ))
176
+ return Generator(run_f = flat_map_run, name=self.name) # type: ignore
177
+
168
178
 
169
179
  def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[ManyResult[GenResult[T]], GenState[T]]:
170
180
  assert at_least > 0, "at_least must be greater than 0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -0,0 +1,261 @@
1
+ from __future__ import annotations
2
+ from syncraft.algebra import NamedResult, Error, ManyResult, OrResult, ThenResult, ThenKind
3
+ from syncraft.parser import literal, parse, Parser
4
+ import syncraft.generator as gen
5
+ from syncraft.generator import TokenGen
6
+ from rich import print
7
+
8
+
9
+ def test1_simple_then() -> None:
10
+ A, B, C = literal("a"), literal("b"), literal("c")
11
+ syntax = A // B // C
12
+ sql = "a b c"
13
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
14
+ print("---" * 40)
15
+ print(ast)
16
+ generated = gen.generate(syntax(gen.Generator), ast)
17
+ print("---" * 40)
18
+ print(generated)
19
+ assert ast == generated
20
+ value, bmap = generated.bimap(None)
21
+ print(value)
22
+ assert bmap(value) == generated
23
+
24
+
25
+ def test2_named_results() -> None:
26
+ A, B = literal("a").bind("x").bind('z'), literal("b").bind("y")
27
+ syntax = A // B
28
+ sql = "a b"
29
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
30
+ print("---" * 40)
31
+ print(ast)
32
+ generated = gen.generate(syntax(gen.Generator), ast)
33
+ print("---" * 40)
34
+ print(generated)
35
+ assert ast == generated
36
+ value, bmap = generated.bimap(None)
37
+ print(value)
38
+ print(bmap(value))
39
+ assert bmap(value) == generated
40
+
41
+
42
+ def test3_many_literals() -> None:
43
+ A = literal("a")
44
+ syntax = A.many()
45
+ sql = "a a a"
46
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
47
+ print("---" * 40)
48
+ print(ast)
49
+ generated = gen.generate(syntax(gen.Generator), ast)
50
+ print("---" * 40)
51
+ print(generated)
52
+ assert ast == generated
53
+ value, bmap = generated.bimap(None)
54
+ print(value)
55
+ assert bmap(value) == generated
56
+
57
+
58
+ def test4_mixed_many_named() -> None:
59
+ A = literal("a").bind("x")
60
+ B = literal("b")
61
+ syntax = (A | B).many()
62
+ sql = "a b a"
63
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
64
+ print("---" * 40)
65
+ print(ast)
66
+ generated = gen.generate(syntax(gen.Generator), ast)
67
+ print("---" * 40)
68
+ print(generated)
69
+ assert ast == generated
70
+ value, bmap = generated.bimap(None)
71
+ print(value)
72
+ assert bmap(value) == generated
73
+
74
+
75
+ def test5_nested_then_many() -> None:
76
+ IF, THEN, END = literal("if"), literal("then"), literal("end")
77
+ syntax = (IF.many() // THEN.many()).many() // END
78
+ sql = "if if then end"
79
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
80
+ print("---" * 40)
81
+ print(ast)
82
+ generated = gen.generate(syntax(gen.Generator), ast)
83
+ print("---" * 40)
84
+ print(generated)
85
+ # assert ast == generated
86
+ value, bmap = generated.bimap(None)
87
+ print(value)
88
+ assert bmap(value) == generated
89
+
90
+
91
+
92
+ def test_then_flatten():
93
+ A, B, C = literal("a"), literal("b"), literal("c")
94
+ syntax = A + (B + C)
95
+ sql = "a b c"
96
+ ast = parse(syntax(Parser), sql, dialect='sqlite')
97
+ print(ast)
98
+ generated = gen.generate(syntax(gen.Generator), ast)
99
+ assert ast == generated
100
+ value, bmap = ast.bimap(None)
101
+ assert bmap(value) == ast
102
+
103
+
104
+
105
+ def test_named_in_then():
106
+ A = literal("a").bind("first")
107
+ B = literal("b").bind("second")
108
+ C = literal("c").bind("third")
109
+ syntax = A + B + C
110
+ sql = "a b c"
111
+ ast = parse(syntax(Parser), sql, dialect='sqlite')
112
+ print(ast)
113
+ generated = gen.generate(syntax(gen.Generator), ast)
114
+ assert ast == generated
115
+ value, bmap = ast.bimap(None)
116
+ assert isinstance(value, tuple)
117
+ print(value)
118
+ assert set(x.name for x in value if isinstance(x, NamedResult)) == {"first", "second", "third"}
119
+ assert bmap(value) == ast
120
+
121
+
122
+ def test_named_in_many():
123
+ A = literal("x").bind("x")
124
+ syntax = A.many()
125
+ sql = "x x x"
126
+ ast = parse(syntax(Parser), sql, dialect='sqlite')
127
+ print(ast)
128
+ generated = gen.generate(syntax(gen.Generator), ast)
129
+ assert ast == generated
130
+ value, bmap = ast.bimap(None)
131
+ assert isinstance(value, list)
132
+ assert all(isinstance(v, NamedResult) for v in value if isinstance(v, NamedResult))
133
+ assert bmap(value) == ast
134
+
135
+
136
+ def test_named_in_or():
137
+ A = literal("a").bind("a")
138
+ B = literal("b").bind("b")
139
+ syntax = A | B
140
+ sql = "b"
141
+ ast = parse(syntax(Parser), sql, dialect='sqlite')
142
+ print(ast)
143
+ generated = gen.generate(syntax(gen.Generator), ast)
144
+ assert ast == generated
145
+ value, bmap = ast.bimap(None)
146
+ assert isinstance(value, NamedResult)
147
+ assert value.name == "b"
148
+ assert bmap(value) == ast
149
+
150
+
151
+
152
+
153
+
154
+ def test_deep_mix():
155
+ A = literal("a").bind("a")
156
+ B = literal("b")
157
+ C = literal("c").bind("c")
158
+ syntax = ((A + B) | C).many() + B
159
+ sql = "a b a b c b"
160
+ ast = parse(syntax(Parser), sql, dialect='sqlite')
161
+ print(ast)
162
+ generated = gen.generate(syntax(gen.Generator), ast)
163
+ print('---' * 40)
164
+ print(generated)
165
+ assert ast == generated
166
+ value, bmap = ast.bimap(None)
167
+ assert bmap(value) == ast
168
+
169
+
170
+ def test_empty_many() -> None:
171
+ A = literal("a")
172
+ syntax = A.many() # This should allow empty matches
173
+ sql = ""
174
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
175
+ assert isinstance(ast, Error)
176
+
177
+
178
+ def test_backtracking_many() -> None:
179
+ A = literal("a")
180
+ B = literal("b")
181
+ syntax = (A.many() + B) # must not eat the final "a" needed for B
182
+ sql = "a a a a b"
183
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
184
+ value, bmap = ast.bimap(None)
185
+ assert value[-1] == TokenGen.from_string("b")
186
+
187
+ def test_deep_nesting() -> None:
188
+ A = literal("a")
189
+ syntax = A
190
+ for _ in range(100):
191
+ syntax = syntax + A
192
+ sql = " " .join("a" for _ in range(101))
193
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
194
+ assert ast is not None
195
+
196
+
197
+ def test_nested_many() -> None:
198
+ A = literal("a")
199
+ syntax = (A.many().many()) # groups of groups of "a"
200
+ sql = "a a a"
201
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
202
+ assert isinstance(ast.focus, ManyResult)
203
+
204
+
205
+ def test_named_many() -> None:
206
+ A = literal("a").bind("alpha")
207
+ syntax = A.many()
208
+ sql = "a a"
209
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
210
+ # Expect [NamedResult("alpha", "a"), NamedResult("alpha", "a")]
211
+ flattened, _ = ast.bimap(None)
212
+ assert all(isinstance(x, NamedResult) for x in flattened)
213
+
214
+
215
+ def test_or_named() -> None:
216
+ A = literal("a").bind("x")
217
+ B = literal("b").bind("y")
218
+ syntax = A | B
219
+ sql = "b"
220
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
221
+ # Either NamedResult("y", "b") or just "b", depending on your design
222
+ assert isinstance(ast.focus, OrResult)
223
+ value, _ = ast.bimap(None)
224
+ assert value == NamedResult(name="y", value=TokenGen.from_string("b"))
225
+
226
+
227
+ def test_then_associativity() -> None:
228
+ A = literal("a")
229
+ B = literal("b")
230
+ C = literal("c")
231
+ syntax = A + B + C
232
+ sql = "a b c"
233
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
234
+ # Should be ThenResult(ThenResult(A,B),C)
235
+ assert ast.focus == ThenResult(kind=ThenKind.BOTH,
236
+ left=ThenResult(kind=ThenKind.BOTH,
237
+ left=TokenGen.from_string('a'),
238
+ right=TokenGen.from_string('b')),
239
+ right=TokenGen.from_string('c'))
240
+
241
+
242
+ def test_ambiguous() -> None:
243
+ A = literal("a")
244
+ B = literal("a") + literal("b")
245
+ syntax = A | B
246
+ sql = "a"
247
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
248
+ # Does it prefer A (shorter) or B (fails)? Depends on design.
249
+ assert ast.focus == OrResult(TokenGen.from_string("a"))
250
+
251
+
252
+ def test_combo() -> None:
253
+ A = literal("a").bind("a")
254
+ B = literal("b")
255
+ C = literal("c").bind("c")
256
+ syntax = ((A + B).many() | C) + B
257
+ sql = "a b a b c b"
258
+ # Should fail, as we discussed earlier
259
+ ast = parse(syntax(Parser), sql, dialect="sqlite")
260
+ assert isinstance(ast, Error)
261
+
@@ -1,88 +0,0 @@
1
- from __future__ import annotations
2
- from syncraft.parser import literal, variable, parse, Parser
3
- from syncraft.ast import AST
4
- import syncraft.generator as gen
5
- from typing import Any
6
- from rich import print
7
-
8
-
9
- def test1_simple_then() -> None:
10
- A, B, C = literal("a"), literal("b"), literal("c")
11
- syntax = A // B // C
12
- sql = "a b c"
13
- ast = parse(syntax(Parser), sql, dialect="sqlite")
14
- print("---" * 40)
15
- print(ast)
16
- generated = gen.generate(syntax(gen.Generator), ast)
17
- print("---" * 40)
18
- print(generated)
19
- assert ast == generated
20
- value, bmap = generated.bimap(None)
21
- print(value)
22
- assert bmap(value) == generated
23
-
24
-
25
- def test2_named_results() -> None:
26
- A, B = literal("a").bind("x").bind('z'), literal("b").bind("y")
27
- syntax = A // B
28
- sql = "a b"
29
- ast = parse(syntax(Parser), sql, dialect="sqlite")
30
- print("---" * 40)
31
- print(ast)
32
- generated = gen.generate(syntax(gen.Generator), ast)
33
- print("---" * 40)
34
- print(generated)
35
- assert ast == generated
36
- value, bmap = generated.bimap(None)
37
- print(value)
38
- print(bmap(value))
39
- assert bmap(value) == generated
40
-
41
-
42
- def test3_many_literals() -> None:
43
- A = literal("a")
44
- syntax = A.many()
45
- sql = "a a a"
46
- ast = parse(syntax(Parser), sql, dialect="sqlite")
47
- print("---" * 40)
48
- print(ast)
49
- generated = gen.generate(syntax(gen.Generator), ast)
50
- print("---" * 40)
51
- print(generated)
52
- assert ast == generated
53
- value, bmap = generated.bimap(None)
54
- print(value)
55
- assert bmap(value) == generated
56
-
57
-
58
- def test4_mixed_many_named() -> None:
59
- A = literal("a").bind("x")
60
- B = literal("b")
61
- syntax = (A | B).many()
62
- sql = "a b a"
63
- ast = parse(syntax(Parser), sql, dialect="sqlite")
64
- print("---" * 40)
65
- print(ast)
66
- generated = gen.generate(syntax(gen.Generator), ast)
67
- print("---" * 40)
68
- print(generated)
69
- assert ast == generated
70
- value, bmap = generated.bimap(None)
71
- print(value)
72
- assert bmap(value) == generated
73
-
74
-
75
- def test5_nested_then_many() -> None:
76
- IF, THEN, END = literal("if"), literal("then"), literal("end")
77
- syntax = (IF.many() // THEN.many()).many() // END
78
- sql = "if if then end"
79
- ast = parse(syntax(Parser), sql, dialect="sqlite")
80
- print("---" * 40)
81
- print(ast)
82
- generated = gen.generate(syntax(gen.Generator), ast)
83
- print("---" * 40)
84
- print(generated)
85
- # assert ast == generated
86
- value, bmap = generated.bimap(None)
87
- print(value)
88
- assert bmap(value) == generated
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes