Fluent Validation Combinator (`Req`)
Req (gram.core.combinators.req.Req) validates that the token at parser.pos matches a target token type and runs a chain of validation and transformation functions on a shallow copy (copy.copy) of the token before delegating it to analyzer.delegate_return_node(cloned_token).
1. Constructor
Req(target_token: Token | CustomToken | str)
target_token: ExpectedTokenenum member,CustomToken, or token name string (Token.IDENT,"IDENT", etc.).- Each chained validation or transformation method appends an operation to
self._operationsand returnsself.
2. Validation Methods
If any validation fails, Req leaves parser.pos unchanged, reports ReqConstraintFailed (GrammarError), and returns None.
Case & Naming Format Checks
.is_lower(): Requiresstr(t.value).islower()to beTrue..is_upper(): Requiresstr(t.value).isupper()to beTrue..is_snake_case(): Requiresval == val.lower()and" " not in val(whereval = str(t.value))..is_camel_case(): Requiresvalto be non-empty,val[0].isalpha(),"_" not in val, and" " not in val.
Prefix, Suffix & Substring Checks
.startswith(prefix: str): Requiresstr(t.value).startswith(prefix)..endswith(suffix: str): Requiresstr(t.value).endswith(suffix)..contains(*items: str): Requires every substring initemsto appear instr(t.value)..excludes(*items: str): Requires none of the substrings initemsto appear instr(t.value).
Length, Numeric Bounds & Equality
.length(n: int): Requireslen(str(t.value)) == n..min(n: int | float):- If
isinstance(t.value, (int, float)): requirest.value >= n. - Otherwise: requires
len(str(t.value)) >= n.
- If
.max(n: int | float):- If
isinstance(t.value, (int, float)): requirest.value <= n. - Otherwise: requires
len(str(t.value)) <= n.
- If
.mindig(n: int): Requires at leastncharacters instr(t.value)to satisfychar.isdigit()..maxdig(n: int): Requires at mostncharacters instr(t.value)to satisfychar.isdigit()..equal(expected: Any): Requiresstr(t.value) == str(expected)..unequal(not_expected: Any): Requiresstr(t.value) != str(not_expected).
3. Transformation Methods
Transformations mutate cloned_token.value in-place (without mutating the original token in parser.tokens if a subsequent step fails):
.apply_lower(): Setst.value = str(t.value).lower()..apply_upper(): Setst.value = str(t.value).upper()..apply_camel(): Splitsstr(t.value).replace("_", " ")on whitespace and joins asparts[0].lower() + "".join(x.title() for x in parts[1:])..apply_zfill(width: int): Setst.value = str(t.value).zfill(width)..replace(old: str, new: str): Setst.value = str(t.value).replace(old, new)..change(new_val: Any): Setst.value = new_val..addl(prefix: str): Setst.value = prefix + str(t.value)..addr(suffix: str): Setst.value = str(t.value) + suffix..transform(target_fn: Callable[[Any], Any]): Setst.value = target_fn(t.value). Iftarget_fnraises an exception,Reqcatches it and reportsReqConstraintFailed.
4. Return Shape & Diagnostics
- Success: Consumes the token from
parserand returnsanalyzer.delegate_return_node(cloned_token)(Identifier,LiteralNumber,LiteralString,ASTKeyword, orLiteralSymbol), wherenode.token.valueholds the transformed value. - Diagnostics on Failure:
ReqUnexpectedEOF(ParserError) ifparser.empty()isTrue.ReqTokenMismatch(ParserError) if the current token's type name does not matchtarget_token.ReqConstraintFailed(GrammarError) if any validation or.transform()step fails.
5. Example
from gram.core.combinators import Req
from gram.core.lexer import Token
# Lowercase snake_case identifier of at least 3 chars, normalized to UPPERCASE
const_name = (
Req(Token.IDENT)
.is_snake_case()
.min(3)
.apply_upper()
)
# Number between 10 and 100 inclusive
port_number = Req(Token.NUMBER).min(10).max(100)