syncraft 0.1.17__tar.gz → 0.1.19__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.
Files changed (23) hide show
  1. {syncraft-0.1.17 → syncraft-0.1.19}/PKG-INFO +1 -1
  2. {syncraft-0.1.17 → syncraft-0.1.19}/pyproject.toml +1 -1
  3. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/diagnostic.py +2 -2
  4. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/generator.py +31 -3
  5. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/parser.py +19 -18
  6. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/sqlite3.py +7 -7
  7. syncraft-0.1.17/syncraft/dsl.py → syncraft-0.1.19/syncraft/syntax.py +56 -56
  8. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/PKG-INFO +1 -1
  9. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/SOURCES.txt +1 -1
  10. {syncraft-0.1.17 → syncraft-0.1.19}/tests/test_bimap.py +42 -30
  11. {syncraft-0.1.17 → syncraft-0.1.19}/tests/test_parse.py +7 -7
  12. {syncraft-0.1.17 → syncraft-0.1.19}/tests/test_until.py +4 -4
  13. {syncraft-0.1.17 → syncraft-0.1.19}/LICENSE +0 -0
  14. {syncraft-0.1.17 → syncraft-0.1.19}/README.md +0 -0
  15. {syncraft-0.1.17 → syncraft-0.1.19}/setup.cfg +0 -0
  16. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/__init__.py +0 -0
  17. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/algebra.py +0 -0
  18. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/ast.py +0 -0
  19. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/cmd.py +0 -0
  20. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft/py.typed +0 -0
  21. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/dependency_links.txt +0 -0
  22. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/requires.txt +0 -0
  23. {syncraft-0.1.17 → syncraft-0.1.19}/syncraft.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.17
3
+ Version: 0.1.19
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.17"
3
+ version = "0.1.19"
4
4
  description = "Parser combinator library"
5
5
  license = "MIT"
6
6
  license-files = ["LICENSE"]
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
  from rich import print
3
3
  from rich.table import Table as RichTable
4
4
  from typing import Tuple, Any, Set
5
- from syncraft.dsl import DSL
5
+ from syncraft.syntax import Syntax
6
6
  from syncraft.algebra import Left, Right, Error, Either, Algebra
7
7
 
8
8
  from syncraft.parser import ParserState, Token
@@ -44,7 +44,7 @@ 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, DSL)):
47
+ elif isinstance(value, (Error, ParserState, Syntax)):
48
48
  return prefix + (value._string or 'N/A')
49
49
  else:
50
50
  return prefix + str(value)
@@ -2,15 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  from typing import (
4
4
  Any, TypeVar, Tuple, Optional, Callable, Generic, Union,
5
- List
5
+ List, Generator as YieldGen
6
6
  )
7
7
  from functools import cached_property
8
8
  from dataclasses import dataclass, replace
9
9
  from syncraft.algebra import (
10
10
  Algebra, ThenResult, Either, Left, Right, Error, Insptectable,
11
- OrResult, ManyResult
11
+ OrResult, ManyResult, NamedResult
12
12
  )
13
+
13
14
  from syncraft.ast import TokenProtocol, ParseResult, AST, Token, TokenSpec
15
+ from syncraft.syntax import Syntax
14
16
  from sqlglot import TokenType
15
17
  import re
16
18
  import rstr
@@ -270,7 +272,8 @@ class Generator(Algebra[GenResult[T], GenState[T]]):
270
272
 
271
273
 
272
274
 
273
- def generate(gen: Algebra[Any, Any], data: Optional[AST[Any]] = None, seed: int = 0) -> AST[Any] | Any:
275
+ def generate(syntax: Syntax[Any, Any], data: Optional[AST[Any]] = None, seed: int = 0) -> AST[Any] | Any:
276
+ gen = syntax(Generator)
274
277
  state = GenState.from_ast(data, seed)
275
278
  result = gen.run(state, use_cache=False)
276
279
  if isinstance(result, Right):
@@ -278,3 +281,28 @@ def generate(gen: Algebra[Any, Any], data: Optional[AST[Any]] = None, seed: int
278
281
  assert isinstance(result, Left), "Generator must return Either[Any, Tuple[Any, Any]]"
279
282
  return result.value
280
283
 
284
+
285
+ def matches(syntax: Syntax[Any, Any], data: AST[Any])-> bool:
286
+ gen = syntax(Generator)
287
+ state = GenState.from_ast(data)
288
+ result = gen.run(state, use_cache=True)
289
+ return isinstance(result, Right)
290
+
291
+
292
+ def search(syntax: Syntax[Any, Any], data: AST[Any]) -> YieldGen[AST[Any], None, None]:
293
+ if matches(syntax, data):
294
+ yield data
295
+ match data.focus:
296
+ case ThenResult(left = left, right=right):
297
+ yield from search(syntax, AST(left))
298
+ yield from search(syntax, AST(right))
299
+ case ManyResult(value = value):
300
+ for e in value:
301
+ yield from search(syntax, AST(e))
302
+ case NamedResult(value=value):
303
+ yield from search(syntax, AST(value))
304
+ case OrResult(value=value):
305
+ yield from search(syntax, AST(value))
306
+ case _:
307
+ pass
308
+
@@ -11,7 +11,7 @@ from syncraft.algebra import (
11
11
  from dataclasses import dataclass, field, replace
12
12
  from enum import Enum
13
13
  from functools import reduce
14
- from syncraft.dsl import DSL
14
+ from syncraft.syntax import Syntax
15
15
 
16
16
  from syncraft.ast import Token, TokenSpec, AST, T
17
17
 
@@ -187,15 +187,16 @@ class Parser(Algebra[T, ParserState[T]]):
187
187
  return Right((tuple(tokens), tmp_state))
188
188
  return cls(until_run, name=cls.__name__ + '.until')
189
189
 
190
- def sqlglot(parser: DSL[Any, Any],
191
- dialect: str) -> DSL[List[exp.Expression], ParserState[Any]]:
190
+ def sqlglot(parser: Syntax[Any, Any],
191
+ dialect: str) -> Syntax[List[exp.Expression], ParserState[Any]]:
192
192
  gp = GlotParser(dialect=dialect)
193
193
  return parser.map(lambda tokens: [e for e in gp.parse(raw_tokens=tokens) if e is not None])
194
194
 
195
195
 
196
- def parse(parser: Algebra[Any, ParserState[Token]],
196
+ def parse(syntax: Syntax[Any, Any],
197
197
  sql: str,
198
198
  dialect: str) -> AST[Any] | Any:
199
+ parser = syntax(Parser)
199
200
  input: ParserState[Token] = token_state(sql, dialect=dialect)
200
201
  result = parser.run(input, True)
201
202
  if isinstance(result, Right):
@@ -212,34 +213,34 @@ def token(token_type: Optional[Enum] = None,
212
213
  text: Optional[str] = None,
213
214
  case_sensitive: bool = False,
214
215
  regex: Optional[re.Pattern[str]] = None
215
- ) -> DSL[Any, Any]:
216
+ ) -> Syntax[Any, Any]:
216
217
  token_type_txt = token_type.name if token_type is not None else None
217
218
  token_value_txt = text if text is not None else None
218
219
  msg = 'token(' + ','.join([x for x in [token_type_txt, token_value_txt, str(regex)] if x is not None]) + ')'
219
- return DSL(
220
+ return Syntax(
220
221
  lambda cls: cls.factory('token', token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex)
221
222
  ).describe(name=msg, fixity='prefix')
222
223
 
223
224
 
224
- def identifier(value: str | None = None) -> DSL[Any, Any]:
225
+ def identifier(value: str | None = None) -> Syntax[Any, Any]:
225
226
  if value is None:
226
227
  return token(TokenType.IDENTIFIER)
227
228
  else:
228
229
  return token(TokenType.IDENTIFIER, text=value)
229
230
 
230
- def variable(value: str | None = None) -> DSL[Any, Any]:
231
+ def variable(value: str | None = None) -> Syntax[Any, Any]:
231
232
  if value is None:
232
233
  return token(TokenType.VAR)
233
234
  else:
234
235
  return token(TokenType.VAR, text=value)
235
236
 
236
- def literal(lit: str) -> DSL[Any, Any]:
237
+ def literal(lit: str) -> Syntax[Any, Any]:
237
238
  return token(token_type=None, text=lit, case_sensitive=True)
238
239
 
239
- def regex(regex: re.Pattern[str]) -> DSL[Any, Any]:
240
+ def regex(regex: re.Pattern[str]) -> Syntax[Any, Any]:
240
241
  return token(token_type=None, regex=regex, case_sensitive=True)
241
242
 
242
- def lift(value: Any)-> DSL[Any, Any]:
243
+ def lift(value: Any)-> Syntax[Any, Any]:
243
244
  if isinstance(value, str):
244
245
  return literal(value)
245
246
  elif isinstance(value, re.Pattern):
@@ -247,22 +248,22 @@ def lift(value: Any)-> DSL[Any, Any]:
247
248
  elif isinstance(value, Enum):
248
249
  return token(value)
249
250
  else:
250
- return DSL(lambda cls: cls.success(value))
251
+ return Syntax(lambda cls: cls.success(value))
251
252
 
252
- def number() -> DSL[Any, Any]:
253
+ def number() -> Syntax[Any, Any]:
253
254
  return token(TokenType.NUMBER)
254
255
 
255
256
 
256
- def string() -> DSL[Any, Any]:
257
+ def string() -> Syntax[Any, Any]:
257
258
  return token(TokenType.STRING)
258
259
 
259
260
 
260
261
 
261
- def until(*open_close: Tuple[DSL[Tuple[T, ...] | T, ParserState[T]], DSL[Tuple[T, ...] | T, ParserState[T]]],
262
- terminator: Optional[DSL[Tuple[T, ...] | T, ParserState[T]]] = None,
262
+ def until(*open_close: Tuple[Syntax[Tuple[T, ...] | T, ParserState[T]], Syntax[Tuple[T, ...] | T, ParserState[T]]],
263
+ terminator: Optional[Syntax[Tuple[T, ...] | T, ParserState[T]]] = None,
263
264
  inclusive: bool = True,
264
- strict: bool = True) -> DSL[Any, Any]:
265
- return DSL(
265
+ strict: bool = True) -> Syntax[Any, Any]:
266
+ return Syntax(
266
267
  lambda cls: cls.factory('until',
267
268
  *[(left.alg(cls), right.alg(cls)) for left, right in open_close],
268
269
  terminator=terminator.alg(cls) if terminator else None,
@@ -4,7 +4,7 @@ https://www.sqlite.org/syntaxdiagrams.html
4
4
  """
5
5
  from __future__ import annotations
6
6
  from typing import Any
7
- from syncraft.dsl import DSL, lazy, choice
7
+ from syncraft.syntax import Syntax, lazy, choice
8
8
  import syncraft.parser as dsl
9
9
  from syncraft.diagnostic import rich_error, rich_debug, rich_parser
10
10
  from sqlglot import TokenType
@@ -607,16 +607,16 @@ update_stmt_limited = (
607
607
  )
608
608
 
609
609
 
610
- def table_or_subquery()->DSL[Any, Any]:
610
+ def table_or_subquery()->Syntax[Any, Any]:
611
611
  t1 = ~schema_name >> table_as_alias >> ~((INDEXED >> BY >> index_name)|(NOT >> INDEXED))
612
612
  t2 = ~schema_name >> table_function_name >> expr.parens(COMMA, L_PAREN, R_PAREN) >> ~(~AS >> var)
613
613
  t3 = select_stmt.between(L_PAREN, R_PAREN) >> ~(~AS >> var)
614
614
  t4 = table_subquery.parens(COMMA, L_PAREN, R_PAREN)
615
615
  t5 = join_clause.between(L_PAREN, R_PAREN)
616
- return (t1 | t2 | t3 | t4 | t5).as_(DSL[Any, Any])
616
+ return (t1 | t2 | t3 | t4 | t5).as_(Syntax[Any, Any])
617
617
 
618
618
 
619
- def expression() -> DSL[Any, Any]:
619
+ def expression() -> Syntax[Any, Any]:
620
620
  return choice(
621
621
  literal_value,
622
622
  bind_parameter,
@@ -639,9 +639,9 @@ def expression() -> DSL[Any, Any]:
639
639
  expr >> ~NOT >> IN >> ~schema_name >> (table_name | (function_name >> expr.parens(COMMA, L_PAREN, R_PAREN))),
640
640
  ~NOT >> ~EXISTS >> select_stmt.between(L_PAREN, R_PAREN),
641
641
  CASE >> ~expr >> (WHEN >> expr >> THEN >> expr).many() >> ~(ELSE >> expr) // END,
642
- ).as_(DSL[Any, Any])
642
+ ).as_(Syntax[Any, Any])
643
643
 
644
- def select_statement() -> DSL[Any, Any]:
644
+ def select_statement() -> Syntax[Any, Any]:
645
645
  select_clause = SELECT >> ~(DISTINCT | ALL) >> result_columns.sep_by(COMMA)
646
646
  from_clause = FROM >> (table_subquery.sep_by(COMMA) | join_clause)
647
647
  where_clause = WHERE >> expr
@@ -657,7 +657,7 @@ def select_statement() -> DSL[Any, Any]:
657
657
  >> select_core.sep_by(compound_operator)
658
658
  >> ~(ordering_clause >> ~limit_clause)
659
659
  >> ~SEMICOLON
660
- ).as_(DSL[Any, Any])
660
+ ).as_(Syntax[Any, Any])
661
661
 
662
662
  column_constraint = ~(CONSTRAINT >> constraint_name) >> (
663
663
  (PRIMARY >> KEY >> ~(ASC | DESC) >> ~conflict_clause >> AUTO_INCREMENT)
@@ -67,11 +67,11 @@ class Description(Insptectable):
67
67
 
68
68
 
69
69
  @dataclass(frozen=True)
70
- class DSL(Generic[A, S], Insptectable):
70
+ class Syntax(Generic[A, S], Insptectable):
71
71
  alg: Callable[[Type[Algebra[Any, Any]]], Algebra[A, S]]
72
72
  meta: Description = field(default_factory=Description, repr=False)
73
73
 
74
- def algebra(self, name: str | MethodType | FunctionType, *args: Any, **kwargs: Any)-> DSL[A, S]:
74
+ def algebra(self, name: str | MethodType | FunctionType, *args: Any, **kwargs: Any)-> Syntax[A, S]:
75
75
  def algebra_run(cls: Type[Algebra[Any, S]]) -> Algebra[Any, S]:
76
76
  a = self.alg(cls)
77
77
  if isinstance(name, str):
@@ -115,33 +115,33 @@ class DSL(Generic[A, S], Insptectable):
115
115
  newline: Optional[str] = None,
116
116
  name: Optional[str] = None,
117
117
  fixity: Optional[Literal['infix', 'prefix', 'postfix']] = None,
118
- parameter: Optional[List[DSL[Any, S]]] = None) -> DSL[A, S]:
118
+ parameter: Optional[List[Syntax[Any, S]]] = None) -> Syntax[A, S]:
119
119
  return self.__class__(alg=self.alg,
120
120
  meta=self.meta.update(name=name,
121
121
  newline=newline,
122
122
  fixity=fixity,
123
123
  parameter=parameter))
124
124
 
125
- def newline(self, info: str='')-> DSL[A, S]:
125
+ def newline(self, info: str='')-> Syntax[A, S]:
126
126
  return self.describe(newline=info)
127
127
 
128
- def terminal(self, name: str)->DSL[A, S]:
128
+ def terminal(self, name: str)->Syntax[A, S]:
129
129
  return self.describe(name=name, fixity='prefix', parameter=[])
130
130
 
131
131
  ######################################################## value transformation ########################################################
132
- def map(self, f: Callable[[A], B]) -> DSL[B, S]:
132
+ def map(self, f: Callable[[A], B]) -> Syntax[B, S]:
133
133
  return self.__class__(lambda cls: self.alg(cls).map(f), meta = self.meta) # type: ignore
134
134
 
135
- def map_error(self, f: Callable[[Optional[Any]], Any]) -> DSL[A, S]:
135
+ def map_error(self, f: Callable[[Optional[Any]], Any]) -> Syntax[A, S]:
136
136
  return self.__class__(lambda cls: self.alg(cls).map_error(f), meta=self.meta)
137
137
 
138
- def map_state(self, f: Callable[[S], S]) -> DSL[A, S]:
138
+ def map_state(self, f: Callable[[S], S]) -> Syntax[A, S]:
139
139
  return self.__class__(lambda cls: self.alg(cls).map_state(f), meta=self.meta)
140
140
 
141
- def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> DSL[B, S]:
141
+ def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
142
142
  return self.__class__(lambda cls: self.alg(cls).flat_map(f)) # type: ignore
143
143
 
144
- def many(self, *, at_least: int = 1, at_most: Optional[int] = None) -> DSL[ManyResult[A], S]:
144
+ def many(self, *, at_least: int = 1, at_most: Optional[int] = None) -> Syntax[ManyResult[A], S]:
145
145
  return self.__class__(lambda cls:self.alg(cls).many(at_least=at_least, at_most=at_most)).describe(name='*', # type: ignore
146
146
  fixity='prefix',
147
147
  parameter=[self])
@@ -150,83 +150,83 @@ class DSL(Generic[A, S], Insptectable):
150
150
 
151
151
 
152
152
 
153
- def between(self, left: DSL[Any, S], right: DSL[Any, S]) -> DSL[ThenResult[None, ThenResult[A, None]], S]:
153
+ def between(self, left: Syntax[Any, S], right: Syntax[Any, S]) -> Syntax[ThenResult[None, ThenResult[A, None]], S]:
154
154
  return left >> self // right
155
155
 
156
- def sep_by(self, sep: DSL[Any, S]) -> DSL[ThenResult[A, ManyResult[ThenResult[None, A]]], S]:
156
+ def sep_by(self, sep: Syntax[Any, S]) -> Syntax[ThenResult[A, ManyResult[ThenResult[None, A]]], S]:
157
157
  return (self + (sep >> self).many()).describe(
158
158
  name='sep_by',
159
159
  fixity='prefix',
160
160
  parameter=[self, sep]
161
161
  )
162
162
 
163
- def parens(self, sep: DSL[Any, S], open: DSL[Any, S], close: DSL[Any, S]) -> DSL[Any, S]:
163
+ def parens(self, sep: Syntax[Any, S], open: Syntax[Any, S], close: Syntax[Any, S]) -> Syntax[Any, S]:
164
164
  return self.sep_by(sep=sep).between(left=open, right=close)
165
165
 
166
- def optional(self, default: Optional[B] = None) -> DSL[Optional[A | B], S]:
166
+ def optional(self, default: Optional[B] = None) -> Syntax[Optional[A | B], S]:
167
167
  return (self | success(default)).describe(name='~', fixity='prefix', parameter=[self])
168
168
 
169
169
 
170
- def cut(self) -> DSL[A, S]:
170
+ def cut(self) -> Syntax[A, S]:
171
171
  return self.__class__(lambda cls:self.alg(cls).cut())
172
172
 
173
173
 
174
174
  ####################################################### operator overloading #############################################
175
- def __ge__(self, f: Callable[[A], Algebra[B, S]]) -> DSL[B, S]:
175
+ def __ge__(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
176
176
  return self.flat_map(f).describe(name='>=', fixity='infix', parameter=[self])
177
177
 
178
178
 
179
- def __gt__(self, other: Callable[[A], B])->DSL[B, S]:
179
+ def __gt__(self, other: Callable[[A], B])->Syntax[B, S]:
180
180
  return self.map(other)
181
181
 
182
182
 
183
- def __floordiv__(self, other: DSL[B, S]) -> DSL[ThenResult[A, None], S]:
184
- other = other if isinstance(other, DSL) else self.lift(other).as_(DSL[B, S])
183
+ def __floordiv__(self, other: Syntax[B, S]) -> Syntax[ThenResult[A, None], S]:
184
+ other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
185
185
  return self.__class__(
186
186
  lambda cls: self.alg(cls).then_left(other.alg(cls)) # type: ignore
187
- ).describe(name=ThenKind.LEFT.value, fixity='infix', parameter=[self, other]).as_(DSL[ThenResult[A, None], S])
187
+ ).describe(name=ThenKind.LEFT.value, fixity='infix', parameter=[self, other]).as_(Syntax[ThenResult[A, None], S])
188
188
 
189
- def __rfloordiv__(self, other: DSL[B, S]) -> DSL[ThenResult[B, None], S]:
190
- other = other if isinstance(other, DSL) else self.lift(other).as_(DSL[B, S])
189
+ def __rfloordiv__(self, other: Syntax[B, S]) -> Syntax[ThenResult[B, None], S]:
190
+ other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
191
191
  return other.__floordiv__(self)
192
192
 
193
- def __invert__(self) -> DSL[A | None, S]:
193
+ def __invert__(self) -> Syntax[A | None, S]:
194
194
  return self.optional()
195
195
 
196
- def __radd__(self, other: DSL[B, S]) -> DSL[ThenResult[B, A], S]:
197
- other = other if isinstance(other, DSL) else self.lift(other).as_(DSL[B, S])
196
+ def __radd__(self, other: Syntax[B, S]) -> Syntax[ThenResult[B, A], S]:
197
+ other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
198
198
  return other.__add__(self)
199
199
 
200
- def __add__(self, other: DSL[B, S]) -> DSL[ThenResult[A, B], S]:
201
- other = other if isinstance(other, DSL) else self.lift(other).as_(DSL[B, S])
200
+ def __add__(self, other: Syntax[B, S]) -> Syntax[ThenResult[A, B], S]:
201
+ other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
202
202
  return self.__class__(
203
203
  lambda cls: self.alg(cls).then_both(other.alg(cls)) # type: ignore
204
204
  ).describe(name=ThenKind.BOTH.value, fixity='infix', parameter=[self, other])
205
205
 
206
- def __rshift__(self, other: DSL[B, S]) -> DSL[ThenResult[None, B], S]:
207
- other = other if isinstance(other, DSL) else self.lift(other).as_(DSL[B, S])
206
+ def __rshift__(self, other: Syntax[B, S]) -> Syntax[ThenResult[None, B], S]:
207
+ other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
208
208
  return self.__class__(
209
209
  lambda cls: self.alg(cls).then_right(other.alg(cls)) # type: ignore
210
- ).describe(name=ThenKind.RIGHT.value, fixity='infix', parameter=[self, other]).as_(DSL[ThenResult[None, B], S])
210
+ ).describe(name=ThenKind.RIGHT.value, fixity='infix', parameter=[self, other]).as_(Syntax[ThenResult[None, B], S])
211
211
 
212
212
 
213
- def __rrshift__(self, other: DSL[B, S]) -> DSL[ThenResult[None, A], S]:
214
- other = other if isinstance(other, DSL) else self.lift(other).as_(DSL[B, S])
213
+ def __rrshift__(self, other: Syntax[B, S]) -> Syntax[ThenResult[None, A], S]:
214
+ other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
215
215
  return other.__rshift__(self)
216
216
 
217
217
 
218
- def __or__(self, other: DSL[B, S]) -> DSL[A | B, S]:
219
- other = other if isinstance(other, DSL) else self.lift(other).as_(DSL[B, S])
218
+ def __or__(self, other: Syntax[B, S]) -> Syntax[A | B, S]:
219
+ other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
220
220
  return self.__class__(lambda cls: self.alg(cls).or_else(other.alg(cls))).describe(name='|', fixity='infix', parameter=[self, other]) # type: ignore
221
221
 
222
222
 
223
- def __ror__(self, other: DSL[B, S]) -> DSL[A | B, S]:
224
- other = other if isinstance(other, DSL) else self.lift(other).as_(DSL[B, S])
225
- return other.__or__(self).as_(DSL[A | B, S])
223
+ def __ror__(self, other: Syntax[B, S]) -> Syntax[A | B, S]:
224
+ other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
225
+ return other.__or__(self).as_(Syntax[A | B, S])
226
226
 
227
227
 
228
228
  ######################################################################## data processing combinators #########################################################
229
- def bind(self, name: str) -> DSL[NamedResult[A], S]:
229
+ def bind(self, name: str) -> Syntax[NamedResult[A], S]:
230
230
  def bind_f(value: A) -> NamedResult[A]:
231
231
  if isinstance(value, NamedResult):
232
232
  return replace(value, name=name)
@@ -235,7 +235,7 @@ class DSL(Generic[A, S], Insptectable):
235
235
  return self.map(bind_f).describe(name=f'bind("{name}")', fixity='postfix', parameter=[self])
236
236
 
237
237
 
238
- def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> DSL[A, S]:
238
+ def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> Syntax[A, S]:
239
239
  def dump_error_run(err: Any)->Any:
240
240
  if isinstance(err, Error) and formatter is not None:
241
241
  formatter(err)
@@ -245,21 +245,21 @@ class DSL(Generic[A, S], Insptectable):
245
245
 
246
246
  def debug(self,
247
247
  label: str,
248
- formatter: Optional[Callable[[Algebra[Any, S], S, Either[Any, Tuple[Any, S]]], None]] = None) -> DSL[A, S]:
248
+ formatter: Optional[Callable[[Algebra[Any, S], S, Either[Any, Tuple[Any, S]]], None]] = None) -> Syntax[A, S]:
249
249
  return self.__class__(lambda cls:self.alg(cls).debug(label, formatter), meta=self.meta)
250
250
 
251
251
 
252
252
 
253
- def lazy(thunk: Callable[[], DSL[A, S]]) -> DSL[A, S]:
254
- return DSL(lambda cls: cls.lazy(lambda: thunk()(cls))).describe(name='lazy(?)', fixity='postfix')
253
+ def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
254
+ return Syntax(lambda cls: cls.lazy(lambda: thunk()(cls))).describe(name='lazy(?)', fixity='postfix')
255
255
 
256
- def fail(error: Any) -> DSL[Any, Any]:
257
- return DSL(lambda alg: alg.fail(error)).describe(name=f'fail({error})', fixity='prefix')
256
+ def fail(error: Any) -> Syntax[Any, Any]:
257
+ return Syntax(lambda alg: alg.fail(error)).describe(name=f'fail({error})', fixity='prefix')
258
258
 
259
- def success(value: Any) -> DSL[Any, Any]:
260
- return DSL(lambda alg: alg.success(value)).describe(name=f'success({value})', fixity='prefix')
259
+ def success(value: Any) -> Syntax[Any, Any]:
260
+ return Syntax(lambda alg: alg.success(value)).describe(name=f'success({value})', fixity='prefix')
261
261
 
262
- def choice(*parsers: DSL[Any, S]) -> DSL[Any, S]:
262
+ def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
263
263
  return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(None)
264
264
 
265
265
 
@@ -269,27 +269,27 @@ def choice(*parsers: DSL[Any, S]) -> DSL[Any, S]:
269
269
 
270
270
 
271
271
 
272
- def all(*parsers: DSL[Any, S]) -> DSL[ThenResult[Any, Any], S]:
272
+ def all(*parsers: Syntax[Any, S]) -> Syntax[ThenResult[Any, Any], S]:
273
273
  return reduce(lambda a, b: a + b, parsers) if len(parsers) > 0 else success(None)
274
274
 
275
- def first(*parsers: DSL[Any, S]) -> DSL[Any, S]:
275
+ def first(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
276
276
  return reduce(lambda a, b: a // b, parsers) if len(parsers) > 0 else success(None)
277
277
 
278
- def last(*parsers: DSL[Any, S]) -> DSL[Any, S]:
278
+ def last(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
279
279
  return reduce(lambda a, b: a >> b, parsers) if len(parsers) > 0 else success(None)
280
280
 
281
- def named(* parsers: DSL[Any, S] | Tuple[str, DSL[Any, S]]) -> DSL[Any, S]:
281
+ def named(* parsers: Syntax[Any, S] | Tuple[str, Syntax[Any, S]]) -> Syntax[Any, S]:
282
282
  def is_named_parser(x: Any) -> bool:
283
- return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], DSL)
283
+ return isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax)
284
284
 
285
- def to_parser(x: DSL[Any, S] | Tuple[str, DSL[Any, S]])->DSL[Any, S]:
286
- if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], DSL):
285
+ def to_parser(x: Syntax[Any, S] | Tuple[str, Syntax[Any, S]])->Syntax[Any, S]:
286
+ if isinstance(x, tuple) and len(x) == 2 and isinstance(x[0], str) and isinstance(x[1], Syntax):
287
287
  return x[1].bind(x[0])
288
- elif isinstance(x, DSL):
288
+ elif isinstance(x, Syntax):
289
289
  return x
290
290
  else:
291
291
  raise ValueError(f"Invalid parser or tuple: {x}", x)
292
- ret: Optional[DSL[Any, S]] = None
292
+ ret: Optional[Syntax[Any, S]] = None
293
293
  has_data = False
294
294
  for p in parsers:
295
295
  just_parser = to_parser(p)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncraft
3
- Version: 0.1.17
3
+ Version: 0.1.19
4
4
  Summary: Parser combinator library
5
5
  Author-email: Michael Afmokt <michael@esacca.com>
6
6
  License-Expression: MIT
@@ -6,11 +6,11 @@ syncraft/algebra.py
6
6
  syncraft/ast.py
7
7
  syncraft/cmd.py
8
8
  syncraft/diagnostic.py
9
- syncraft/dsl.py
10
9
  syncraft/generator.py
11
10
  syncraft/parser.py
12
11
  syncraft/py.typed
13
12
  syncraft/sqlite3.py
13
+ syncraft/syntax.py
14
14
  syncraft.egg-info/PKG-INFO
15
15
  syncraft.egg-info/SOURCES.txt
16
16
  syncraft.egg-info/dependency_links.txt
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
  from syncraft.algebra import NamedResult, Error, ManyResult, OrResult, ThenResult, ThenKind
3
- from syncraft.parser import literal, parse, Parser
3
+ from syncraft.parser import literal, parse
4
4
  import syncraft.generator as gen
5
5
  from syncraft.generator import TokenGen
6
6
  from rich import print
@@ -10,10 +10,10 @@ def test1_simple_then() -> None:
10
10
  A, B, C = literal("a"), literal("b"), literal("c")
11
11
  syntax = A // B // C
12
12
  sql = "a b c"
13
- ast = parse(syntax(Parser), sql, dialect="sqlite")
13
+ ast = parse(syntax, sql, dialect="sqlite")
14
14
  print("---" * 40)
15
15
  print(ast)
16
- generated = gen.generate(syntax(gen.Generator), ast)
16
+ generated = gen.generate(syntax, ast)
17
17
  print("---" * 40)
18
18
  print(generated)
19
19
  assert ast == generated
@@ -26,10 +26,10 @@ def test2_named_results() -> None:
26
26
  A, B = literal("a").bind("x").bind('z'), literal("b").bind("y")
27
27
  syntax = A // B
28
28
  sql = "a b"
29
- ast = parse(syntax(Parser), sql, dialect="sqlite")
29
+ ast = parse(syntax, sql, dialect="sqlite")
30
30
  print("---" * 40)
31
31
  print(ast)
32
- generated = gen.generate(syntax(gen.Generator), ast)
32
+ generated = gen.generate(syntax, ast)
33
33
  print("---" * 40)
34
34
  print(generated)
35
35
  assert ast == generated
@@ -43,10 +43,10 @@ def test3_many_literals() -> None:
43
43
  A = literal("a")
44
44
  syntax = A.many()
45
45
  sql = "a a a"
46
- ast = parse(syntax(Parser), sql, dialect="sqlite")
46
+ ast = parse(syntax, sql, dialect="sqlite")
47
47
  print("---" * 40)
48
48
  print(ast)
49
- generated = gen.generate(syntax(gen.Generator), ast)
49
+ generated = gen.generate(syntax, ast)
50
50
  print("---" * 40)
51
51
  print(generated)
52
52
  assert ast == generated
@@ -60,10 +60,10 @@ def test4_mixed_many_named() -> None:
60
60
  B = literal("b")
61
61
  syntax = (A | B).many()
62
62
  sql = "a b a"
63
- ast = parse(syntax(Parser), sql, dialect="sqlite")
63
+ ast = parse(syntax, sql, dialect="sqlite")
64
64
  print("---" * 40)
65
65
  print(ast)
66
- generated = gen.generate(syntax(gen.Generator), ast)
66
+ generated = gen.generate(syntax, ast)
67
67
  print("---" * 40)
68
68
  print(generated)
69
69
  assert ast == generated
@@ -76,10 +76,10 @@ def test5_nested_then_many() -> None:
76
76
  IF, THEN, END = literal("if"), literal("then"), literal("end")
77
77
  syntax = (IF.many() // THEN.many()).many() // END
78
78
  sql = "if if then end"
79
- ast = parse(syntax(Parser), sql, dialect="sqlite")
79
+ ast = parse(syntax, sql, dialect="sqlite")
80
80
  print("---" * 40)
81
81
  print(ast)
82
- generated = gen.generate(syntax(gen.Generator), ast)
82
+ generated = gen.generate(syntax, ast)
83
83
  print("---" * 40)
84
84
  print(generated)
85
85
  # assert ast == generated
@@ -93,9 +93,9 @@ def test_then_flatten():
93
93
  A, B, C = literal("a"), literal("b"), literal("c")
94
94
  syntax = A + (B + C)
95
95
  sql = "a b c"
96
- ast = parse(syntax(Parser), sql, dialect='sqlite')
96
+ ast = parse(syntax, sql, dialect='sqlite')
97
97
  print(ast)
98
- generated = gen.generate(syntax(gen.Generator), ast)
98
+ generated = gen.generate(syntax, ast)
99
99
  assert ast == generated
100
100
  value, bmap = ast.bimap(None)
101
101
  assert bmap(value) == ast
@@ -108,9 +108,9 @@ def test_named_in_then():
108
108
  C = literal("c").bind("third")
109
109
  syntax = A + B + C
110
110
  sql = "a b c"
111
- ast = parse(syntax(Parser), sql, dialect='sqlite')
111
+ ast = parse(syntax, sql, dialect='sqlite')
112
112
  print(ast)
113
- generated = gen.generate(syntax(gen.Generator), ast)
113
+ generated = gen.generate(syntax, ast)
114
114
  assert ast == generated
115
115
  value, bmap = ast.bimap(None)
116
116
  assert isinstance(value, tuple)
@@ -123,9 +123,9 @@ def test_named_in_many():
123
123
  A = literal("x").bind("x")
124
124
  syntax = A.many()
125
125
  sql = "x x x"
126
- ast = parse(syntax(Parser), sql, dialect='sqlite')
126
+ ast = parse(syntax, sql, dialect='sqlite')
127
127
  print(ast)
128
- generated = gen.generate(syntax(gen.Generator), ast)
128
+ generated = gen.generate(syntax, ast)
129
129
  assert ast == generated
130
130
  value, bmap = ast.bimap(None)
131
131
  assert isinstance(value, list)
@@ -138,9 +138,9 @@ def test_named_in_or():
138
138
  B = literal("b").bind("b")
139
139
  syntax = A | B
140
140
  sql = "b"
141
- ast = parse(syntax(Parser), sql, dialect='sqlite')
141
+ ast = parse(syntax, sql, dialect='sqlite')
142
142
  print(ast)
143
- generated = gen.generate(syntax(gen.Generator), ast)
143
+ generated = gen.generate(syntax, ast)
144
144
  assert ast == generated
145
145
  value, bmap = ast.bimap(None)
146
146
  assert isinstance(value, NamedResult)
@@ -157,9 +157,9 @@ def test_deep_mix():
157
157
  C = literal("c").bind("c")
158
158
  syntax = ((A + B) | C).many() + B
159
159
  sql = "a b a b c b"
160
- ast = parse(syntax(Parser), sql, dialect='sqlite')
160
+ ast = parse(syntax, sql, dialect='sqlite')
161
161
  print(ast)
162
- generated = gen.generate(syntax(gen.Generator), ast)
162
+ generated = gen.generate(syntax, ast)
163
163
  print('---' * 40)
164
164
  print(generated)
165
165
  assert ast == generated
@@ -171,7 +171,7 @@ def test_empty_many() -> None:
171
171
  A = literal("a")
172
172
  syntax = A.many() # This should allow empty matches
173
173
  sql = ""
174
- ast = parse(syntax(Parser), sql, dialect="sqlite")
174
+ ast = parse(syntax, sql, dialect="sqlite")
175
175
  assert isinstance(ast, Error)
176
176
 
177
177
 
@@ -180,7 +180,7 @@ def test_backtracking_many() -> None:
180
180
  B = literal("b")
181
181
  syntax = (A.many() + B) # must not eat the final "a" needed for B
182
182
  sql = "a a a a b"
183
- ast = parse(syntax(Parser), sql, dialect="sqlite")
183
+ ast = parse(syntax, sql, dialect="sqlite")
184
184
  value, bmap = ast.bimap(None)
185
185
  assert value[-1] == TokenGen.from_string("b")
186
186
 
@@ -190,7 +190,7 @@ def test_deep_nesting() -> None:
190
190
  for _ in range(100):
191
191
  syntax = syntax + A
192
192
  sql = " " .join("a" for _ in range(101))
193
- ast = parse(syntax(Parser), sql, dialect="sqlite")
193
+ ast = parse(syntax, sql, dialect="sqlite")
194
194
  assert ast is not None
195
195
 
196
196
 
@@ -198,7 +198,7 @@ def test_nested_many() -> None:
198
198
  A = literal("a")
199
199
  syntax = (A.many().many()) # groups of groups of "a"
200
200
  sql = "a a a"
201
- ast = parse(syntax(Parser), sql, dialect="sqlite")
201
+ ast = parse(syntax, sql, dialect="sqlite")
202
202
  assert isinstance(ast.focus, ManyResult)
203
203
 
204
204
 
@@ -206,7 +206,7 @@ def test_named_many() -> None:
206
206
  A = literal("a").bind("alpha")
207
207
  syntax = A.many()
208
208
  sql = "a a"
209
- ast = parse(syntax(Parser), sql, dialect="sqlite")
209
+ ast = parse(syntax, sql, dialect="sqlite")
210
210
  # Expect [NamedResult("alpha", "a"), NamedResult("alpha", "a")]
211
211
  flattened, _ = ast.bimap(None)
212
212
  assert all(isinstance(x, NamedResult) for x in flattened)
@@ -217,7 +217,7 @@ def test_or_named() -> None:
217
217
  B = literal("b").bind("y")
218
218
  syntax = A | B
219
219
  sql = "b"
220
- ast = parse(syntax(Parser), sql, dialect="sqlite")
220
+ ast = parse(syntax, sql, dialect="sqlite")
221
221
  # Either NamedResult("y", "b") or just "b", depending on your design
222
222
  assert isinstance(ast.focus, OrResult)
223
223
  value, _ = ast.bimap(None)
@@ -230,7 +230,7 @@ def test_then_associativity() -> None:
230
230
  C = literal("c")
231
231
  syntax = A + B + C
232
232
  sql = "a b c"
233
- ast = parse(syntax(Parser), sql, dialect="sqlite")
233
+ ast = parse(syntax, sql, dialect="sqlite")
234
234
  # Should be ThenResult(ThenResult(A,B),C)
235
235
  assert ast.focus == ThenResult(kind=ThenKind.BOTH,
236
236
  left=ThenResult(kind=ThenKind.BOTH,
@@ -244,7 +244,7 @@ def test_ambiguous() -> None:
244
244
  B = literal("a") + literal("b")
245
245
  syntax = A | B
246
246
  sql = "a"
247
- ast = parse(syntax(Parser), sql, dialect="sqlite")
247
+ ast = parse(syntax, sql, dialect="sqlite")
248
248
  # Does it prefer A (shorter) or B (fails)? Depends on design.
249
249
  assert ast.focus == OrResult(TokenGen.from_string("a"))
250
250
 
@@ -256,6 +256,18 @@ def test_combo() -> None:
256
256
  syntax = ((A + B).many() | C) + B
257
257
  sql = "a b a b c b"
258
258
  # Should fail, as we discussed earlier
259
- ast = parse(syntax(Parser), sql, dialect="sqlite")
259
+ ast = parse(syntax, sql, dialect="sqlite")
260
260
  assert isinstance(ast, Error)
261
261
 
262
+
263
+ def test_optional():
264
+ A = literal("a").bind("a")
265
+ syntax = A.optional()
266
+ ast1 = parse(syntax, "", dialect="sqlite")
267
+ v1, _ = ast1.bimap(None)
268
+ assert v1 is None
269
+ ast2 = parse(syntax, "a", dialect="sqlite")
270
+ v2, _ = ast2.bimap(None)
271
+ assert v2 == NamedResult(name='a', value=TokenGen.from_string('a'))
272
+
273
+
@@ -1,4 +1,4 @@
1
- from syncraft.parser import AST, literal, variable, parse, Parser
1
+ from syncraft.parser import AST, literal, variable, parse
2
2
  import syncraft.generator as gen
3
3
  from typing import Any
4
4
 
@@ -12,16 +12,16 @@ var = variable()
12
12
  def test_between()->None:
13
13
  sql = "then if then"
14
14
  syntax = IF.between(THEN, THEN)
15
- ast:AST[Any] = parse(syntax(Parser), sql, dialect='sqlite')
16
- generated = gen.generate(syntax(gen.Generator), ast)
15
+ ast:AST[Any] = parse(syntax, sql, dialect='sqlite')
16
+ generated = gen.generate(syntax, ast)
17
17
  assert ast == generated, "Parsed and generated results do not match."
18
18
 
19
19
 
20
20
  def test_sep_by()->None:
21
21
  sql = "if then if then if then if"
22
22
  syntax = IF.sep_by(THEN)
23
- ast:AST[Any] = parse(syntax(Parser), sql, dialect='sqlite')
24
- generated = gen.generate(syntax(gen.Generator), ast)
23
+ ast:AST[Any] = parse(syntax, sql, dialect='sqlite')
24
+ generated = gen.generate(syntax, ast)
25
25
  assert ast == generated, "Parsed and generated results do not match."
26
26
 
27
27
  def test_many_or()->None:
@@ -30,6 +30,6 @@ def test_many_or()->None:
30
30
  END = literal("end")
31
31
  syntax = (IF.many() | THEN.many()).many() // END
32
32
  sql = "if if then end"
33
- ast:AST[Any] = parse(syntax(Parser), sql, dialect='sqlite')
34
- generated = gen.generate(syntax(gen.Generator), ast)
33
+ ast:AST[Any] = parse(syntax, sql, dialect='sqlite')
34
+ generated = gen.generate(syntax, ast)
35
35
  assert ast == generated, "Parsed and generated results do not match."
@@ -10,7 +10,7 @@ LB, RB = literal("["), literal("]")
10
10
  def test_until_accepts_proper_nesting() -> None:
11
11
  sql = "([])"
12
12
  syntax = until((LP, RP), (LB, RB))
13
- ast: AST[Any] | Any = parse(syntax(Parser), sql, dialect="sqlite")
13
+ ast: AST[Any] | Any = parse(syntax, sql, dialect="sqlite")
14
14
  assert isinstance(ast, AST), f"Expected AST for proper nesting, got {ast}"
15
15
 
16
16
 
@@ -18,7 +18,7 @@ def test_until_rejects_mismatched_pairs() -> None:
18
18
  # Mismatched: ( ] should fail immediately
19
19
  sql = "(]"
20
20
  syntax = until((LP, RP), (LB, RB))
21
- res = parse(syntax(Parser), sql, dialect="sqlite")
21
+ res = parse(syntax, sql, dialect="sqlite")
22
22
  from syncraft.algebra import Error
23
23
  assert isinstance(res, Error), "Mismatched pairs should be rejected with an Error"
24
24
 
@@ -26,7 +26,7 @@ def test_until_rejects_unterminated_group() -> None:
26
26
  # Unterminated: ( ... EOF
27
27
  sql = "("
28
28
  syntax = until((LP, RP))
29
- res = parse(syntax(Parser), sql, dialect="sqlite")
29
+ res = parse(syntax, sql, dialect="sqlite")
30
30
  from syncraft.algebra import Error
31
31
  assert isinstance(res, Error), "Unterminated group should be rejected with an Error"
32
32
 
@@ -35,6 +35,6 @@ def test_until_rejects_crossing_pairs() -> None:
35
35
  sql = "([)]"
36
36
  syntax = until((LP, RP), (LB, RB))
37
37
  # Use postgres dialect so [ and ] are tokenized distinctly (not as bracketed identifier)
38
- res = parse(syntax(Parser), sql, dialect="postgres")
38
+ res = parse(syntax, sql, dialect="postgres")
39
39
  from syncraft.algebra import Error
40
40
  assert isinstance(res, Error), "Crossing pairs should be rejected with an Error"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes