Skip to content

math_spec.expansion

Named sub-expressions and macros, expanded into the core AST before anything reads the expression.

expand(node, schema, context, *, shadow=frozenset(), inlined=None) #

expand(
    node: ArithmeticNode,
    schema: Spec,
    context: str,
    *,
    shadow: frozenset[str] = ...,
    inlined: set[str] | None = ...,
) -> ArithmeticNode
expand(
    node: ComparisonNode,
    schema: Spec,
    context: str,
    *,
    shadow: frozenset[str] = ...,
    inlined: set[str] | None = ...,
) -> ComparisonNode

Expand all named sub-expressions and macro calls under node.

A comparison stays a comparison and an arithmetic node stays arithmetic.

PARAMETER DESCRIPTION
node

The parsed expression.

TYPE: ParsedNode

schema

Where names and macros are declared.

TYPE: Spec

context

What an error names.

TYPE: str

shadow

Names left as written even where a named expression has that name — a template's formals, checked without a call to bind them.

TYPE: frozenset[str] DEFAULT: frozenset()

inlined

Where given, collects the name of every named expression inlined.

TYPE: set[str] | None DEFAULT: None

Source code in src/math_spec/expansion.py
def expand(
    node: ParsedNode,
    schema: Spec,
    context: str,
    *,
    shadow: frozenset[str] = frozenset(),
    inlined: set[str] | None = None,
) -> ParsedNode:
    """Expand all named sub-expressions and macro calls under *node*.

    A comparison stays a comparison and an arithmetic node stays arithmetic.

    Args:
        node: The parsed expression.
        schema: Where names and macros are declared.
        context: What an error names.
        shadow: Names left as written even where a named expression has that
            name — a template's formals, checked without a call to bind them.
        inlined: Where given, collects the name of every named expression inlined.
    """
    if inlined is None:
        inlined = set()
    if isinstance(node, ComparisonNode):
        return ComparisonNode(
            node.op,
            _expand(node.left, schema, context, (), shadow, inlined),
            _expand(node.right, schema, context, (), shadow, inlined),
        )
    return _expand(node, schema, context, (), shadow, inlined)

macro_signature(name, macro) #

Human-readable call signature, for error messages.

Source code in src/math_spec/expansion.py
def macro_signature(name: str, macro: MacroBlock) -> str:
    """Human-readable call signature, for error messages."""
    parts = [*macro.args, *(f'{k}=...' for k in macro.kwargs)]
    return f'{name}({", ".join(parts)})'

parse_and_expand(text, schema, context, *, inlined=None) #

Parse text and expand named sub-expressions and macros to core AST.

PARAMETER DESCRIPTION
text

The expression as the file wrote it.

TYPE: str

schema

Where names and macros are declared.

TYPE: Spec

context

What an error names.

TYPE: str

inlined

Where given, every named expression inlined on the way is added to it — the ones a reference reaches through another entry or a macro included.

TYPE: set[str] | None DEFAULT: None

Source code in src/math_spec/expansion.py
def parse_and_expand(text: str, schema: Spec, context: str, *, inlined: set[str] | None = None) -> ParsedNode:
    """Parse *text* and expand named sub-expressions and macros to core AST.

    Args:
        text: The expression as the file wrote it.
        schema: Where names and macros are declared.
        context: What an error names.
        inlined: Where given, every named expression inlined on the way is
            added to it — the ones a reference reaches through another entry
            or a macro included.
    """
    return expand(parse_expression(text), schema, context, inlined=inlined)

parse_template(name, macro, context) #

Parse a macro template, rejecting comparisons.

Source code in src/math_spec/expansion.py
def parse_template(name: str, macro: MacroBlock, context: str) -> ArithmeticNode:
    """Parse a macro template, rejecting comparisons."""
    return _parse_body(macro.template, f"macro '{name}' template", context)

read_by_the_math(schema) #

The named expressions the math reads: every entry the objective or a constraint inlines, transitively.

Decided by expanding those two positions alone: a bound and a where name no entry, and a piecewise link's expression reaches here through the constraints its expansion emits. The rest of the expressions: section is read back after a solve and never fed to one (:attr:math_spec.program.ExpressionDeclaration.in_math).

Source code in src/math_spec/expansion.py
def read_by_the_math(schema: Spec) -> frozenset[str]:
    """The named expressions the math reads: every entry the objective or a constraint inlines, transitively.

    Decided by expanding those two positions alone: a bound and a ``where``
    name no entry, and a piecewise link's expression reaches here through the
    constraints its expansion emits. The rest of the ``expressions:`` section
    is read back after a solve and never fed to one
    (:attr:`math_spec.program.ExpressionDeclaration.in_math`).

    """
    inlined: set[str] = set()
    for name, block in schema.constraints.items():
        parse_and_expand(block.expression, schema, f"constraint '{name}'", inlined=inlined)
    if schema.objective is not None:
        parse_and_expand(schema.objective.expression, schema, 'the objective', inlined=inlined)
    return frozenset(inlined)