Pratt Expression Combinators
gram.core.combinators.expressions provides a Top-Down Operator Precedence (Pratt) parser for prefix unary and infix binary expressions with parenthesized grouping (Token.LPAREN ... Token.RPAREN).
1. Operator Precedence & Binding Power Tables
Binding power helpers:
bind_left(p: int) -> tuple[int, int]: returns(2 * p, 2 * p + 1)for left-associative binary operators((a op b) op c).bind_right(p: int) -> tuple[int, int]: returns(2 * p, 2 * p - 1)for right-associative binary operators(a op (b op c)).
Prefix Unary Operators (right_bp)
| Dictionary | Tokens | Symbols / Lexemes | right_bp |
|---|---|---|---|
ARITHMETIC_UNARY_OPS |
Token.PLUS, Token.MINUS, Token.NOT, Token.INCREMENT, Token.DECREMENT, Token.SQRT |
+, -, ~, ++, --, √ |
150 |
CONDITIONAL_UNARY_OPS |
Token.NOT_LOGIC, Token.LOGIC_NOT, Token.EXCLAMATION |
not, ! |
50 |
Infix Binary Operators ((left_bp, right_bp))
| Dictionary | Precedence $p$ | (left_bp, right_bp) |
Associativity | Tokens (MAP_SYMBOLS) |
|---|---|---|---|---|
ARITHMETIC_BINARY_OPS |
80 | (160, 159) |
Right | Token.POW (**) |
ARITHMETIC_BINARY_OPS |
70 | (140, 141) |
Left | Token.STAR (*), Token.SLASH (/), Token.FLOOR_DIV (//), Token.PERCENT (%) |
ARITHMETIC_BINARY_OPS |
60 | (120, 121) |
Left | Token.PLUS (+), Token.MINUS (-) |
ARITHMETIC_BINARY_OPS |
50 | (100, 101) |
Left | Token.SHL (<<), Token.SHR (>>) |
ARITHMETIC_BINARY_OPS |
45 | (90, 91) |
Left | Token.AND (&) |
ARITHMETIC_BINARY_OPS |
40 | (80, 81) |
Left | Token.XOR (^) |
ARITHMETIC_BINARY_OPS |
35 | (70, 71) |
Left | Token.OR (|) |
CONDITIONAL_BINARY_OPS |
30 | (60, 61) |
Left | Token.EQUAL (==), Token.NOT_EQUAL (!=), Token.LESS (<), Token.LESS_EQUAL (<=), Token.GREATER (>), Token.GREATER_EQUAL (>=) |
CONDITIONAL_BINARY_OPS |
20 | (40, 41) |
Left | Token.AND_LOGIC (&&), Token.LOGIC_AND (and) |
CONDITIONAL_BINARY_OPS |
15 | (30, 31) |
Left | Token.OR_LOGIC (||), Token.LOGIC_OR (or) |
DUAL_BINARY_OPS and DUAL_UNARY_OPS are ChainMap views combining the conditional and arithmetic operator dictionaries.
2. Expression Combinator Classes
All three classes inherit from BaseExpr and share the same constructor signature:
ArithmeticExpr(*values: Token | str | Ref | type[RuleItem] | Iterable[...])
ConditionalExpr(*values: Token | str | Ref | type[RuleItem] | Iterable[...])
DualExpr(*values: Token | str | Ref | type[RuleItem] | Iterable[...])
ArithmeticExpr: UsesARITHMETIC_BINARY_OPSandARITHMETIC_UNARY_OPS.ConditionalExpr: UsesCONDITIONAL_BINARY_OPSandCONDITIONAL_UNARY_OPS.DualExpr: UsesDUAL_BINARY_OPSandDUAL_UNARY_OPSso arithmetic and comparison/logical operators can be mixed in a single expression (e.g.,x + 1 >= 10 && !done).
3. Allowed Operand Specifications (*values)
When *values is omitted, BaseExpr defaults to (Token.NUMBER, Token.IDENT).
Each item passed in *values (or inside an iterable passed to *values) is validated at construction time:
Tokenenum member (or validTokenname/symbolstr, e.g.,"NUMBER"): Matched as a terminal token atom (note:Token.KEYWORDtokens are never matched as token atoms).RuleItemsubclass withnodeable = True: Automatically wrapped inRef(rule).Refcombinator with a non-empty.name: Evaluated speculatively insideanalyzer.failure_scope()before falling back to token atoms. Passing aRefwithname=Noneandnodeable=FalseraisesUnnamedRefInExpr(GrammarError).
Invalid string names raise InvalidExprTokenName; any other type raises UnsupportedExprOperand.
Parenthesized Grouping
Parentheses (Token.LPAREN ... Token.RPAREN) are handled natively at atom positions:
- Enters bracket-aware whitespace mode (
parser.enter_delimiter()/parser.skip_whitespace()), soNEWLINE,INDENT, andDEDENTinside(...)are skipped. - Empty parentheses
()fail and reportEmptyParenthesesInExpr. - Missing
)reportsUnclosedExprParenEOForUnclosedExprParenMismatch.
4. Return Shapes
- Single Atom: Returns the atom's
ASTNodedirectly (LiteralNumber,Identifier,ASTRef, etc.). - Prefix Unary Operation: Returns
ASTUnaryOp(operator: TokenType, operand: ASTNode)(gram.core.combinators.expressions.ASTUnaryOp). - Infix Binary Operation: Returns
ASTBinaryOp(left: ASTNode, operator: TokenType, right: ASTNode)(gram.core.combinators.expressions.ASTBinaryOp).
5. Example
from gram.core.combinators import DualExpr, Enclosed, MatchToken, Ref, RuleItem, Separator, Seq
from gram.core.lexer import Token
class FuncCall(RuleItem):
nodeable = True
entrypoint = Seq(
MatchToken(Token.IDENT),
Enclosed(
open=Token.LPAREN,
content=Separator(values=[Token.IDENT, Token.NUMBER], sep=Token.COMMA),
close=Token.RPAREN,
),
)
# Evaluates FuncCall first (Ref atoms are tried before bare Token atoms),
# then falls back to IDENT, NUMBER, STRING, or BOOL.
expr = DualExpr(
Ref(FuncCall),
Token.IDENT,
Token.NUMBER,
Token.STRING,
Token.BOOL,
)