TileFoundry Spec - Parser¶
The Parser accepts authored Python functions and produces HIR or TIR through one typed API.
1. Public API¶
@module executes its Python class body and finalizes the collected Function declarations,
child Modules, and ordinary methods. Module authoring is two-phase: class execution records
Function, specialization, and converter declarations; finalization attaches all child Modules
and puts them in parser scope before parsing Functions in source order. @func produces an HIR
Function; @prim_func produces a TIR PrimFunction.
specialize and converter register variants and weight converters on an existing HIR Function.
def parse_function(
fn: FunctionType, context: FuncParserContext
) -> hir.Function | tir.PrimFunction: ...
1.1 HIR Return Contracts¶
An ordinary HIR function and a specialization variant MAY omit -> return-type;
an ordinary function then records its inferred body.type as Function.return_type.
When either declares an annotation, the parser MUST require
types_compatible(annotation, body.type). This is directional compatibility,
not a parser-only equality relation: an annotation with layout=None leaves
layout unconstrained according to the shared type rule.
A pass HIR function is a dispatch prototype and MUST declare -> return-type.
That annotation is its Function.return_type and callable return type. Every
variant body MUST be compatible with that base return type; its own IR return
type remains the exact base type so all variants share one dispatch signature.
Tensor[...] without a storage slot constructs storage=GMEM, including in a
return annotation. It is not an unspecified-storage spelling. Consequently an
SMEM body under -> Tensor[...] must explicitly return a GMEM result; the
parser reports both the annotation and inferred body type, with the authored
function location, when they are incompatible. tuple[...] annotations are
accepted and apply this same compatibility rule recursively to every field.
1.2 Source Span Metadata¶
Every parser-authored Call reachable from a Function body carries SourceSpanMetadata for
the AST expression that constructed it. A parent match fills only Calls without a span, so it
cannot replace a more precise child span. Traversal follows Call operands and IR Tuple
values, but does not assign source identity to shared lexical Var values. Source spans use
physical source-file coordinates with a one-based start column.
1.3 Tuple Binding Metadata¶
For a, b = producer(...), detached TupleGetItem(index=0) and
TupleGetItem(index=1) lexical values carry the respective target Name spans (a and b) and
matching BindingMetadata; later reads do not replace that identity. A multi-carry loop's
derived projections carry the for statement span and their carry binding name.
1.4 Context and Diagnostics¶
FuncParserContext carries the dialect, Function role, closure, topology scope, target, and
optional base/key for one parse. FunctionRole is ROOT, VARIANT, or CONVERTER.
ParseError is the single authored-source diagnostic type and includes source location and
recursive parse situation. These are the only public parser symbols.
1.5 Mesh declarations and region captures¶
Mesh declarations are values. Mesh(...) constructs the compile-time domain
object used by layout sugar and by a MeshScope; it is not itself a runtime
expression in the HIR value graph. A with Mesh(...) statement uses that value
to delimit an execution domain and does not describe the placement of its
result. An as name binding is lexical: it is available inside the with
body and expires when the statement ends. When a body binds names that are read
after the with, each escaping name is rebound to the enclosing MeshScope
result (a tuple region with TupleGetItem projections when several names
escape); names used only inside the body remain local to the scope. Names read
inside a region but bound outside it are captured as MeshScope.args, with a
fresh MeshScope.params binding used by the body. Capture is performed one
region boundary at a time, so nested regions pass a value through each door.
2. Syntax and Rules¶
2.1 Syntax¶
; root: function
; literal: Python ast.Constant syntax, e.g. 1, "bf16", or None
; name: Python variable name; primary: name/attribute base for calls and subscripts
; expression: Python syntax composed from literals, names, primaries, and operators
; runtime-expression: expression lowered to a TileFoundry IR Expr
mesh-axis ::= identifier
| identifier '.' identifier
dim-expr ::= integer-literal
| identifier
| primary '.' identifier
| dim-expr ('+' | '-' | '*' | '//' | '%') dim-expr
| (identifier | primary '.' identifier) '(' (dim-expr (',' dim-expr)*)?
')'
placed-layout ::= '(' ((expression '@' ('(' mesh-axis (',' mesh-axis)* ')' | mesh-axis) |
dim-expr) (',' (expression '@' ('(' mesh-axis (',' mesh-axis)* ')' |
mesh-axis) | dim-expr))*)? ')'
shape ::= '(' (dim-expr (',' dim-expr)*)? ')'
| identifier
| primary '.' identifier
tensor-shape-layout ::= placed-layout
| shape
dtype ::= string-literal
| primary
literal ::= None
| Ellipsis
| boolean-literal
| integer-literal
| float-literal
| complex-literal
| string-literal
| bytes-literal
primary ::= identifier
| primary '.' identifier
sequence ::= '(' (expression (',' expression)*)? ')'
| '[' (expression (',' expression)*)? ']'
| '{' (expression (',' expression)*)? '}'
dict ::= '{' (expression ':' expression (',' expression ':' expression)*)? '}'
binary-operation ::= expression ('+' | '-' | '*' | '/' | '//' | '%' | '**') expression
unary-operation ::= ('+' | '-' | 'not') expression
slice ::= (expression)? ':' (expression)? (':' expression)?
subscript ::= expression '[' expression ']'
expression ::= literal
| primary
| sequence
| dict
| binary-operation
| unary-operation
| call
| slice
| subscript
call ::= expression '(' ((expression | keyword-name '=' expression) (','
(expression | keyword-name '=' expression))*)? ')'
explicit-layout ::= '(' tensor-shape-layout ',' shape ')'
plain-layout ::= '(' (dim-expr (',' dim-expr)*)? ')'
layout ::= None
| primary
| call
| explicit-layout
| placed-layout
| plain-layout
storage ::= string-literal
| primary
tensor-optional-slot ::= layout
| storage
tensor ::= tensor-head '[' '(' (tensor-shape-layout ',' dtype | tensor-shape-layout
',' dtype ',' tensor-optional-slot | tensor-shape-layout ',' dtype ','
tensor-optional-slot ',' tensor-optional-slot) ')' ']'
tuple-type ::= 'tuple' '[' '(' type-annotation (',' type-annotation)* ')' ']'
| 'tuple' '[' type-annotation ']'
scalar-type ::= primary
type-annotation ::= tensor
| tuple-type
| scalar-type
signature ::= (name ':' type-annotation (',' name ':' type-annotation)*)?
return-type ::= type-annotation
loop-iterator ::= 'tile' '(' expression ',' expression ')'
| 'range' '(' (expression | expression ',' expression | expression ','
expression ',' expression) ')'
loop-carry-statement ::= expression '=' expression
| 'for' name 'in' expression ':' loop-carry
| statement
loop-carry ::= (loop-carry-statement (newline loop-carry-statement)*)?
loop-header ::= 'for' identifier 'in' loop-iterator ':' loop-carry
loop-body ::= (statement (newline statement)*)?
for ::= 'for' name 'in' expression ':' loop-body
mesh-context ::= ('Mesh' | primary '.' identifier) '(' (expression | ('layout' | 'names')
'=' expression) (',' (expression | ('layout' | 'names') '='
expression))* ')'
| expression
with ::= 'with' mesh-context ('as' identifier)? ':' block
op-call ::= primary '(' ((expression | keyword-name '=' expression) (',' (expression |
keyword-name '=' expression))*)? ')'
launch ::= callee '(' ')'
slice-endpoint-binary ::= index-endpoint dim-op index-endpoint
mesh-coordinate ::= identifier '.' identifier
index-endpoint ::= literal
| primary
| slice-endpoint-binary
| mesh-coordinate
| runtime-expression
| expression
index-slice ::= (index-endpoint)? ':' (index-endpoint)? (':' index-endpoint)?
subscript-index ::= '(' ((index-slice | index-endpoint) (',' (index-slice |
index-endpoint))*)? ')'
| index-slice
| index-endpoint
subscript-expression ::= runtime-expression '[' subscript-index ']'
matmul-expression ::= runtime-expression '@' runtime-expression
binary-expression ::= runtime-expression ('+' | '-' | '*' | '/' | '//' | '%') runtime-expression
| runtime-expression ('==' | '!=' | '<' | '<=' | '>' | '>=')
runtime-expression
| runtime-expression ('and' | 'or') runtime-expression
unary-expression ::= unary-op runtime-expression
name ::= identifier
constant ::= boolean-literal
| integer-literal
| float-literal
tuple-expression ::= '(' (runtime-expression (',' runtime-expression)*)? ')'
runtime-expression ::= op-call
| launch
| subscript-expression
| matmul-expression
| binary-expression
| unary-expression
| mesh-coordinate
| name
| constant
| tuple-expression
| tensor
| primary '.' identifier
tuple-assignment ::= '(' identifier (',' identifier)* ')' '=' runtime-expression
where-annotation ::= 'where' '(' ')'
statement ::= for
| with
| tuple-assignment
| identifier '=' (runtime-expression | expression)
| identifier ':' (where-annotation | type-annotation) ('='
(runtime-expression | expression))?
| 'return' (runtime-expression)?
| runtime-expression
| 'pass'
block ::= (statement (newline statement)*)?
function ::= 'def' name '(' signature ')' ('->' return-type)? ':' block
2.2 Rules¶
| Owner | Situation | Rule | Statement | Source |
|---|---|---|---|---|
| binary_expression, matmul_expression, op_call, unary_expression | expression, slice_endpoint, subscript_index | CallBindingRule | A call must bind its arguments into a Call tuple. | src/tilefoundry/parser/pattern_nodes.py |
| binary_expression, matmul_expression, op_call, unary_expression | expression, slice_endpoint, subscript_index | CallTypeInferenceRule | A call's result type must be inferred from its binding. | src/tilefoundry/parser/pattern_nodes.py |
| dim_expr | dim_expr, layout_extent, layout_shape, tensor_dim_expr, tensor_optional_slot, tensor_shape | ShapeDimRule | A shape dimension must be an integer, DimVar, or expression. | src/tilefoundry/parser/ast_pattern.py |
| dtype | tensor_dtype | CanonicalDTypeRule | A dtype must resolve to a canonical DType. | src/tilefoundry/parser/ast_pattern.py |
| explicit_layout, layout, placed_layout, plain_layout | layout_shape, tensor_optional_slot, tensor_shape | LayoutPositionRule | A layout must be legal for its parser position. | src/tilefoundry/parser/ast_pattern.py |
| explicit_layout, layout, placed_layout, plain_layout | layout_shape, tensor_optional_slot, tensor_shape | LayoutShapeRule | A layout must have a valid non-boolean shape. | src/tilefoundry/parser/ast_pattern.py |
| function | function | FunctionDialectRule | A function kind and constructed value must agree with the active dialect. | src/tilefoundry/parser/pattern_nodes.py |
| function | function | FunctionRegistrationRule | A validated function must be registered exactly once in its owning scope. | src/tilefoundry/parser/pattern_nodes.py |
| function | function | FunctionReturnCompatibilityRule | A HIR body with a return annotation must satisfy that annotation; a dispatch prototype must declare one, and each variant body must satisfy the prototype return contract. | src/tilefoundry/parser/pattern_nodes.py |
| function | function | FunctionRoleValidationRule | A root, variant, or converter must satisfy its role before registration. | src/tilefoundry/parser/pattern_nodes.py |
| function | function | FunctionSignatureRule | A function must construct an ordered parameter tuple. | src/tilefoundry/parser/pattern_nodes.py |
| index_slice | subscript_index | TileWindowSliceBoundRule | A tile window cannot be used as a slice bound. | src/tilefoundry/parser/pattern_nodes.py |
| module | module_finalization | ModuleFinalizationRule | A module declaration must contain valid unique members and a resolvable entry. | src/tilefoundry/parser/ast_pattern.py |
| module | module_function | ModuleFunctionRegistrationRule | A validated module function must be recorded in declaration order. | src/tilefoundry/parser/ast_pattern.py |
| module | module_function | ModuleFunctionValidationRule | A module function must satisfy its root, variant, or converter role before mutation. | src/tilefoundry/parser/ast_pattern.py |
| op_call | expression, slice_endpoint, subscript_index | CallVariadicInputFormRule | A variadic call must use one explicit list, tuple, or supported static list comprehension. | src/tilefoundry/parser/pattern_nodes.py |
| shape | layout_shape, layout_strides, tensor_shape | ShapeTupleRule | A shape must construct a tuple of dimensions. | src/tilefoundry/parser/ast_pattern.py |
| storage | tensor_optional_slot | StorageValueRule | Storage must resolve to a StorageKind. | src/tilefoundry/parser/ast_pattern.py |
| tensor | annotation, expression, slice_endpoint, subscript_index, type_annotation | TensorLayoutStorageRule | A tensor type must contain compatible layout and storage values. | src/tilefoundry/parser/ast_pattern.py |
| tensor | annotation, expression, slice_endpoint, subscript_index, type_annotation | TensorPositionRule | A tensor type's storage must be legal for its dialect and position. | src/tilefoundry/parser/ast_pattern.py |
3. Implementation Overview¶
| Component | Responsibility |
|---|---|
| Parser API and Context | Receives authored Functions and carries dialect, role, scope, and recursion inputs. |
| Executable Pattern Graph | Composes concrete AST elements into the Function root pattern. |
| Match and Construction | Matches recursively into AstMatch, then constructs owner values on return. |
| Ordered Rules | Validates and normalizes each owner value after construction. |
| Module Build | Lets Python execute the class body, collects declarations, resolves child Modules first, then parses Functions in source order and finalizes the Module. |
| Pattern Visitor | Traverses the same graph to render this section's generated grammar and constraints. |
| Refusal | Carries the reason from the pattern that claimed a node and then refused it, so a report names a cause rather than the absence of a match. |
classDiagram
ParserAPI --> FuncParserContext
ParserAPI --> FunctionPattern
AstPattern <|.. Element
Element o-- AstPattern
Element o-- AstRule
AstPattern --> AstMatch
AstPattern --> MatchFailure
MatchFailure <|.. PatternFailure
MatchFailure <|.. ChoiceFailure
ChoiceFailure o-- MatchFailure : causes
ParseError <.. MatchFailure
PatternVisitor ..> AstPattern
ParserAPI ..> ModuleBuild
flowchart TD
API["parse_function(fn, context)"] --> AST["Extract FunctionDef AST"]
AST --> ROOT["FunctionPattern.match"]
ROOT --> TREE["AstMatch tree"]
TREE --> BACKWARD["construct children, then apply Rules"]
BACKWARD --> FUNCTION["HIR Function / TIR PrimFunction"]
FUNCTION --> MODULE{"Module authoring context?"}
MODULE -->|yes| FINALIZE["defer declaration"]
FINALIZE --> CHILDREN["attach child Modules and bind module scope"]
CHILDREN --> ORDERED["parse roots in source order; then variants/converters"]
ORDERED --> BUILT["construct final Module and verify"]
BUILT --> RETURN
MODULE -->|no| RETURN["return standalone result"]
flowchart TD
TRY["alternative.match(node)"] --> OUT{"outcome"}
OUT -->|"AstMatch"| WIN["choice accepts it; pending refusals are discarded"]
OUT -->|"MatchFailure"| CLAIM["claimed the node and refused: reason recorded"]
OUT -->|"None"| PASS["did not recognize the node: nothing recorded"]
PASS --> NEXT["try the next alternative"]
CLAIM --> NEXT
NEXT --> DONE{"any refusal recorded?"}
DONE -->|no| SILENT["return None: no alternative recognized this node"]
DONE -->|yes| COLLECT["ChoiceFailure over the claimants"]
COLLECT --> UP["travels up unchanged; combinators add no wrapping"]
UP --> RENDER["render(): a sole claimant is the whole report"]
RENDER --> RAISE["ParseError with source location"]
SILENT --> RAISE
A pattern MUST establish that a node is its own before it refuses with a reason. That claim is
what makes the reason trustworthy: it says no remaining alternative can accept this node, so
the refusal is the author's mistake and not another pattern's turn. A callee resolving to an op
schema is such a claim, and a wrong argument count after it is an error. A callee that does not
resolve is not a claim: it may be a bare name or a foreign namespace that another alternative
owns, so the pattern returns None and says nothing. Reasons are never reconstructed from the
AST after the fact; an inspection outside the refusing pattern cannot see which step it failed
at, and becomes a second, divergent copy of that knowledge.
Being last in one choice is not a claim either, because that choice may itself be an
alternative in another. parse_node is the single place where no alternative remains, so it
is the only place that MAY describe a node from its shape rather than from a pattern's
statement, and it does so only when the shape says something worth reading.
None and a MatchFailure differ only for a choice; every other combinator returns either one
unchanged, so a refusal keeps the identity and the wording of the pattern that produced it all
the way to parse_node. Nothing is wrapped, filtered, or re-described on the way up. A
ChoiceFailure records only the alternatives that claimed the node, which is normally one, and
it renders as that sole claimant. Two claimants mean two patterns claim overlapping shapes; the
report states both rather than choosing between them, because the ambiguity is in the grammar
and not in the report.
Pattern combinators serve both runtime matching and Spec traversal. AstMatch separates syntax
matching from object construction, while each Rule reads the recursive context after its owner
value exists. Module class control flow remains Python execution; no Module AST grammar exists.