Repetition and Delimited Combinators
Repetition combinators evaluate loops, token scanning up to a stopping condition or indentation level, and delimiter-separated sequences.
1. Many (Zero-or-More)
Repeatedly evaluates combinator ($0..n$ times) while tokens remain in the stream (not parser.empty()).
Constructor
Many(combinator: Combinator)
Execution & Loop Termination
Each iteration saves a checkpoint ("MANY-Iteration") and runs combinator inside an isolated analyzer.failure_scope():
- Match: Appends the result to
results. If the child matched without advancing the cursor (parser.pos == checkpoint.pos), the loop terminates immediately to prevent infinite zero-width loops. - Mismatch (
res is None): Restores the iteration checkpoint and exits the loop.- If the failed iteration had already advanced past
checkpoint.pos(attempt.candidate[0] > checkpoint.pos), that deep partial failure is staged and published (when not inside an outersafe_mode). - If the iteration failed cleanly at
checkpoint.pos, the failure is discarded.
- If the failed iteration had already advanced past
- Signals:
BreakFlowSignal: Restores the current iteration checkpoint and exits the loop cleanly.SkipFlowSignal: Ifparser.pos == checkpoint.pos, restores the checkpoint and exits the loop; if the iteration had already advanced the cursor, keeps the consumed tokens and starts the next iteration.
- Return Value: Always returns
list[Any]([]when 0 iterations match).
Example
from gram.core.combinators import Many, MatchGroup, MatchToken, Seq
from gram.core.lexer import Token
declarations = Many(
Seq(MatchGroup("types"), MatchToken(Token.IDENT))
)
2. Some (One-or-More)
Repeatedly evaluates combinator ($1..n$ times). Requires at least one successful iteration.
Constructor
Some(combinator: Combinator)
Execution & Return Shape
- Shares the same iteration,
BreakFlowSignal,SkipFlowSignal, and zero-width guard behavior asMany. - 1 or more matches: Returns
list[Any]of matched results (and publishes any deeper partial failure from the final rejected iteration when not insafe_mode). - 0 matches: Restores the initial checkpoint, stages/publishes the first iteration's failure candidate (or
SomeMinimumNotMet), and returnsNone.
Example
from gram.core.combinators import MatchToken, Some
from gram.core.lexer import Token
non_empty_idents = Some(MatchToken(Token.IDENT))
3. Until
Probes target at each cursor position and, while target does not match, consumes one token at a time and converts it via analyzer.delegate_return_node(tok).
Constructor
Until(
target: Combinator,
*,
consume_target: bool = False,
max_tokens: int | None = None,
)
consume_target:False(default): Restores the probe checkpoint whentargetmatches, leaving thetargettokens unconsumed in the stream.True: Keeps thetargettokens consumed and appends (or extends, iftargetreturned alist)target's result into the returned list.
max_tokens: Optional maximum number of single-token steps allowed beforetargetmust match.
Return Value & Diagnostics
- Match: Returns
list[Any]of delegatedASTTokenNodeitems consumed beforetarget(plustarget's result whenconsume_target=True). - EOF or
max_tokensreached without matchingtarget: Restores the initial checkpoint, reportsUntilTargetNotFound(GrammarError), and returnsNone.
Example
from gram.core.combinators import MatchToken, Until
from gram.core.lexer import Token
# Collects tokens as AST nodes up to ';', leaving ';' at parser.pos
until_semicolon = Until(MatchToken(Token.SEMICOLON), consume_target=False)
4. UntilIndent
Consumes tokens one by one (converting each via analyzer.delegate_return_node) until encountering a Token.INDENT or Token.DEDENT token that satisfies target.
Constructor
UntilIndent(
target: int | Literal["back", "front"] | str,
*,
consume_target: bool = False,
allow_eof: bool = False,
max_tokens: int | None = None,
)
target:int($\ge 0$): Matches when aDEDENTreaches $\le \text{target}$ (from a higher initial level) or anINDENTreaches $\ge \text{target}$ (from a lower initial level), or equalstarget."back": Matches aToken.DEDENTwhose indentation value is lower than the indentation level active whenUntilIndentstarted."front": Matches aToken.INDENTwhose indentation value is higher than the indentation level active whenUntilIndentstarted.- Any negative integer or unrecognized string raises
InvalidUntilIndentTarget(GrammarError) at construction time.
consume_target: IfFalse(default), stops before the matchingINDENT/DEDENTtoken. IfTrue, consumes it and appends its delegated node.allow_eof: IfTrue, reachingEOFwithout seeing the target indentation token succeeds and returnscollected.
Return Value & Diagnostics
- Match: Returns
list[Any]of delegatedASTNodeinstances. - Failure: If
EOFormax_tokensis reached without matching (andallow_eof=False), restores the initial checkpoint, reportsUntilIndentTargetNotFound(GrammarError), and returnsNone.
Example
from gram.core.combinators import UntilIndent
# Consume tokens until dedenting below the current block level
body_tokens = UntilIndent("back", consume_target=False, allow_eof=True)
5. Separator
Parses delimiter-separated elements (such as a, b, c) with minimum/maximum element counts and optional trailing delimiter support.
Note: The class is named
Separator(there is noSepalias ingram.core.combinators).
Constructor
Separator(
values: Sequence[Any] | Combinator | Token | type[RuleItem] | str,
sep: Combinator | Token | str = Token.COMMA,
min: int = 0,
max: int | None = None,
allow_trailing: bool = True,
)
valuesnormalization: Accepts a single item or a sequence of:Tokenenum members or validTokenname/symbol strings (wrapped inMatchToken).RuleItemsubclasses withnodeable = True(wrapped inRef(rule)).Refinstances that produce anASTRef(nameis non-empty, which is automatic whenrule.nodeable = True). Passing aRefwithname=Noneandnodeable=FalseraisesUnnamedRefInSeparator(GrammarError).- Any other
Combinatorinstance. - Unrecognized strings raise
InvalidSeparatorTokenName; unsupported types raiseUnsupportedSeparatorValue.
sepnormalization: Accepts aCombinator, aTokenenum member, or astr(resolved viaToken.from_string,MAP_SYMBOLS, orMatchSeqSymbol). Unsupported types raiseUnsupportedSeparatorDelimiter.min: Minimum required elements (defaults to0).max: Optional cap on matched elements (Nonefor unbounded).allow_trailing: WhenTrue(default), allows a trailing separator after the last matched element without requiring another element.
Return Shape (ArrayNode)
Returns an ArrayNode (gram.core.ast.nodes.ArrayNode) on success, or None if fewer than min elements matched:
node.items:tuple[Any, ...]of matched element nodes.node.trailing:bool(Trueif a trailing separator was consumed).- Supports
len(node),iter(node),node[i], andbool(node).
Example
from gram.core.combinators import Separator
from gram.core.lexer import Token
ident_list = Separator(
values=[Token.IDENT],
sep=Token.COMMA,
min=1,
allow_trailing=True,
)