syncraft 0.2.5__py3-none-any.whl → 0.2.7__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/syntax.py CHANGED
@@ -6,13 +6,15 @@ from typing import (
6
6
  )
7
7
  from dataclasses import dataclass, field, replace
8
8
  from functools import reduce
9
- from syncraft.algebra import Algebra, Error, Either, Right, Left
10
- from syncraft.constraint import Bindable, FrozenDict
11
- from syncraft.ast import Then, ThenKind, Marked, Choice, Many, ChoiceKind, Nothing, Collect, E, Collector
9
+ from syncraft.algebra import Algebra, Error, Right, Left, Incomplete
10
+ from syncraft.cache import Cache
11
+ from syncraft.constraint import Bindable
12
+ from syncraft.ast import Then, ThenKind, Marked, Choice, Many, ChoiceKind, Nothing, Collect, E, Collector, SyncraftError
12
13
  from types import MethodType, FunctionType
13
14
  import keyword
14
-
15
-
15
+ import re
16
+ from enum import Enum
17
+ from rich import print
16
18
 
17
19
  def valid_name(name: str) -> bool:
18
20
  return (name.isidentifier()
@@ -32,56 +34,27 @@ S = TypeVar('S', bound=Bindable) # State type
32
34
  @dataclass(frozen=True)
33
35
  class Description:
34
36
  name: Optional[str] = None
35
- newline: Optional[str] = None
36
37
  fixity: Literal['infix', 'prefix', 'postfix'] = 'infix'
37
38
  parameter: Tuple[Any, ...] = field(default_factory=tuple)
38
39
 
39
40
  def update(self,
40
41
  *,
41
- newline: Optional[str] = None,
42
42
  name: Optional[str] = None,
43
43
  fixity: Optional[Literal['infix', 'prefix', 'postfix']] = None,
44
44
  parameter: Optional[Tuple[Any, ...]] = None) -> 'Description':
45
45
  return Description(
46
46
  name=name if name is not None else self.name,
47
- newline= newline if newline is not None else self.newline,
48
47
  fixity=fixity if fixity is not None else self.fixity,
49
48
  parameter=parameter if parameter is not None else self.parameter
50
49
  )
51
50
 
52
- def to_string(self, interested: Callable[[Any], bool]) -> Optional[str]:
53
- if self.name is not None:
54
- if self.fixity == 'infix':
55
- assert len(self.parameter) == 2, "Expected exactly two parameters for infix operator"
56
- left = self.parameter[0].to_string(interested) if interested(self.parameter[0]) else '...'
57
- right = self.parameter[1].to_string(interested) if interested(self.parameter[1]) else '...'
58
- if self.parameter[1].meta.newline is not None:
59
- new = '\u2570' # '\u2936'
60
- return f"{left}\n{new} \"{self.parameter[1].meta.newline}\" {self.name} {right}"
61
- return f"{left} {self.name} {right}"
62
- elif self.fixity == 'prefix':
63
- if len(self.parameter) == 0:
64
- return self.name
65
- tmp = [x.to_string(interested) if interested(x) else '...' for x in self.parameter]
66
- return f"{self.name}({','.join(str(x) for x in tmp)})"
67
- elif self.fixity == 'postfix':
68
- if len(self.parameter) == 0:
69
- return self.name
70
- tmp = [x.to_string(interested) if interested(x) else '...' for x in self.parameter]
71
- return f"({','.join(str(x) for x in tmp)}).{self.name}"
72
- else:
73
- return f"Invalid fixity: {self.fixity}"
74
- return None
75
-
76
-
77
-
78
51
 
79
52
  @dataclass(frozen=True)
80
53
  class Syntax(Generic[A, S]):
81
54
  """
82
55
  The core signature of Syntax is take an Algebra Class and return an Algebra Instance.
83
56
  """
84
- alg: Callable[[Type[Algebra[Any, Any]]], Algebra[A, S]]
57
+ alg: Callable[[Type[Algebra[Any, Any]], Cache[Any, Any]], Algebra[A, S]]
85
58
  meta: Description = field(default_factory=Description, repr=False)
86
59
 
87
60
  def algebra(self, name: str | MethodType | FunctionType, *args: Any, **kwargs: Any) -> Syntax[A, S]:
@@ -98,8 +71,8 @@ class Syntax(Generic[A, S]):
98
71
  Returns:
99
72
  A new Syntax reflecting the transformed algebra.
100
73
  """
101
- def algebra_run(cls: Type[Algebra[Any, S]]) -> Algebra[Any, S]:
102
- a = self.alg(cls)
74
+ def algebra_run(cls: Type[Algebra[Any, S]], cache: Cache[Any, Any]) -> Algebra[Any, S]:
75
+ a = self(cls, cache)
103
76
  if isinstance(name, str):
104
77
  attr = getattr(a, name, None) or getattr(cls, name, None)
105
78
  if attr is None:
@@ -124,16 +97,14 @@ class Syntax(Generic[A, S]):
124
97
  def as_(self, typ: Type[B]) -> B:
125
98
  return cast(typ, self) # type: ignore
126
99
 
127
- def __call__(self, alg: Type[Algebra[Any, Any]]) -> Algebra[A, S]:
128
- return self.alg(alg)
100
+ def __call__(self, alg: Type[Algebra[Any, Any]], cache: Cache[Any, Any]) -> Algebra[A, S]:
101
+ return self.alg(alg, cache)
102
+
129
103
 
130
- def to_string(self, interested: Callable[[Any], bool]) -> Optional[str]:
131
- return self.meta.to_string(interested)
132
104
 
133
105
  def describe(
134
106
  self,
135
107
  *,
136
- newline: Optional[str] = None,
137
108
  name: Optional[str] = None,
138
109
  fixity: Optional[Literal['infix', 'prefix', 'postfix']] = None,
139
110
  parameter: Optional[Tuple[Syntax[Any, S], ...]] = None,
@@ -141,12 +112,10 @@ class Syntax(Generic[A, S]):
141
112
  return self.__class__(
142
113
  alg=self.alg,
143
114
  meta=self.meta.update(
144
- name=name, newline=newline, fixity=fixity, parameter=parameter
115
+ name=name, fixity=fixity, parameter=parameter
145
116
  ),
146
117
  )
147
118
 
148
- def newline(self, info: str = '') -> Syntax[A, S]:
149
- return self.describe(newline=info)
150
119
 
151
120
  ######################################################## value transformation ########################################################
152
121
  def map(self, f: Callable[[A], B]) -> Syntax[B, S]:
@@ -158,7 +127,7 @@ class Syntax(Generic[A, S]):
158
127
  Returns:
159
128
  Syntax yielding B with the same resulting state.
160
129
  """
161
- return self.__class__(lambda cls: self.alg(cls).map(f), meta=self.meta) # type: ignore
130
+ return self.__class__(lambda cls, cache: self(cls, cache).map(f), meta=self.meta) # type: ignore
162
131
 
163
132
  def bimap(self, f: Callable[[A], B], i: Callable[[B], A]) -> Syntax[B, S]:
164
133
  """Bidirectionally map values with an inverse, keeping round-trip info.
@@ -173,7 +142,7 @@ class Syntax(Generic[A, S]):
173
142
  Returns:
174
143
  Syntax yielding B with state alignment preserved.
175
144
  """
176
- return self.__class__(lambda cls: self.alg(cls).bimap(f, i), meta=self.meta) # type: ignore
145
+ return self.__class__(lambda cls, cache: self(cls, cache).bimap(f, i), meta=self.meta) # type: ignore
177
146
 
178
147
  def map_all(self, f: Callable[[A, S], Tuple[B, S]]) -> Syntax[B, S]:
179
148
  """Map both value and state on success.
@@ -184,7 +153,7 @@ class Syntax(Generic[A, S]):
184
153
  Returns:
185
154
  Syntax yielding transformed value and state.
186
155
  """
187
- return self.__class__(lambda cls: self.alg(cls).map_all(f), meta=self.meta) # type: ignore
156
+ return self.__class__(lambda cls, cache: self(cls, cache).map_all(f), meta=self.meta) # type: ignore
188
157
 
189
158
  def map_error(self, f: Callable[[Optional[Any]], Any]) -> Syntax[A, S]:
190
159
  """Transform the error payload when this syntax fails.
@@ -195,7 +164,7 @@ class Syntax(Generic[A, S]):
195
164
  Returns:
196
165
  Syntax that preserves successes and maps failures.
197
166
  """
198
- return self.__class__(lambda cls: self.alg(cls).map_error(f), meta=self.meta)
167
+ return self.__class__(lambda cls, cache: self(cls, cache).map_error(f), meta=self.meta)
199
168
 
200
169
  def map_state(self, f: Callable[[S], S]) -> Syntax[A, S]:
201
170
  """Map the input state before running this syntax.
@@ -206,7 +175,7 @@ class Syntax(Generic[A, S]):
206
175
  Returns:
207
176
  Syntax that runs with f(state).
208
177
  """
209
- return self.__class__(lambda cls: self.alg(cls).map_state(f), meta=self.meta)
178
+ return self.__class__(lambda cls, cache: self(cls, cache).map_state(f), meta=self.meta)
210
179
 
211
180
  def flat_map(self, f: Callable[[A], Algebra[B, S]]) -> Syntax[B, S]:
212
181
  """Chain computations where the next step depends on the value.
@@ -217,7 +186,7 @@ class Syntax(Generic[A, S]):
217
186
  Returns:
218
187
  Syntax yielding the result of the chained computation.
219
188
  """
220
- return self.__class__(lambda cls: self.alg(cls).flat_map(f)) # type: ignore
189
+ return self.__class__(lambda cls, cache: self(cls, cache).flat_map(f)) # type: ignore
221
190
 
222
191
  def many(self, *, at_least: int = 1, at_most: Optional[int] = None) -> Syntax[Many[A], S]:
223
192
  """Repeat this syntax and collect results into Many.
@@ -232,7 +201,7 @@ class Syntax(Generic[A, S]):
232
201
  Syntax producing Many of values.
233
202
  """
234
203
  return self.__class__(
235
- lambda cls: self.alg(cls).many(at_least=at_least, at_most=at_most) # type: ignore
204
+ lambda cls, cache: self(cls, cache).many(at_least=at_least, at_most=at_most) # type: ignore
236
205
  ).describe(
237
206
  name='*', fixity='prefix', parameter=(self,)
238
207
  ) # type: ignore
@@ -283,13 +252,11 @@ class Syntax(Generic[A, S]):
283
252
  ):
284
253
  return Many(value=(left,) + tuple([b.right for b in bs]))
285
254
  case _:
286
- raise ValueError(f"Bad data shape {a}")
255
+ raise SyncraftError(f"Bad data shape {a}", offending=a, expect="Then(BOTH) with Choice on the right")
287
256
 
288
257
  def i(a: Many[A]) -> Then[A, Choice[Many[Then[B | None, A]], Optional[Nothing]]]:
289
258
  if not isinstance(a, Many) or len(a.value) < 1:
290
- raise ValueError(
291
- f"sep_by inverse expect Many with at least one element, got {a}"
292
- )
259
+ raise SyncraftError(f"sep_by inverse expect Many with at least one element, got {a}", offending=a, expect="Many with at least one element")
293
260
  if len(a.value) == 1:
294
261
  return Then(
295
262
  kind=ThenKind.BOTH,
@@ -349,7 +316,7 @@ class Syntax(Generic[A, S]):
349
316
  Returns:
350
317
  Syntax that marks downstream failures as committed.
351
318
  """
352
- return self.__class__(lambda cls: self.alg(cls).cut())
319
+ return self.__class__(lambda cls, cache: self(cls, cache).cut())
353
320
 
354
321
  ###################################################### operator overloading #############################################
355
322
  def __floordiv__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
@@ -363,14 +330,14 @@ class Syntax(Generic[A, S]):
363
330
  Returns:
364
331
  Syntax producing Then(left, right, kind=LEFT).
365
332
  """
366
- other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
333
+ other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
367
334
  ret: Syntax[Then[A, B], S] = self.__class__(
368
- lambda cls: self.alg(cls).then_left(other.alg(cls)) # type: ignore
335
+ lambda cls, cache: self(cls, cache).then_left(other(cls, cache)) # type: ignore
369
336
  ) # type: ignore
370
337
  return ret.describe(name=ThenKind.LEFT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
371
338
 
372
339
  def __rfloordiv__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
373
- other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
340
+ other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
374
341
  return other.__floordiv__(self)
375
342
 
376
343
  def __add__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
@@ -384,14 +351,14 @@ class Syntax(Generic[A, S]):
384
351
  Returns:
385
352
  Syntax producing Then(left, right, kind=BOTH).
386
353
  """
387
- other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
354
+ other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
388
355
  ret: Syntax[Then[A, B], S] = self.__class__(
389
- lambda cls: self.alg(cls).then_both(other.alg(cls)) # type: ignore
356
+ lambda cls, cache: self(cls, cache).then_both(other(cls, cache)) # type: ignore
390
357
  ) # type: ignore
391
358
  return ret.describe(name=ThenKind.BOTH.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
392
359
 
393
360
  def __radd__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
394
- other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
361
+ other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
395
362
  return other.__add__(self)
396
363
 
397
364
  def __rshift__(self, other: Syntax[B, S]) -> Syntax[Then[A, B], S]:
@@ -405,14 +372,14 @@ class Syntax(Generic[A, S]):
405
372
  Returns:
406
373
  Syntax producing Then(left, right, kind=RIGHT).
407
374
  """
408
- other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
375
+ other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
409
376
  ret: Syntax[Then[A, B], S] = self.__class__(
410
- lambda cls: self.alg(cls).then_right(other.alg(cls)) # type: ignore
377
+ lambda cls, cache: self(cls, cache).then_right(other(cls, cache)) # type: ignore
411
378
  ) # type: ignore
412
379
  return ret.describe(name=ThenKind.RIGHT.value, fixity='infix', parameter=(self, other)).as_(Syntax[Then[A, B], S])
413
380
 
414
381
  def __rrshift__(self, other: Syntax[B, S]) -> Syntax[Then[B, A], S]:
415
- other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
382
+ other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
416
383
  return other.__rshift__(self)
417
384
 
418
385
  def __or__(self, other: Syntax[B, S]) -> Syntax[Choice[A, B], S]:
@@ -426,14 +393,14 @@ class Syntax(Generic[A, S]):
426
393
  Returns:
427
394
  Syntax producing Choice.LEFT or Choice.RIGHT.
428
395
  """
429
- other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
396
+ other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
430
397
  ret: Syntax[Choice[A, B], S] = self.__class__(
431
- lambda cls: self.alg(cls).or_else(other.alg(cls)) # type: ignore
432
- ) # type: ignore
398
+ lambda cls, cache: self(cls, cache).or_else(other(cls, cache)) # type: ignore
399
+ )
433
400
  return ret.describe(name='|', fixity='infix', parameter=(self, other))
434
401
 
435
402
  def __ror__(self, other: Syntax[B, S]) -> Syntax[Choice[B, A], S]:
436
- other = other if isinstance(other, Syntax) else self.lift(other).as_(Syntax[B, S])
403
+ other = other if isinstance(other, Syntax) else lift(other).as_(Syntax[B, S])
437
404
  return other.__or__(self)
438
405
 
439
406
  def __invert__(self) -> Syntax[Choice[A, Optional[Nothing]], S]:
@@ -515,34 +482,46 @@ class Syntax(Generic[A, S]):
515
482
  return m.value if isinstance(m, Marked) else m
516
483
 
517
484
  return self.bimap(mark_s, imark_s).describe(name=f'mark("{name}")', fixity='postfix', parameter=(self,))
485
+
486
+ def when(f: Callable[[], bool], then: Syntax[A, S], otherwise: Syntax[B, S]) -> Syntax[A | B, S]:
487
+ """
488
+ Conditionally selects between two syntax branches based on a predicate function.
518
489
 
519
- def dump_error(self, formatter: Optional[Callable[[Error], None]] = None) -> Syntax[A, S]:
520
- def dump_error_run(err: Any) -> Any:
521
- if isinstance(err, Error) and formatter is not None:
522
- formatter(err)
523
- return err
524
-
525
- return self.__class__(lambda cls: self.alg(cls).map_error(dump_error_run))
490
+ Args:
491
+ f: A callable returning a boolean to choose the branch.
492
+ then: Syntax to use if f() is True.
493
+ otherwise: Syntax to use if f() is False.
526
494
 
527
- def debug(
528
- self,
529
- label: str,
530
- formatter: Optional[
531
- Callable[[Algebra[Any, S], S, Either[Any, Tuple[Any, S]]], None]
532
- ] = None,
533
- ) -> Syntax[A, S]:
534
- return self.__class__(lambda cls: self.alg(cls).debug(label, formatter), meta=self.meta)
495
+ Returns:
496
+ A Syntax object representing the chosen branch.
497
+ """
498
+ return lazy(lambda: then if f() else otherwise).describe(name='when(?)', fixity='postfix', parameter=(then, otherwise)) # type: ignore
535
499
 
500
+ def fail(error: Any) -> Syntax[Any, Any]:
501
+ """
502
+ Creates a syntax node that always fails with the given error.
536
503
 
537
-
538
- def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
539
- return Syntax(lambda cls: cls.lazy(lambda: thunk()(cls))).describe(name='lazy(?)', fixity='postfix')
504
+ Args:
505
+ error: The error to raise or propagate.
540
506
 
541
- def fail(error: Any) -> Syntax[Any, Any]:
542
- return Syntax(lambda alg: alg.fail(error)).describe(name=f'fail({error})', fixity='prefix')
507
+ Returns:
508
+ A Syntax object that always fails.
509
+ """
510
+ return Syntax(lambda alg, cache: alg.fail(error, cache=cache)).describe(name=f'fail({error})', fixity='prefix')
543
511
 
544
512
  def success(value: Any) -> Syntax[Any, Any]:
545
- return Syntax(lambda alg: alg.success(value)).describe(name=f'success({value})', fixity='prefix')
513
+ """
514
+ Creates a syntax node that always succeeds with the given value.
515
+
516
+ Args:
517
+ value: The value to return on success.
518
+
519
+ Returns:
520
+ A Syntax object that always succeeds.
521
+ """
522
+ return Syntax(lambda alg, cache: alg.success(value, cache=cache)).describe(name=f'success({value})', fixity='prefix')
523
+
524
+
546
525
 
547
526
  def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
548
527
  """
@@ -550,18 +529,126 @@ def choice(*parsers: Syntax[Any, S]) -> Syntax[Any, S]:
550
529
  """
551
530
  return reduce(lambda a, b: a | b, parsers) if len(parsers) > 0 else success(Nothing())
552
531
 
553
- def run(syntax: Syntax[A, S], alg: Type[Algebra[A, S]], use_cache:bool, *args: Any, **kwargs: Any) -> Tuple[Any, FrozenDict[str, Tuple[Any, ...]]] | Tuple[Any, None]:
532
+ def run(*,
533
+ syntax: Syntax[A, S],
534
+ alg: Type[Algebra[A, S]],
535
+ use_cache:bool,
536
+ **kwargs: Any) -> Tuple[Any, None | S]:
554
537
  """
555
538
  Run the syntax over the given algebra, and return the result and bind.
539
+
540
+ Args:
541
+ *args, **kwargs: the arguments passed to alg.state to construct the state object of the algebra.
556
542
  """
557
- parser = syntax(alg)
558
- input: Optional[S] = alg.state(*args, **kwargs)
543
+ parser = syntax(alg, Cache())
544
+ input: Optional[S] = alg.state(**kwargs)
559
545
  if input:
560
- result = parser.run(input, use_cache=use_cache)
561
- if isinstance(result, Right):
562
- return result.value[0], result.value[1].binding.bound()
563
- assert isinstance(result, Left), "Algebra must return Either[E, Tuple[A, S]]"
564
- return result.value, None
546
+ gen = parser.run(input, use_cache=use_cache)
547
+ try:
548
+ result = next(gen)
549
+ while isinstance(result, Incomplete):
550
+ old_input = result.state
551
+ result = gen.send(old_input)
552
+ return Error(this=result, message="Algebra yield data that is not Incomplete"), None
553
+ except StopIteration as e:
554
+ result = e.value
555
+ if isinstance(result, Right):
556
+ return result.value[0], result.value[1]
557
+ elif isinstance(result, Left):
558
+ return result.value, None
559
+ else:
560
+ return Error(this=result, message="Algebra returned data that is not Left or Right"), None
561
+ return Error(this=None, message="Algebra failed to create initial state"), None
562
+
563
+
564
+ def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
565
+ syntax: Optional[Syntax[A, S]] = None
566
+ algebra: Optional[Algebra[A, S]] = None
567
+ def syntax_lazy_run(cls: Type[Algebra[Any, S]], cache: Cache) -> Algebra[A, S]:
568
+ nonlocal syntax, algebra
569
+ # print('==' * 20, 'Syntax.lazy.syntax_lazy_run', '==' * 20)
570
+ # print('thunk', thunk, id(thunk))
571
+ # print('syntax', syntax, id(syntax))
572
+ # print('algebra', algebra, id(algebra))
573
+ if syntax is None:
574
+ syntax = thunk()
575
+ def algebra_lazy_f():
576
+ if syntax is None:
577
+ raise SyncraftError("Lazy thunk did not resolve to a Syntax", offending=thunk, expect="a Syntax")
578
+ return syntax(cls, cache)
579
+ if algebra is None:
580
+ algebra = cls.lazy(algebra_lazy_f, cache=cache)
581
+ return algebra
582
+ return Syntax(syntax_lazy_run).describe(name='lazy(?)', fixity='postfix')
583
+
584
+
585
+ # def lazy(thunk: Callable[[], Syntax[A, S]]) -> Syntax[A, S]:
586
+ # resolved: Optional[Syntax[A, S]] = None
587
+
588
+ # def run_lazy(cls: Type[Algebra[Any, S]], cache: Cache) -> Algebra[A, S]:
589
+ # nonlocal resolved
590
+ # if resolved is None:
591
+ # resolved = thunk()
592
+ # return resolved(cls, cache) # reuse the same Algebra instance
593
+ # return Syntax(run_lazy)
594
+
595
+
596
+
597
+
598
+ def token(*,
599
+ text: Optional[str] = None,
600
+ token_type: Optional[Enum] = None,
601
+ case_sensitive: bool = False,
602
+ regex: Optional[re.Pattern[str]] = None
603
+ ) -> Syntax[Any, Any]:
604
+ """Build a ``Syntax`` that matches a single token.
605
+
606
+ Convenience wrapper around ``Parser.token``. You can match by
607
+ type, exact text, or regex.
608
+
609
+ Args:
610
+ token_type: Expected token enum type.
611
+ text: Exact token text to match.
612
+ case_sensitive: Whether text matching respects case.
613
+ regex: Pattern to match token text.
614
+
615
+ Returns:
616
+ Syntax[Any, Any]: A syntax that matches one token.
617
+ """
618
+ token_type_txt = token_type.name if token_type is not None else None
619
+ token_value_txt = text if text is not None else None
620
+ msg = 'token(' + ','.join([x for x in [token_type_txt, token_value_txt, str(regex)] if x is not None]) + ')'
621
+ return Syntax(
622
+ lambda cls, cache: cls.factory('token', token_type=token_type, text=text, case_sensitive=case_sensitive, regex=regex, cache=cache)
623
+ ).describe(name=msg, fixity='prefix')
624
+
625
+
626
+
627
+ def literal(lit: str) -> Syntax[Any, Any]:
628
+ """Match an exact literal string (case-sensitive)."""
629
+ return token(token_type=None, text=lit, case_sensitive=True)
630
+
631
+ def regex(regex: re.Pattern[str] | str) -> Syntax[Any, Any]:
632
+ """Match a token whose text satisfies the given regular expression."""
633
+ if isinstance(regex, str):
634
+ regex = re.compile(regex)
635
+ return token(token_type=None, regex=regex, case_sensitive=True)
636
+
637
+ def lift(value: Any)-> Syntax[Any, Any]:
638
+ """Lift a Python value into the nearest matching token syntax.
639
+
640
+ - ``str`` -> ``literal``
641
+ - ``re.Pattern`` -> ``token`` with regex
642
+ - ``Enum`` -> ``token`` with type
643
+ - otherwise -> succeed with the value
644
+ """
645
+ if isinstance(value, str):
646
+ return literal(value)
647
+ elif isinstance(value, re.Pattern):
648
+ return token(regex=value)
649
+ elif isinstance(value, Enum):
650
+ return token(token_type=value)
565
651
  else:
566
- return Error(this=None, message="Algebra failed to create initial state"), None
652
+ return Syntax(lambda cls, cache: cls.success(value, cache=cache))
653
+
567
654