Structural Combinators
Structural combinators compose child combinators into ordered sequences, prioritized alternatives, optional elements, and delimiter-enclosed blocks.
1. Seq
Seq evaluates child combinators in linear order. You can also compose sequences with the + operator (comb1 + comb2).
Constructor
Seq(*combinators: Combinator)
- Raises
EmptySequence(GrammarError) at construction time if called with no arguments.
Execution & Return Shape
- Saves a checkpoint (
"SEQ-Analysis") before evaluating children. - For each child combinator:
- If the child returns
None,Seqrestores the checkpoint, stages/flushesSequenceElementMismatch(unless a deeper failure was already staged), and returnsNone. - If the child returns
[](for example, an omittedOpt, a 0-matchMany,Peek, orNot),Seqtreats the step as successful and omits[]from the output list. - Otherwise,
Seqappends the child's result toresults.
- If the child returns
- Control-Flow Signals:
SkipFlowSignalis caught bySeq: the sequence stops immediately and returns theresultsaccumulated so far as a successful match.BacktrackSignal,BreakFlowSignal,CutFlowSignal, andAbortFlowSignalare not caught bySeqand propagate to the enclosing combinator (Alt,Many,Some,Separator).
- Return Value:
list[Any]of non-[]child results on success, orNoneon failure.
Example
from gram.core.combinators import MatchKeyword, MatchToken, Opt, Seq
from gram.core.lexer import Token
# Using Seq(...) or the + operator:
let_binding = Seq(
MatchKeyword("let"),
MatchToken(Token.IDENT),
Opt(MatchToken(Token.ASSIGN) + MatchToken(Token.NUMBER)),
)
2. Alt (Alternative)
Evaluates candidate branches in order until the first branch returns a non-None result. You can also compose alternatives with the | operator (comb1 | comb2).
Constructor
Alt(*combinators: Combinator, ignore: bool = False)
ignore: WhenTrue,Visitordoes not increment nesting depth or record thisAltcontainer itself inRayTracing(ASTAnalyzer.is_valid_entrypointsetsignore = Trueon the root entrypointAlt).- Raises
EmptyAlternatives(GrammarError) at construction time if called with no branches.
Execution, Signals & Diagnostics
- Each branch is evaluated inside an isolated
analyzer.failure_scope(). - If a branch returns
None(or raisesBacktrackSignal),Altrestores the checkpoint, keeps the deepest failure candidate (best_staged) across rejected branches, and tries the next branch. - If a branch raises
CutFlowSignal:Altcommits to that branch's failure candidate (discarding candidates from earlier branches), restores the checkpoint, stops trying remaining branches, and reports the failure.
- If all branches fail,
Altreportsbest_staged(orNoAlternativeMatchedat the starting position if no branch staged a specific error) and returnsNone. - Return Value: The exact return value of the first matching branch, or
None.
Example
from gram.core.combinators import Alt, MatchToken
from gram.core.lexer import Token
atom = (
MatchToken(Token.NUMBER)
| MatchToken(Token.STRING)
| MatchToken(Token.IDENT)
)
3. Opt (Optional)
Evaluates a single child combinator with $0..1$ cardinality inside analyzer.failure_scope().
Constructor
Opt(combinator: Combinator)
- Raises
InvalidOptionalTarget(GrammarError) at construction time ifcombinatoris not aCombinatorinstance.
Execution & Deep Partial Failures
- Matched: Returns the child combinator's result directly.
- Clean Omission (0 tokens consumed): If the child fails without advancing past
checkpoint.pos(candidateisNoneorcandidate[0] <= checkpoint.pos),Optrestores the checkpoint without reporting an error and returns[]. - Deep Partial Failure (
candidate[0] > checkpoint.pos): If the child consumed one or more tokens before failing inside a multi-step sub-rule (for example, matching=inOpt(Seq(MatchToken(Token.ASSIGN), MatchToken(Token.NUMBER)))and failing on the missing number),Optrestores the checkpoint, reports the deep failure candidate, and returnsNone.
Example
from gram.core.combinators import MatchToken, Opt, Seq
from gram.core.lexer import Token
optional_initializer = Opt(
Seq(MatchToken(Token.ASSIGN), MatchToken(Token.NUMBER))
)
4. Enclosed
Matches an opening Token, an inner content combinator, and a closing Token.
Constructor
Enclosed(
open: Token,
content: Combinator,
close: Token,
skip_whitespace: bool = True,
allow_empty: bool = True,
)
openandclosemust beTokenenum members; any other type raisesInvalidDelimiterType(GrammarError) at construction time.skip_whitespace: WhenTrue, callsparser.enter_delimiter()/parser.exit_delimiter()andparser.skip_whitespace(), which incrementsparser.bracket_depthand skipsToken.NEWLINE,Token.INDENT, andToken.DEDENTinside the delimiters.allow_empty: WhenTrue, ifcloseimmediately followsopen,contentis not evaluated andEnclosedreturns[]. WhenFalse, empty delimiters (or acontentmatch that consumes 0 tokens) fail withEmptyEnclosedContent.
Return Value & Diagnostics
- Match: Returns the result of
content(or[]when empty andallow_empty=True). Theopenandclosetokens are consumed but not included in the returned value. - Failure: Restores the initial checkpoint, reports one of
UnexpectedEOFBeforeOpenDelimiter,OpeningDelimiterMismatch,EmptyEnclosedContent,EnclosedContentMismatch,UnclosedDelimiterEOF, orClosingDelimiterMismatch, and returnsNone.
Example
from gram.core.combinators import Enclosed, Separator
from gram.core.lexer import Token
param_list = Enclosed(
open=Token.LPAREN,
content=Separator(values=[Token.IDENT], sep=Token.COMMA),
close=Token.RPAREN,
skip_whitespace=True,
allow_empty=True,
)