Module

compiler.expressions

Expression compilation for Kida compiler.

Provides mixin for compiling Kida expression AST nodes to Python AST expressions.

Uses inline TYPE_CHECKING declarations for host attributes. See: plan/rfc-mixin-protocol-typing.md

Classes

ExpressionCompilationMixin 11
Mixin for compiling expressions. Host attributes and cross-mixin dependencies are declared via inl…

Mixin for compiling expressions.

Host attributes and cross-mixin dependencies are declared via inline TYPE_CHECKING blocks.

Methods

Internal Methods 11
_get_filter_suggestion 1 str | None
Find closest matching filter name for typo suggestions. Uses difflib.get_close…
def _get_filter_suggestion(self, name: str) -> str | None

Find closest matching filter name for typo suggestions.

Uses difflib.get_close_matches with 0.6 cutoff for reasonable typo detection. Returns None if no close match found.

Parameters
Name Type Description
name
Returns
str | None
_get_test_suggestion 1 str | None
Find closest matching test name for typo suggestions. Uses difflib.get_close_m…
def _get_test_suggestion(self, name: str) -> str | None

Find closest matching test name for typo suggestions.

Uses difflib.get_close_matches with 0.6 cutoff for reasonable typo detection. Returns None if no close match found.

Parameters
Name Type Description
name
Returns
str | None
_is_potentially_string 1 bool
Check if node could produce a string value (macro call, filter chain). Used to…
def _is_potentially_string(self, node: Any) -> bool

Check if node could produce a string value (macro call, filter chain).

Used to determine when numeric coercion is needed for arithmetic operations. Recursively checks nested expressions to catch Filter nodes inside parentheses.

This handles cases like (a | length) + (b | length) where the left/right operands are Filter nodes that need numeric coercion.

Parameters
Name Type Description
node
Returns
bool
_wrap_coerce_numeric 1 ast.expr
Wrap expression in _coerce_numeric() call for arithmetic safety. Ensures that …
def _wrap_coerce_numeric(self, expr: ast.expr) -> ast.expr

Wrap expression in _coerce_numeric() call for arithmetic safety.

Ensures that Markup objects (from macros) are converted to numbers before arithmetic operations, preventing string multiplication.

Parameters
Name Type Description
expr
Returns
ast.expr
_compile_expr 2 ast.expr
Compile expression node to Python AST expression. Complexity: O(1) dispatch + …
def _compile_expr(self, node: Any, store: bool = False) -> ast.expr

Compile expression node to Python AST expression.

Complexity: O(1) dispatch + O(d) for recursive expressions.

Parameters
Name Type Description
node
store Default:False
Returns
ast.expr
_compile_null_coalesce 1 ast.expr
Compile a ?? b to handle both None and undefined variables. Uses _null_coalesc…
def _compile_null_coalesce(self, node: Any) -> ast.expr

Compile a ?? b to handle both None and undefined variables.

Uses _null_coalesce helper to catch UndefinedError for undefined variables. Part of RFC: kida-modern-syntax-features.

The helper is called as:

_null_coalesce(lambda: a, lambda: b)

This allows:

  • a ?? b to return b if a is undefined (UndefinedError)
  • a ?? b to return b if a is None
  • a ?? b to return a if a is any other value (including falsy: 0, '', [])
Parameters
Name Type Description
node
Returns
ast.expr
_compile_optional_getattr 1 ast.expr
Compile obj?.attr using walrus operator to avoid double evaluation. obj?.attr …
def _compile_optional_getattr(self, node: Any) -> ast.expr

Compile obj?.attr using walrus operator to avoid double evaluation.

obj?.attr compiles to: '' if (_oc := obj) is None else (_oc_val := _getattr_none(_oc, 'attr')) if _oc_val is not None else ''

The double check ensures that:

  1. If obj is None, return ''
  2. If obj.attr is None, return '' (for output) but preserve None for ??

For null coalescing to work, we need a different approach: the optional chain preserves None so ?? can check it, but for direct output, None becomes ''.

Actually, we return None but rely on the caller to handle None → '' conversion. For output, the expression is wrapped differently.

Simplified: Return None when short-circuiting, let output handle conversion.

Part of RFC: kida-modern-syntax-features.

Parameters
Name Type Description
node
Returns
ast.expr
_compile_optional_getitem 1 ast.expr
Compile obj?[key] using walrus operator to avoid double evaluation. obj?[key] …
def _compile_optional_getitem(self, node: Any) -> ast.expr

Compile obj?[key] using walrus operator to avoid double evaluation.

obj?[key] compiles to: None if (_oc := obj) is None else _oc[key]

Part of RFC: kida-modern-syntax-features.

Parameters
Name Type Description
node
Returns
ast.expr
_compile_range 1 ast.expr
Compile range literal to range() call. 1..10 → range(1, 11) # inclusiv…
def _compile_range(self, node: Any) -> ast.expr

Compile range literal to range() call.

1..10 → range(1, 11) # inclusive 1...11 → range(1, 11) # exclusive 1..10 by 2 → range(1, 11, 2)

Part of RFC: kida-modern-syntax-features.

Parameters
Name Type Description
node
Returns
ast.expr
_compile_inlined_filter 1 ast.Call
Compile inlined filter to direct method call. Generates: _str(value).method(*a…
def _compile_inlined_filter(self, node: Any) -> ast.Call

Compile inlined filter to direct method call.

Generates: _str(value).method(*args)

This replaces filter dispatch overhead with a direct method call, providing ~5-10% speedup for filter-heavy templates.

Parameters
Name Type Description
node
Returns
ast.Call
_compile_pipeline 1 ast.expr
Compile pipeline: expr |> filter1 |> filter2. Pipelines compile to nested filt…
def _compile_pipeline(self, node: Any) -> ast.expr

Compile pipeline: expr |> filter1 |> filter2.

Pipelines compile to nested filter calls using the _filters dict, exactly like regular filter chains. The difference is purely syntactic.

expr |> a |> b(x) → _filters['b'](_filters'a', x)

Validates filter existence at compile time (same as Filter nodes).

Parameters
Name Type Description
node
Returns
ast.expr