syncraft 0.2.2__py3-none-any.whl → 0.2.3__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/__init__.py CHANGED
@@ -0,0 +1,59 @@
1
+ from .syntax import (
2
+ Syntax,
3
+ choice,
4
+ lazy,
5
+ success,
6
+ fail,
7
+ run,
8
+ )
9
+ from .parser import (
10
+ parse,
11
+ sqlglot,
12
+ token,
13
+ identifier,
14
+ variable,
15
+ literal,
16
+ number,
17
+ string,
18
+ regex,
19
+ until,
20
+ )
21
+ from .generator import (
22
+ generate,
23
+ )
24
+ from .finder import (
25
+ find,
26
+ matches,
27
+ anything,
28
+ )
29
+ from .constraint import (
30
+ Constraint,
31
+ Quantifier,
32
+ forall,
33
+ exists,
34
+ )
35
+ from .ast import (
36
+ AST,
37
+ Token,
38
+ Then,
39
+ ThenKind,
40
+ Choice,
41
+ ChoiceKind,
42
+ Many,
43
+ Marked,
44
+ Collect,
45
+ )
46
+
47
+ __all__ = [
48
+ # syntax & core
49
+ "Syntax", "choice", "lazy", "success", "fail", "run",
50
+ # parsing/generation helpers
51
+ "parse", "sqlglot", "token", "identifier", "variable", "literal", "number", "string", "regex", "until",
52
+ "generate",
53
+ # finder
54
+ "find", "matches", "anything",
55
+ # constraints
56
+ "Constraint", "Quantifier", "forall", "exists",
57
+ # ast
58
+ "AST", "Token", "Then", "ThenKind", "Choice", "ChoiceKind", "Many", "Marked", "Collect",
59
+ ]
syncraft/algebra.py CHANGED
@@ -93,8 +93,38 @@ class Algebra(Generic[A, S]):
93
93
  def __call__(self, input: S, use_cache: bool) -> Either[Any, Tuple[A, S]]:
94
94
  return self.run(input, use_cache=use_cache)
95
95
 
96
-
97
96
  def run(self, input: S, use_cache: bool) -> Either[Any, Tuple[A, S]]:
97
+ """Execute this algebra on the given state with optional memoization.
98
+
99
+ This is the core evaluation entry point used by all combinators. It
100
+ supports per-"parser" memoization and protects against infinite
101
+ recursion by detecting left-recursive re-entrance.
102
+
103
+ Args:
104
+ input: The initial state to run against. Must be hashable as it's
105
+ used as a cache key.
106
+ use_cache: When True, memoize results for ``(self.run_f, input)``
107
+ so repeated calls short-circuit. When False, the cache entry is
108
+ cleared after the run to effectively disable the cache.
109
+
110
+ Returns:
111
+ Either[Error, Tuple[A, S]]: On success, ``Right((value, next_state))``.
112
+ On failure, ``Left(Error)``. Errors produced downstream are
113
+ automatically enriched with ``this=self`` and ``state=input`` to
114
+ preserve context. If an exception escapes the user code, it's
115
+ captured and returned as ``Left(Error)`` with a traceback in
116
+ ``stack``.
117
+
118
+ Notes:
119
+ - Left recursion: if a re-entrant call is observed on the same
120
+ ``input`` while an evaluation is in progress, a ``Left(Error)``
121
+ is returned indicating left-recursion was detected.
122
+ - Memoization scope: results are cached per ``run_f`` (the concrete
123
+ compiled function of this algebra) and keyed by the input state.
124
+ - Commitment: downstream combinators (e.g. ``cut``) may set the
125
+ ``committed`` flag in ``Error``; ``run`` preserves that flag but
126
+ does not set it itself.
127
+ """
98
128
  cache = self._cache[self.run_f]
99
129
  assert cache is not None, "Cache should be initialized in __post_init__"
100
130
  if input in cache:
@@ -136,12 +166,31 @@ class Algebra(Generic[A, S]):
136
166
 
137
167
  @classmethod
138
168
  def lazy(cls, thunk: Callable[[], Algebra[A, S]]) -> Algebra[A, S]:
169
+ """Lazily construct an algebra at run time.
170
+
171
+ Useful for recursive definitions. The thunk is evaluated when this
172
+ algebra runs, and the resulting algebra is executed.
173
+
174
+ Args:
175
+ thunk: Zero-argument function returning the underlying algebra.
176
+
177
+ Returns:
178
+ An algebra that defers to the thunk-provided algebra.
179
+ """
139
180
  def lazy_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
140
181
  return thunk().run(input, use_cache)
141
182
  return cls(lazy_run, name=cls.__name__ + '.lazy')
142
183
 
143
184
  @classmethod
144
185
  def fail(cls, error: Any) -> Algebra[Any, S]:
186
+ """Return an algebra that always fails with ``error``.
187
+
188
+ Args:
189
+ error: The error payload to wrap in ``Left``.
190
+
191
+ Returns:
192
+ An algebra producing ``Left(Error(...))`` without consuming input.
193
+ """
145
194
  def fail_run(input: S, use_cache:bool) -> Either[Any, Tuple[Any, S]]:
146
195
  return Left(Error(
147
196
  error=error,
@@ -152,12 +201,35 @@ class Algebra(Generic[A, S]):
152
201
 
153
202
  @classmethod
154
203
  def success(cls, value: Any) -> Algebra[Any, S]:
204
+ """Return an algebra that always succeeds with ``value``.
205
+
206
+ The input state is passed through unchanged.
207
+
208
+ Args:
209
+ value: The constant value to return.
210
+
211
+ Returns:
212
+ ``Right((value, input))`` for any input state.
213
+ """
155
214
  def success_run(input: S, use_cache:bool) -> Either[Any, Tuple[Any, S]]:
156
215
  return Right((value, input))
157
216
  return cls(success_run, name=cls.__name__ + '.success')
158
217
 
159
218
  @classmethod
160
219
  def factory(cls, name: str, *args: Any, **kwargs: Any) -> Algebra[A, S]:
220
+ """Call a named class method to construct an algebra.
221
+
222
+ Args:
223
+ name: Name of a classmethod/staticmethod on this class.
224
+ *args: Positional args passed to the method.
225
+ **kwargs: Keyword args passed to the method.
226
+
227
+ Returns:
228
+ The algebra returned by the method.
229
+
230
+ Raises:
231
+ ValueError: If the method is missing or not callable.
232
+ """
161
233
  method = getattr(cls, name, None)
162
234
  if method is None or not callable(method):
163
235
  raise ValueError(f"Method {name} is not defined in {cls.__name__}")
@@ -166,6 +238,14 @@ class Algebra(Generic[A, S]):
166
238
 
167
239
 
168
240
  def cut(self) -> Algebra[A, S]:
241
+ """Commit this branch by marking failures as committed.
242
+
243
+ Converts downstream errors into committed errors (``committed=True``),
244
+ which prevents alternatives from being tried in ``or_else``.
245
+
246
+ Returns:
247
+ An algebra that commits errors produced by this one.
248
+ """
169
249
  def commit_error(e: Any) -> Error:
170
250
  match e:
171
251
  case Error():
@@ -188,6 +268,15 @@ class Algebra(Generic[A, S]):
188
268
  ],
189
269
  Either[Any, Tuple[B, S]]],
190
270
  ctx: Optional[Any] = None) -> Algebra[A | B, S]:
271
+ """Run a handler only when this algebra fails.
272
+
273
+ Args:
274
+ func: Callback ``(alg, input, left, ctx) -> Either`` executed on failure.
275
+ ctx: Optional context object passed to the callback.
276
+
277
+ Returns:
278
+ An algebra that intercepts failures and can recover or transform them.
279
+ """
191
280
  assert callable(func), "func must be callable"
192
281
  def fail_run(input: S, use_cache:bool) -> Either[Any, Tuple[A | B, S]]:
193
282
  result = self.run(input, use_cache)
@@ -206,6 +295,15 @@ class Algebra(Generic[A, S]):
206
295
  ],
207
296
  Either[Any, Tuple[B, S]]],
208
297
  ctx: Optional[Any] = None) -> Algebra[A | B, S]:
298
+ """Run a handler only when this algebra succeeds.
299
+
300
+ Args:
301
+ func: Callback ``(alg, input, right, ctx) -> Either`` executed on success.
302
+ ctx: Optional context object passed to the callback.
303
+
304
+ Returns:
305
+ An algebra that can transform or post-process successes.
306
+ """
209
307
  assert callable(func), "func must be callable"
210
308
  def success_run(input: S, use_cache:bool) -> Either[Any, Tuple[A | B, S]]:
211
309
  result = self.run(input, use_cache)
@@ -244,24 +342,29 @@ class Algebra(Generic[A, S]):
244
342
  return lazy_self
245
343
 
246
344
  ######################################################## map on state ###########################################
247
- def post_state(self, f: Callable[[S], S]) -> Algebra[A, S]:
248
- def post_state_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
249
- match self.run(input, use_cache):
250
- case Right((value, state)):
251
- return Right((value, f(state)))
252
- case Left(err):
253
- return Left(err)
254
- case x:
255
- raise ValueError(f"Unexpected result from self.run {x}")
256
- return self.__class__(post_state_run, name=self.name)
345
+ def map_state(self, f: Callable[[S], S]) -> Algebra[A, S]:
346
+ """Map the input state before running this algebra.
347
+
348
+ Args:
349
+ f: ``S -> S`` function applied to the state prior to running.
257
350
 
258
- def pre_state(self, f: Callable[[S], S]) -> Algebra[A, S]:
259
- def pre_state_run(state: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
351
+ Returns:
352
+ An algebra that runs with ``f(state)``.
353
+ """
354
+ def map_state_run(state: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
260
355
  return self.run(f(state), use_cache)
261
- return self.__class__(pre_state_run, name=self.name)
356
+ return self.__class__(map_state_run, name=self.name)
262
357
 
263
358
 
264
359
  def map_all(self, f: Callable[[A, S], Tuple[B, S]]) -> Algebra[B, S]:
360
+ """Map both the produced value and the resulting state on success.
361
+
362
+ Args:
363
+ f: Function mapping ``(value, state)`` to ``(new_value, new_state)``.
364
+
365
+ Returns:
366
+ An algebra producing the transformed value and state.
367
+ """
265
368
  def map_all_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
266
369
  match self.run(input, use_cache):
267
370
  case Right((value, state)):
@@ -273,23 +376,54 @@ class Algebra(Generic[A, S]):
273
376
  raise ValueError(f"Unexpected result from self.run {x}")
274
377
  return self.__class__(map_all_run, name=self.name) # type: ignore
275
378
  ######################################################## fundamental combinators ############################################
276
- def fmap(self, f: Callable[[A], B]) -> Algebra[B, S]:
277
- def fmap_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
379
+ def map(self, f: Callable[[A], B]) -> Algebra[B, S]:
380
+ """Transform the success value, leaving the state unchanged.
381
+
382
+ Args:
383
+ f: Mapper from ``A`` to ``B``.
384
+
385
+ Returns:
386
+ An algebra that yields ``B`` with the same resulting state.
387
+ """
388
+ def map_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
278
389
  parsed = self.run(input, use_cache)
279
390
  if isinstance(parsed, Right):
280
391
  return Right((f(parsed.value[0]), parsed.value[1]))
281
392
  else:
282
393
  return cast(Either[Any, Tuple[B, S]], parsed)
283
- return self.__class__(fmap_run, name=self.name) # type: ignore
394
+ return self.__class__(map_run, name=self.name) # type: ignore
284
395
 
285
-
286
- def map(self, f: Callable[[A], B]) -> Algebra[B, S]:
287
- return self.fmap(f)
288
-
396
+
289
397
  def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Algebra[B, S]:
290
- return self.fmap(f).pre_state(lambda s: s.map(i))
398
+ """Bidirectionally map values with an inverse, updating the state.
399
+
400
+ Applies ``f`` to the success value. The state is pre-mapped with the
401
+ inverse ``i`` via the state's ``map`` method to preserve round-trips.
402
+
403
+ Args:
404
+ f: Forward mapping ``A -> B``.
405
+ i: Inverse mapping ``B -> A`` applied to the state.
406
+
407
+ Returns:
408
+ An algebra producing ``B`` while keeping value/state alignment.
409
+
410
+ Note:
411
+ Different subclass of Algebra can override state.map method to change
412
+ the behavior of bimap. For example, ParserState.map will return the
413
+ state unchanged, and GenState.map will apply the inverse map and update
414
+ the next AST node for generation.
415
+ """
416
+ return self.map(f).map_state(lambda s: s.map(i))
291
417
 
292
418
  def map_error(self, f: Callable[[Optional[Any]], Any]) -> Algebra[A, S]:
419
+ """Transform the error payload when this algebra fails.
420
+
421
+ Args:
422
+ f: Function applied to the error payload inside ``Left``.
423
+
424
+ Returns:
425
+ An algebra that preserves successes and maps failures.
426
+ """
293
427
  def map_error_run(input: S, use_cache:bool) -> Either[Any, Tuple[A, S]]:
294
428
  parsed = self.run(input, use_cache)
295
429
  if isinstance(parsed, Left):
@@ -298,6 +432,17 @@ class Algebra(Generic[A, S]):
298
432
  return self.__class__(map_error_run, name=self.name)
299
433
 
300
434
  def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Algebra[B, S]:
435
+ """Chain computations where the next algebra depends on the value.
436
+
437
+ On success, passes the produced value to ``f`` to obtain the next
438
+ algebra, then runs it with the resulting state.
439
+
440
+ Args:
441
+ f: Mapper from a value to the next algebra.
442
+
443
+ Returns:
444
+ An algebra yielding the result of the chained computation.
445
+ """
301
446
  def flat_map_run(input: S, use_cache:bool) -> Either[Any, Tuple[B, S]]:
302
447
  parsed = self.run(input, use_cache)
303
448
  if isinstance(parsed, Right):
@@ -308,6 +453,18 @@ class Algebra(Generic[A, S]):
308
453
 
309
454
 
310
455
  def or_else(self: Algebra[A, S], other: Algebra[B, S]) -> Algebra[Choice[A, B], S]:
456
+ """Try this algebra; if it fails uncommitted, try ``other``.
457
+
458
+ If the failure is committed (``committed=True``), the alternative is
459
+ not attempted and the error is propagated.
460
+
461
+ Args:
462
+ other: Fallback algebra to try from the same input state.
463
+
464
+ Returns:
465
+ An algebra producing ``Choice.LEFT`` for this success or
466
+ ``Choice.RIGHT`` for the other's success.
467
+ """
311
468
  def or_else_run(input: S, use_cache:bool) -> Either[Any, Tuple[Choice[A, B], S]]:
312
469
  match self.run(input, use_cache):
313
470
  case Right((value, state)):
@@ -325,27 +482,75 @@ class Algebra(Generic[A, S]):
325
482
  return self.__class__(or_else_run, name=f'{self.name} | {other.name}') # type: ignore
326
483
 
327
484
  def then_both(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
485
+ """Sequence two algebras and keep both values.
486
+
487
+ Returns a ``Then(kind=BOTH)`` holding the left and right values.
488
+
489
+ Args:
490
+ other: The algebra to run after this one.
491
+
492
+ Returns:
493
+ An algebra producing ``Then(left, right, kind=BOTH)``.
494
+ """
328
495
  def then_both_f(a: A) -> Algebra[Then[A, B], S]:
329
496
  def combine(b: B) -> Then[A, B]:
330
497
  return Then(left=a, right=b, kind=ThenKind.BOTH)
331
- return other.fmap(combine)
498
+ return other.map(combine)
332
499
  return self.flat_map(then_both_f).named(f'{self.name} + {other.name}')
333
500
 
334
501
  def then_left(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
502
+ """Sequence two algebras, keep the left value in the result.
503
+
504
+ Produces ``Then(kind=LEFT)`` with both values attached.
505
+
506
+ Args:
507
+ other: The algebra to run after this one.
508
+
509
+ Returns:
510
+ An algebra producing ``Then(left, right, kind=LEFT)``.
511
+ """
335
512
  def then_left_f(a: A) -> Algebra[Then[A, B], S]:
336
513
  def combine(b: B) -> Then[A, B]:
337
514
  return Then(left=a, right=b, kind=ThenKind.LEFT)
338
- return other.fmap(combine)
515
+ return other.map(combine)
339
516
  return self.flat_map(then_left_f).named(f'{self.name} // {other.name}')
340
517
 
341
518
  def then_right(self, other: Algebra[B, S]) -> Algebra[Then[A, B], S]:
519
+ """Sequence two algebras, keep the right value in the result.
520
+
521
+ Produces ``Then(kind=RIGHT)`` with both values attached.
522
+
523
+ Args:
524
+ other: The algebra to run after this one.
525
+
526
+ Returns:
527
+ An algebra producing ``Then(left, right, kind=RIGHT)``.
528
+ """
342
529
  def then_right_f(a: A) -> Algebra[Then[A, B], S]:
343
530
  def combine(b: B) -> Then[A, B]:
344
531
  return Then(left=a, right=b, kind=ThenKind.RIGHT)
345
- return other.fmap(combine)
532
+ return other.map(combine)
346
533
  return self.flat_map(then_right_f).named(f'{self.name} >> {other.name}')
347
534
 
348
535
  def many(self, *, at_least: int, at_most: Optional[int]) -> Algebra[Many[A], S]:
536
+ """Repeat this algebra and collect results into ``Many``.
537
+
538
+ Repeats greedily until failure or no progress. Enforces cardinality
539
+ constraints. If ``at_most`` is ``None``, there is no upper bound.
540
+
541
+ Args:
542
+ at_least: Minimum number of matches required (>= 1).
543
+ at_most: Optional maximum number of matches.
544
+
545
+ Returns:
546
+ On success, ``Right((Many(values), state))``.
547
+ Note:
548
+ at_most, if given, is enforced strictly, more than at_most matches
549
+ is treated as an error.
550
+ Raises:
551
+ ValueError: If bounds are invalid (e.g., ``at_least<=0`` or
552
+ ``at_most<at_least``).
553
+ """
349
554
  if at_least <=0 or (at_most is not None and at_most < at_least):
350
555
  raise ValueError(f"Invalid arguments for many: at_least={at_least}, at_most={at_most}")
351
556
  def many_run(input: S, use_cache:bool) -> Either[Any, Tuple[Many[A], S]]:
syncraft/ast.py CHANGED
@@ -90,10 +90,28 @@ class Lens(Generic[C, A]):
90
90
 
91
91
  @dataclass(frozen=True)
92
92
  class Bimap(Generic[A, B]):
93
+ """A reversible mapping that returns both a forward value and an inverse function.
94
+
95
+ ``Bimap`` is like a function ``A -> B`` paired with a way to map a value
96
+ of type ``B`` back into an ``A``. It composes with other ``Bimap``s or a
97
+ ``Biarrow`` using ``>>`` and ``<<``-style operations, preserving an
98
+ automatically derived inverse.
99
+ """
93
100
  run_f: Callable[[A], Tuple[B, Callable[[B], A]]]
94
101
  def __call__(self, a: A) -> Tuple[B, Callable[[B], A]]:
102
+ """Apply the mapping to ``a``.
103
+
104
+ Returns:
105
+ tuple: ``(forward_value, inverse)`` where ``inverse`` maps
106
+ a compatible ``B`` back into an ``A``.
107
+ """
95
108
  return self.run_f(a)
96
109
  def __rshift__(self, other: Bimap[B, C] | Biarrow[B, C]) -> Bimap[A, C]:
110
+ """Compose this mapping with another mapping/arrow.
111
+
112
+ ``self >> other`` first applies ``self``, then ``other``. The produced
113
+ inverse runs ``other``'s inverse followed by ``self``'s inverse.
114
+ """
97
115
  if isinstance(other, Biarrow):
98
116
  def biarrow_then_run(a: A) -> Tuple[C, Callable[[C], A]]:
99
117
  b, inv1 = self(a)
@@ -114,6 +132,7 @@ class Bimap(Generic[A, B]):
114
132
  else:
115
133
  raise TypeError(f"Unsupported type for Bimap >>: {type(other)}")
116
134
  def __rrshift__(self, other: Bimap[C, A] | Biarrow[C, A]) -> Bimap[C, B]:
135
+ """Right-composition so arrows or bimaps can be on the left of ``>>``."""
117
136
  if isinstance(other, Biarrow):
118
137
  def biarrow_then_run(c: C) -> Tuple[B, Callable[[B], C]]:
119
138
  a = other.forward(c)
@@ -137,18 +156,28 @@ class Bimap(Generic[A, B]):
137
156
 
138
157
 
139
158
  @staticmethod
140
- def const(a: B)->Bimap[B, B]:
159
+ def const(a: B) -> Bimap[B, B]:
160
+ """Return a bimap that ignores input and always yields ``a``.
161
+
162
+ The inverse is identity for the output type.
163
+ """
141
164
  return Bimap(lambda _: (a, lambda b: b))
142
165
 
143
166
  @staticmethod
144
- def identity()->Bimap[A, A]:
167
+ def identity() -> Bimap[A, A]:
168
+ """The identity bimap where forward and inverse are no-ops."""
145
169
  return Bimap(lambda a: (a, lambda b: b))
146
170
 
147
171
  @staticmethod
148
172
  def when(cond: Callable[[A], bool],
149
173
  then: Bimap[A, B],
150
174
  otherwise: Optional[Bimap[A, C]] = None) -> Bimap[A, A | B | C]:
151
- def when_run(a:A) -> Tuple[A | B | C, Callable[[A | B | C], A]]:
175
+ """Choose a mapping depending on the input value.
176
+
177
+ Applies ``then`` when ``cond(a)`` is true; otherwise applies
178
+ ``otherwise`` if provided, or ``identity``.
179
+ """
180
+ def when_run(a: A) -> Tuple[A | B | C, Callable[[A | B | C], A]]:
152
181
  bimap = then if cond(a) else (otherwise if otherwise is not None else Bimap.identity())
153
182
  abc, inv = bimap(a)
154
183
  def inv_f(b: Any) -> A:
@@ -184,11 +213,22 @@ class Reducer(Generic[A, S]):
184
213
 
185
214
  @dataclass(frozen=True)
186
215
  class AST:
216
+ """Base class for all Syncraft AST nodes.
217
+
218
+ Nodes implement ``bimap`` to transform contained values while providing an
219
+ inverse that can reconstruct the original node from transformed output.
220
+ """
187
221
  def bimap(self, r: Bimap[Any, Any]=Bimap.identity()) -> Tuple[Any, Callable[[Any], Any]]:
222
+ """Apply a bimap to this node, returning a value and an inverse.
223
+
224
+ The default behavior defers to the provided mapping ``r`` with the
225
+ node itself as input. The ``r`` only applies to the leaf node of AST tree.
226
+ """
188
227
  return r(self)
189
228
 
190
229
  @dataclass(frozen=True)
191
230
  class Nothing(AST):
231
+ """Singleton sentinel representing the absence of a value in the AST."""
192
232
  _instance = None
193
233
  def __new__(cls):
194
234
  if cls._instance is None:
@@ -202,9 +242,19 @@ class Nothing(AST):
202
242
 
203
243
  @dataclass(frozen=True)
204
244
  class Marked(Generic[A], AST):
245
+ """Annotate a AST node with a name.
246
+
247
+ Used to tag subtrees so they can be collected by name later (e.g., in
248
+ collectors) without altering the structural shape.
249
+ """
205
250
  name: str
206
251
  value: A
207
252
  def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[Marked[B], Callable[[Marked[B]], Marked[A]]]:
253
+ """Transform the inner value while preserving the mark name.
254
+
255
+ Returns a new ``Marked`` with transformed value and an inverse that
256
+ expects a ``Marked`` to recover the original.
257
+ """
208
258
  v, inner_f = self.value.bimap(r) if isinstance(self.value, AST) else r(self.value)
209
259
  return Marked(name=self.name, value=v), lambda b: Marked(name = b.name, value=inner_f(b.value))
210
260
 
@@ -214,9 +264,19 @@ class ChoiceKind(Enum):
214
264
 
215
265
  @dataclass(frozen=True)
216
266
  class Choice(Generic[A, B], AST):
267
+ """Represent a binary alternative between left and right values.
268
+
269
+ ``kind`` indicates which branch was taken, or ``None`` when unknown.
270
+ """
217
271
  kind: Optional[ChoiceKind]
218
272
  value: Optional[A | B] = None
219
273
  def bimap(self, r: Bimap[A | B, C]=Bimap.identity()) -> Tuple[Optional[C], Callable[[Optional[C]], Choice[A, B]]]:
274
+ """Map over the held value if present; propagate ``None`` otherwise.
275
+
276
+ The inverse resets ``kind`` to ``None`` to avoid biasing the result.
277
+ When user edit the data we cannot assume which branch the data should go
278
+ back to. Set ``kind`` to ``None`` to indicate this situation.
279
+ """
220
280
  if self.value is None:
221
281
  return None, lambda c: replace(self, value=None, kind=None)
222
282
  else:
@@ -225,8 +285,15 @@ class Choice(Generic[A, B], AST):
225
285
 
226
286
  @dataclass(frozen=True)
227
287
  class Many(Generic[A], AST):
288
+ """A finite sequence of values within the AST."""
228
289
  value: Tuple[A, ...]
229
290
  def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[List[B], Callable[[List[B]], Many[A]]]:
291
+ """Map each element to a list and provide an inverse.
292
+
293
+ The inverse accepts a list of transformed elements. If the provided
294
+ list is shorter than the original, only the prefix is used. If longer,
295
+ the extra values are inverted using the last element's inverse.
296
+ """
230
297
  ret = [v.bimap(r) if isinstance(v, AST) else r(v) for v in self.value]
231
298
  def inv(bs: List[B]) -> Many[A]:
232
299
  if len(bs) <= len(ret):
@@ -244,6 +311,12 @@ class ThenKind(Enum):
244
311
 
245
312
  @dataclass(eq=True, frozen=True)
246
313
  class Then(Generic[A, B], AST):
314
+ """Pair two values with a composition kind (both, left, or right).
315
+
316
+ The ``kind`` determines how values are combined.
317
+ ``LEFT``/``RIGHT`` indicate single-sided results; ``BOTH`` flattens both
318
+ sides.
319
+ """
247
320
  kind: ThenKind
248
321
  left: A
249
322
  right: B
@@ -259,7 +332,15 @@ class Then(Generic[A, B], AST):
259
332
  else:
260
333
  return 1
261
334
 
262
- def bimap(self, r: Bimap[A|B, Any]=Bimap.identity()) -> Tuple[Any | Tuple[Any, ...], Callable[[Any | Tuple[Any, ...]], Then[A, B]]]:
335
+ def bimap(self, r: Bimap[A | B, Any] = Bimap.identity()) -> Tuple[Any | Tuple[Any, ...], Callable[[Any | Tuple[Any, ...]], Then[A, B]]]:
336
+ """Transform the left/right values according to ``kind``.
337
+
338
+ - ``LEFT``: map and return the left value; inverse sets only ``left``.
339
+ - ``RIGHT``: map and return the right value; inverse sets only ``right``.
340
+ - ``BOTH``: return a flattened tuple of mapped left values followed by
341
+ mapped right values. The inverse expects a tuple whose length equals
342
+ ``left.arity() + right.arity()`` and reconstructs the structure.
343
+ """
263
344
  def need_wrap(x: Any) -> bool:
264
345
  return not (isinstance(x, Then) and x.kind == ThenKind.BOTH)
265
346
  match self.kind:
@@ -296,9 +377,23 @@ E = TypeVar("E", bound=DataclassInstance)
296
377
  Collector = Type[E] | Callable[..., E]
297
378
  @dataclass(frozen=True)
298
379
  class Collect(Generic[A, E], AST):
380
+ """Apply a collector to a value to build a dataclass-like instance.
381
+
382
+ When the inner value is a ``Then`` and the forward result is a tuple, any
383
+ ``Marked`` elements become named arguments to the collector; the remainder
384
+ are passed positionally. The inverse breaks the produced instance back into
385
+ a structure compatible with the original ``Then``.
386
+ """
299
387
  collector: Collector
300
388
  value: A
301
389
  def bimap(self, r: Bimap[A, B]=Bimap.identity()) -> Tuple[B | E, Callable[[B | E], Collect[A, E]]]:
390
+ """Map the inner value, collect it, and supply a matching inverse.
391
+
392
+ For multi-field tuples derived from ``Then``, the inverse rebuilds the
393
+ appropriate mix of ``Marked`` and positional elements using the
394
+ collector's dataclass fields. For single-argument collectors, the first
395
+ field of the dataclass is used.
396
+ """
302
397
 
303
398
  def inv_one_positional(e: E) -> B:
304
399
  if not is_dataclass(e):
@@ -341,6 +436,7 @@ class Collect(Generic[A, E], AST):
341
436
  #########################################################################################################################
342
437
  @dataclass(frozen=True)
343
438
  class Token(AST):
439
+ """Leaf node representing a single token with type and text."""
344
440
  token_type: Enum
345
441
  text: str
346
442
  def __str__(self) -> str:
@@ -374,6 +470,7 @@ class TokenSpec:
374
470
  return type_match and value_match
375
471
 
376
472
 
473
+ #: Union-like type describing the shape of AST parse results across nodes.
377
474
  ParseResult = Union[
378
475
  Then['ParseResult[T]', 'ParseResult[T]'],
379
476
  Marked['ParseResult[T]'],