Lexical Matching Combinators
Matching combinators (gram.core.combinators.match) inspect the token at parser.pos. On match, they consume the token (parser.consume()), discard stale staged failures behind the new cursor position, and return the terminal ASTNode created by analyzer.delegate_return_node(token).
Token-to-AST Node Delegation (analyzer.delegate_return_node)
Every matching combinator delegates its consumed TokenType through ASTState.delegate_return_node(current), which maps current.token as follows:
current.token |
Returned ASTNode Subclass |
|---|---|
Token.IDENT |
Identifier |
Token.NUMBER |
LiteralNumber |
Token.STRING |
LiteralString |
Token.KEYWORD |
ASTKeyword |
Any other Token or CustomToken |
LiteralSymbol |
All terminal node classes inherit from ASTTokenNode (gram.core.ast.nodes.ASTTokenNode), exposing the consumed TokenType via node.token.
1. MatchToken
Validates that the current token matches a specific Token enum member, CustomToken, or canonical Token name string.
Important:
MatchTokenexplicitly rejectsToken.KEYWORDtokens (reportingUnexpectedKeywordToken). Keywords must be matched withMatchKeywordorMatchGroup.
Constructor
MatchToken(token: Token | CustomToken | str)
- Passing a
strresolves it viaToken.from_string(token). If the string does not match anyTokenmember,InvalidTokenReference(GrammarError) is raised immediately at construction time.
Return Value & Diagnostics
- Match: Consumes the token and returns the delegated
ASTTokenNode(Identifier,LiteralNumber,LiteralString, orLiteralSymbol). - Mismatch / EOF: Leaves
parser.posunchanged, reportsEmptyTokenStream,EOFReached,UnexpectedKeywordToken, orTokenMismatch(ParserError), and returnsNone.
Example
from gram.core.combinators import MatchToken
from gram.core.lexer import Token
match_ident = MatchToken(Token.IDENT)
match_number = MatchToken("NUMBER")
2. MatchKeyword
Validates that the current token has current.token == Token.KEYWORD and current.value == keyword.
Constructor
MatchKeyword(keyword: str | Keyword)
Return Value & Diagnostics
- Match: Consumes the token and returns an
ASTKeywordnode wrapping the token. - Mismatch / EOF: Reports
EmptyTokenStream,KeywordTypeMismatch, orKeywordValueMismatch(ParserError) and returnsNone.
Example
from gram.core.combinators import MatchKeyword
from gram.core.lexer import words
words.add_keyword("return")
match_return = MatchKeyword("return")
3. MatchGroup
Validates that the current token's value belongs to a registered WordGroup in gram.core.lexer.words (words.exists_in_group(current.value, group)), with optional allow/deny filtering.
Constructor
MatchGroup(
group: str | WordGroup,
values: Iterable[str] | None = None,
behavior_values: Literal["allow", "deny", "none"] = "none",
)
Filter Modes (behavior_values)
"none": Accepts any keyword registered ingroup."allow": Accepts only members ofgroupwhose lexeme is invalues(otherwise reportsGroupValueNotAllowed)."deny": Accepts members ofgroupwhose lexeme is not invalues(otherwise reportsGroupValueDenied).
Return Value & Diagnostics
- Match: Consumes the token and returns an
ASTKeywordnode. - Mismatch / Missing Group:
- If
groupdoes not exist inwords, reportsWordGroupNotFound(LexerError) and returnsNone. - If the token does not belong to
group, reportsGroupMismatch(ParserError) and returnsNone.
- If
Example
from gram.core.combinators import MatchGroup
from gram.core.lexer import add_group, add_keyword
add_group("types")
add_keyword("int", group="types")
add_keyword("str", group="types")
add_keyword("bool", group="types")
match_any_type = MatchGroup("types")
match_numeric_only = MatchGroup("types", values=["int"], behavior_values="allow")
4. MatchSeqSymbol
MatchSeqSymbol validates str(tok.value) against a compiled regular expression (fullmatch) and optionally relabels the token's .token attribute with a CustomToken before delegating to the AST.
Constructor
MatchSeqSymbol(
pattern: str | None = None,
symbols: list[str] | None = None,
regex: str | None = None,
custom_token_name: str | None = None,
is_pattern: bool = True,
case_sensitive: bool = False,
raw: bool = False,
)
- Pattern resolution priority:
regex: Used as-is (is_patternis forced toTrue).symbols: Escapes each string insymbolsand joins them with|(is_patternis forced toTrue).pattern: Used as a regex ifis_pattern=True, or escaped viare.escape(pattern)ifis_pattern=False.
case_sensitive: Defaults toFalse(re.IGNORECASE). Set toTruefor case-sensitive matching.custom_token_name: When provided, replacestok.tokenin-place withCustomToken(custom_token_name, val_str)before delegating toanalyzer.delegate_return_node(tok).
Return Value & Diagnostics
- Match: Consumes the token and returns the delegated
ASTTokenNode(aLiteralSymbolwhencustom_token_nameis set, withnode.token.tokenholding theCustomToken). - Mismatch / EOF: Reports
SymbolUnexpectedEOForSymbolMismatch(ParserError) and returnsNone.
Example
from gram.core.combinators import MatchSeqSymbol
match_colon = MatchSeqSymbol(pattern=":", is_pattern=False)
match_comparison = MatchSeqSymbol(
symbols=["==", "!=", "<=", ">="],
custom_token_name="COMPARISON_OP",
)
5. generador_expr
Helper function in gram.core.combinators.match that builds a regular expression string from literal strings and 2-element character or integer ranges [start, end].
generador_expr(*parts: str | list[str | int] | tuple[str | int, str | int]) -> str
- Literal
strparts are escaped withre.escape. - Consecutive 2-element
[start, end]pairs ofstrorintare merged into a single character class[...]. - Any other argument type raises
InvalidRegexGeneratorPart(GrammarError).
Example
from gram.core.combinators import MatchSeqSymbol, generador_expr
# Produces '#[A-Fa-f0-9]'
hex_digit = generador_expr("#", ["A", "F"], ["a", "f"], [0, 9])
hex_matcher = MatchSeqSymbol(pattern=hex_digit, case_sensitive=True)