math_spec.resolution
Name resolution — the pass that makes the core AST fully typed.
Parsers emit NameNode: a token, not yet a meaning. This module rewrites
each one into a typed node (VariableNode / ParameterNode / DimensionNode /
LookupNode, and
ParameterComparisonNode / DimensionComparisonNode / ParameterDefinedNode on the where
side), so the AST reaching either backend holds no unresolved names.
Doing this once here is what makes scoping identical across the lanes by construction rather than by test: a backend that resolves for itself is one that can build a model the other refuses. The name-resolution rules live in the language reference.
The namespace is flat and collisions are load errors; macro formals are the one scope, and may not collide with a declared dimension.
Namespace(variables, parameters, dimensions, lookups=None, dtypes=None)
#
The declared names of one schema, by kind.
Flat by construction: :meth:kind is a single lookup, not an ordered
walk through several stores.
Source code in src/math_spec/resolution.py
dimensions = frozenset(dimensions)
instance-attribute
#
dtypes = dict(dtypes or {})
instance-attribute
#
lookups = dict(lookups or {})
instance-attribute
#
parameters = frozenset(parameters)
instance-attribute
#
variables = frozenset(variables)
instance-attribute
#
groupable()
#
The lookups a by= may name: name -> the dimension it maps into.
A label space is absent, which is what makes naming one in a by=
answerable with the promotion rewrite rather than "no such lookup".
Source code in src/math_spec/resolution.py
into_of(lookup)
#
kind(name)
#
'variable' | 'parameter' | 'dimension' | 'lookup' | None.
Source code in src/math_spec/resolution.py
of(schema)
classmethod
#
Build the namespace of schema.
Every name a file may use is declared in that file (hard rule 5), so the schema is the whole namespace and there is nothing to widen it with.
Source code in src/math_spec/resolution.py
expression_of(text, schema, ns, context)
#
Parse, expand and resolve text — the only way a backend gets an AST.
validation.py runs the same path at load time, so a backend calling
this gets a typed tree off a result already known to be clean, without
duplicating the pass.
| RAISES | DESCRIPTION |
|---|---|
LanguageError
|
Listing every problem the text has. |
Source code in src/math_spec/resolution.py
resolve_expression(node, ns, context, errors)
#
Rewrite every NameNode under node to a typed node.
Operator call shapes are checked here too (operators.call_shape_error).
Arity is a language rule, and this is the pass every consumer goes through,
so neither backend has to state a signature a second time.
| RETURNS | DESCRIPTION |
|---|---|
ExpressionNode | None
|
The typed tree, or |
ExpressionNode | None
|
errors rather than raising, so a caller collecting problems across a |
ExpressionNode | None
|
whole schema reports them together. |
Source code in src/math_spec/resolution.py
resolve_where(node, ns, context, errors, self_variable=None)
#
Rewrite a parsed where AST into typed predicates.
Both parameters and dimensions are legal here — a where-string is a predicate over the frame, and the frame carries its own coordinates. What is not legal is an unknown name: read as "scalar False" it would mask every row out and produce an empty model in silence.
Source code in src/math_spec/resolution.py
where_of(text, ns, context, self_variable=None)
#
Parse and resolve a where string; None stays None.
| RAISES | DESCRIPTION |
|---|---|
LanguageError
|
Listing every problem the predicate has. |