# core

URL: /kida/api/compiler/core/
Section: compiler
Description: Kida Compiler Core — main Compiler class.

The Compiler transforms Kida AST into Python AST, then compiles to
executable code objects. Uses a mixin-based design for maintainability.

Design Principles:
1. **AST-to-AST**: Generate `ast.Module`, not source strings
2. **StringBuilder**: Output via `buf.append()`, join at end
3. **Local caching**: Cache `_escape`, `_str`, `buf.append` as locals
4. **O(1) dispatch**: Dict-based node type → handler lookup

Performance Optimizations:
- `LOAD_FAST` for cached functions (vs `LOAD_GLOBAL` + hash)
- Method lookup cached once: `_append = buf.append`
- Single `''.join(buf)` at return (vs repeated concatenation)
- Line markers only for error-prone nodes (Output, For, If, etc.)

Block Inheritance:
Templates with `{% extends %}` generate:
1. Block functions: `_block_header(ctx, _blocks)`
2. Render function: Registers blocks, delegates to parent

    ```python
    def _block_header(ctx, _blocks):
        buf = []
        # Block body...
        return ''.join(buf)

    def render(ctx, _blocks=None):
        if _blocks is None: _blocks = {}
        _blocks.setdefault('header', _block_header)
        return _extends('base.html', ctx, _blocks)
    ```

---

> For a complete page index, fetch /kida/llms.txt.

Open LLM text
(/kida/api/compiler/core/index.txt)

Share with AI

Ask Claude
(https://claude.ai/new?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fkida%2Fapi%2Fcompiler%2Fcore%2Findex.txt)

Ask ChatGPT
(https://chatgpt.com/?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fkida%2Fapi%2Fcompiler%2Fcore%2Findex.txt)

Ask Gemini
(https://gemini.google.com/app?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fkida%2Fapi%2Fcompiler%2Fcore%2Findex.txt)

Ask Copilot
(https://copilot.microsoft.com/?q=Please%20help%20me%20understand%20this%20documentation%3A%20%2Fkida%2Fapi%2Fcompiler%2Fcore%2Findex.txt)

Module

#
`compiler.core`

Kida Compiler Core — main Compiler class.

The Compiler transforms Kida AST into Python AST, then compiles to
executable code objects. Uses a mixin-based design for maintainability.

Design Principles:

- AST-to-AST: Generate`ast.Module`, not source strings

- StringBuilder: Output via`buf.append()`, join at end

- Local caching: Cache`_escape`, `_str`, `buf.append`as locals

- O(1) dispatch: Dict-based node type → handler lookup

Performance Optimizations:

- `LOAD_FAST` for cached functions (vs `LOAD_GLOBAL`+ hash)

- Method lookup cached once:`_append = buf.append`

- Single`''.join(buf)`at return (vs repeated concatenation)

- Line markers only for error-prone nodes (Output, For, If, etc.)

Block Inheritance:

Templates with`{% extends %}`generate:

-

Block functions:`_block_header(ctx, _blocks)`

-

Render function: Registers blocks, delegates to parent

```
    def _block_header(ctx, _blocks):
        buf = []
        # Block body...
        return ''.join(buf)

    def render(ctx, _blocks=None):
        if _blocks is None: _blocks = {}
        _blocks.setdefault('header', _block_header)
        return _extends('base.html', ctx, _blocks)
```

1Class1Function

## Classes

`Compiler`

50

▼

Compile Kida AST to Python code objects.

The Compiler transforms a Kida Template AST into an `ast.…

Compile Kida AST to Python code objects.

The Compiler transforms a Kida Template AST into an`ast.Module`, then
compiles it to a code object ready for`exec()`. The generated code
defines a`render(ctx, _blocks=None)`function.

Node Dispatch:
Uses O(1) dict lookup for node type → handler:
`python dispatch = { "Data": self._compile_data, "Output": self._compile_output, "If": self._compile_if, ... } handler = dispatch[type(node).__name__] `

Line Tracking:
For nodes that can cause runtime errors (Output, For, If, etc.),
generates`_get_render_ctx().line = N`before the node's code.
This updates the ContextVar-stored RenderContext instead of polluting
the user's ctx dict, enabling rich error messages with source line
numbers while keeping user context clean.

#### Attributes

Name
Type
Description

`_NODE_DISPATCH_NAMES`

`ClassVar[dict[str, str]]`

—

`_class_dispatch`

`ClassVar[dict[str, Callable] | None]`

—

`_SCOPED_PARENTS`

`ClassVar[dict[str, str]]`

—

`_last_block_cacheable_vars`

`set[str]`

—

`_last_block_compiled_stmts`

`list[ast.stmt] | None`

—

`_env`

—

Parent Environment (for filter/test access)

`_name`

`str | None`

Template name for error messages

`_filename`

`str | None`

Source file path for compile()

`_locals`

`set[str]`

Set of local variable names (loop vars, macro args)

`_blocks`

`dict[str, Block | Region]`

Dict of block_name → Block node (for inheritance)

`_block_counter`

`int`

Counter for unique variable names

#### Methods

`warnings`

0

`list`

▼

Compile-time warnings accumulated during compilation.

property

`def warnings(self) -> list`

##### Returns

`list`

`precomputed`

0

`list[Any]`

▼

Values that must be injected into the exec() namespace as ``_pc_N``.

property

`def precomputed(self) -> list[Any]`

##### Returns

`list[Any]`

`compile`

3

`types.CodeType`

▼

Compile template AST to code object.

`def compile(self, node: TemplateNode, name: str | None = None, filename: str | None = None) -> types.CodeType`

##### Parameters

Name
Type
Description

`node`
`—`

Root Template node

`name`
`—`

Template name for error messages

Default:`None`

`filename`
`—`

Source filename for error messages

Default:`None`

##### Returns

`types.CodeType`

Compiled code object ready for exec()

Internal Methods
36

▼

`_ensure_dispatch`

0

`dict[str, Callable]`

▼

Build and cache the class-level dispatch table of unbound functions.

classmethod

`def _ensure_dispatch(cls) -> dict[str, Callable]`

##### Returns

`dict[str, Callable]`

`__init_subclass__`

1

▼

`def __init_subclass__(cls, **kwargs: Any) -> None`

##### Parameters

Name
Type
Description

`**kwargs`
`—`

`__init__`

1

▼

`def __init__(self, env: Environment)`

##### Parameters

Name
Type
Description

`env`
`—`

`_emit_warning`

4

▼

Record a compile-time warning.

`def _emit_warning(self, code, message: str, *, lineno: int | None = None, suggestion: str | None = None) -> None`

##### Parameters

Name
Type
Description

`code`
`—`

`message`
`—`

`lineno`
`—`

Default:`None`

`suggestion`
`—`

Default:`None`

`_get_literal_extends_target`

1

`str | None`

▼

Return literal extends target if template uses {% extends "literal" %}, else No…

`def _get_literal_extends_target(self, node: TemplateNode) -> str | None`

Return literal extends target if template uses {% extends "literal" %}, else None.

##### Parameters

Name
Type
Description

`node`
`—`

##### Returns

`str | None`

`_check_definition_nesting`

2

▼

Recursively walk the AST and reject {% def %} / {% region %}
nested inside any …

`def _check_definition_nesting(self, nodes: Sequence[Node], parent_chain: tuple[str, ...]) -> None`

Recursively walk the AST and reject {% def %} / {% region %}
nested inside any control-flow construct.

`parent_chain`carries the human-readable tag names of enclosing
scoped parents (e.g.`("for", "if")`). When a `Def` or `Region`
is reached and the chain is non-empty, raises`TemplateSyntaxError`
with code`K-TPL-004`.

Other container nodes (Block, CallBlock, Embed, Def, Region, ...)
are traversed but do not contribute to the chain — defining nested
helpers inside them is a supported pattern.

##### Parameters

Name
Type
Description

`nodes`
`—`

`parent_chain`
`—`

`_collect_blocks`

1

▼

Recursively collect all Block nodes from the AST.

This ensures nested blocks (…

`def _collect_blocks(self, nodes: Sequence[Node]) -> None`

Recursively collect all Block nodes from the AST.

This ensures nested blocks (blocks inside blocks, blocks inside
conditionals, etc.) are all registered for compilation.

##### Parameters

Name
Type
Description

`nodes`
`—`

`_emit_output`

1

`ast.stmt`

▼

Generate output statement: yield (streaming) or _append (StringBuilder).

All o…

`def _emit_output(self, value_expr: ast.expr) -> ast.stmt`

Generate output statement: yield (streaming) or _append (StringBuilder).

All output generation in compiled templates flows through this method,
allowing the compiler to switch between StringBuilder and generator modes.

##### Parameters

Name
Type
Description

`value_expr`
`—`

##### Returns

`ast.stmt`

`_compile_template`

1

`ast.Module`

▼

Generate Python module from template.

Produces both StringBuilder functions (r…

`def _compile_template(self, node: TemplateNode) -> ast.Module`

Generate Python module from template.

Produces both StringBuilder functions (render, block) and
generator functions (render_stream, block_stream) in a single
module. The StringBuilder path is used by Template.render() and
the generator path by Template.render_stream().

When async constructs are detected (AsyncFor, Await), also generates
async generator functions (render_stream_async, block*_stream_async).

##### Parameters

Name
Type
Description

`node`
`—`

##### Returns

`ast.Module`

`_make_globals_setup`

1

`ast.FunctionDef | None`

▼

Generate _globals_setup(ctx) from {% globals %}, {% imports %}, and top-level i…

`def _make_globals_setup(self, node: TemplateNode) -> ast.FunctionDef | None`

Generate _globals_setup(ctx) from {% globals %}, {% imports %}, and top-level imports.

Scans the template body for:

- Top-level FromImport and Import nodes (so render_block has macros in scope)

- Globals and Imports nodes (macros/variables for block context)

This function is called by render_block() to inject macros and variables
into the block's context. Returns None if neither globals nor top-level
imports exist.

##### Parameters

Name
Type
Description

`node`
`—`

##### Returns

`ast.FunctionDef | None`

`_make_runtime_preamble`

10

`list[ast.stmt]`

▼

Build shared runtime locals preamble for generated functions.

`def _make_runtime_preamble(self, *, include_blocks_guard: bool = False, include_scope_stack: bool = False, include_escape_str: bool = False, include_getattr: bool = False, include_buf_append: bool = False, include_acc: bool = False, acc_none: bool = False, include_cap: bool = False, include_lookup_scope: bool = False, include_render_ctx: bool = False) -> list[ast.stmt]`

##### Parameters

Name
Type
Description

`include_blocks_guard`
`—`

Default:`False`

`include_scope_stack`
`—`

Default:`False`

`include_escape_str`
`—`

Default:`False`

`include_getattr`
`—`

Default:`False`

`include_buf_append`
`—`

Default:`False`

`include_acc`
`—`

Default:`False`

`acc_none`
`—`

Default:`False`

`include_cap`
`—`

Default:`False`

`include_lookup_scope`
`—`

Default:`False`

`include_render_ctx`
`—`

Default:`False`

##### Returns

`list[ast.stmt]`

`_make_block_preamble`

1

`list[ast.stmt]`

▼

Common setup stmts for block functions.

Non-streaming adds buf and _append for…

`def _make_block_preamble(self, streaming: bool) -> list[ast.stmt]`

Common setup stmts for block functions.

Non-streaming adds buf and _append for StringBuilder.

##### Parameters

Name
Type
Description

`streaming`
`—`

##### Returns

`list[ast.stmt]`

`_build_region_keywords`

1

`tuple[list[str], list[as…`

▼

Build param_names and keywords for region block delegation.

Returns (param_nam…

`def _build_region_keywords(self, region_node: Region) -> tuple[list[str], list[ast.keyword]]`

Build param_names and keywords for region block delegation.

Returns (param_names, keywords) where keywords includes both the
per-param entries and the trailing _outer_ctx / _blocks keywords.

##### Parameters

Name
Type
Description

`region_node`
`—`

##### Returns

`tuple[list[str], list[ast.keyword]]`

`_make_region_block_function`

2

`ast.FunctionDef`

▼

Generate block wrapper that delegates to region callable with ctx params.

`def _make_region_block_function(self, name: str, region_node: Region) -> ast.FunctionDef`

##### Parameters

Name
Type
Description

`name`
`—`

`region_node`
`—`

##### Returns

`ast.FunctionDef`

`_has_unconditional_exprs`

1

`bool`

▼

Fast check: does the body have any nodes that produce unconditional Name refs?
…

staticmethod

`def _has_unconditional_exprs(body: Sequence) -> bool`

Fast check: does the body have any nodes that produce unconditional Name refs?

Returns False when every top-level node is a control-flow node or Data
(raw text), meaning CSE analysis would find no unconditional refs and
can be skipped entirely.

##### Parameters

Name
Type
Description

`body`
`—`

##### Returns

`bool`

`_analyze_for_cse`

1

`set[str]`

▼

Combined CSE analysis: returns cacheable variable names.

Performs the fast unc…

`def _analyze_for_cse(self, body_nodes: Sequence) -> set[str]`

Combined CSE analysis: returns cacheable variable names.

Performs the fast unconditional-expression guard check followed by
the full variable reference collection in a single call, deduplicating
the pattern used across _make_block_function and _make_render_direct_body.

##### Parameters

Name
Type
Description

`body_nodes`
`—`

##### Returns

`set[str]`

`_collect_var_refs`

1

`tuple[dict[str, int], se…`

▼

Collect variable reference counts and mutated names from Kida AST nodes.

Walks…

staticmethod

`def _collect_var_refs(nodes: Any) -> tuple[dict[str, int], set[str]]`

Collect variable reference counts and mutated names from Kida AST nodes.

Walks the AST recursively, collecting mutations from ALL code but only
counting Name references in unconditional (non-branching) code paths.
This ensures eager cache assignments at function entry won't raise
UndefinedError for variables only referenced inside conditional branches.

Does NOT recurse into Block or Def bodies (separate compilation scopes).

##### Parameters

Name
Type
Description

`nodes`
`—`

##### Returns

`tuple[dict[str, int], set[str]]`

(ref_counts, mutated) where ref_counts maps var name to usage count
in unconditional code, and mutated is the set of names assigned
anywhere in the body.

`_emit_cache_assignments`

1

`list[ast.stmt]`

▼

Emit _cv_name = _ls(ctx, _scope_stack, 'name') for each cached variable.

`def _emit_cache_assignments(self, names: set[str]) -> list[ast.stmt]`

##### Parameters

Name
Type
Description

`names`
`—`

##### Returns

`list[ast.stmt]`

`_is_append_constant`

1

`str | None`

▼

If *stmt* is ``_append()``, return the string; else None.

staticmethod

`def _is_append_constant(stmt: ast.stmt) -> str | None`

##### Parameters

Name
Type
Description

`stmt`
`—`

##### Returns

`str | None`

`_is_line_tracking`

1

`bool`

▼

Return True if *stmt* is ``_rc.line = N`` (render-context line tracking).

staticmethod

`def _is_line_tracking(stmt: ast.stmt) -> bool`

##### Parameters

Name
Type
Description

`stmt`
`—`

##### Returns

`bool`

`_is_single_append_expr`

1

`ast.expr | None`

▼

If *stmt* is ``_append(expr)``, return *expr*; else None.

staticmethod

`def _is_single_append_expr(stmt: ast.stmt) -> ast.expr | None`

##### Parameters

Name
Type
Description

`stmt`
`—`

##### Returns

`ast.expr | None`

`_make_block_function`

2

`ast.FunctionDef`

▼

Generate a block function: _block_name(ctx, _blocks) -> str.

Also stores the r…

`def _make_block_function(self, name: str, block_node: Block | Region) -> ast.FunctionDef`

Generate a block function: _block_name(ctx, _blocks) -> str.

Also stores the raw compiled stmts in`_last_block_compiled_stmts`
so the stream variant can be derived without recompiling the Kida AST.

##### Parameters

Name
Type
Description

`name`
`—`

`block_node`
`—`

##### Returns

`ast.FunctionDef`

`_make_render_preamble`

0

`list[ast.stmt]`

▼

Shared init block for render functions: if _blocks, _scope_stack, _acc, _cap.

`def _make_render_preamble(self) -> list[ast.stmt]`

##### Returns

`list[ast.stmt]`

`_make_render_extends_body`

5

`list[ast.stmt]`

▼

Top-level statements, block registration, and extends return/yield.

`def _make_render_extends_body(self, node: TemplateNode, extends_node: Extends, block_names: dict[str, Block | Region], block_suffix: str, extends_helper: str) -> list[ast.stmt]`

##### Parameters

Name
Type
Description

`node`
`—`

`extends_node`
`—`

`block_names`
`—`

`block_suffix`
`—`

`extends_helper`
`—`

##### Returns

`list[ast.stmt]`

`_make_render_direct_body`

2

`list[ast.stmt]`

▼

No-extends path: buf setup (if not streaming), body compile, return vs yield.

`def _make_render_direct_body(self, node: TemplateNode, streaming: bool) -> list[ast.stmt]`

##### Parameters

Name
Type
Description

`node`
`—`

`streaming`
`—`

##### Returns

`list[ast.stmt]`

`_make_render_function`

1

`ast.FunctionDef`

▼

Generate the render(ctx, _blocks=None) function.

Optimization: Cache global fu…

`def _make_render_function(self, node: TemplateNode) -> ast.FunctionDef`

Generate the render(ctx, _blocks=None) function.

Optimization: Cache global function references as locals for
O(1) LOAD_FAST instead of O(1) LOAD_GLOBAL + hash lookup.

For templates with extends:

```
def render(ctx, _blocks=None):
    if _blocks is None: _blocks = {}
    # Register child blocks
    _blocks.setdefault('name', _block_name)
    # Render parent with blocks
    return _extends('parent.html', ctx, _blocks)
```

##### Parameters

Name
Type
Description

`node`
`—`

##### Returns

`ast.FunctionDef`

`_make_block_function_stream`

2

`ast.FunctionDef`

▼

Generate a streaming block: _block_name_stream(ctx, _blocks) -> Generator[str].

`def _make_block_function_stream(self, name: str, block_node: Block | Region) -> ast.FunctionDef`

Generate a streaming block: _block_name_stream(ctx, _blocks) -> Generator[str].

##### Parameters

Name
Type
Description

`name`
`—`

`block_node`
`—`

##### Returns

`ast.FunctionDef`

`_make_render_function_stream`

2

`ast.FunctionDef`

▼

Generate render_stream(ctx, _blocks=None) generator function.

For templates wi…

`def _make_render_function_stream(self, node: TemplateNode, blocks: dict[str, Block | Region]) -> ast.FunctionDef`

Generate render_stream(ctx, _blocks=None) generator function.

For templates with extends:

```
yield from _extends_stream('parent.html', ctx, _blocks)
```

For templates without extends:

```
yield chunks directly
```

##### Parameters

Name
Type
Description

`node`
`—`

`blocks`
`—`

##### Returns

`ast.FunctionDef`

`_make_region_block_function_stream`

2

`ast.FunctionDef`

▼

Streaming block wrapper for region — yields callable result.

`def _make_region_block_function_stream(self, name: str, region_node: Region) -> ast.FunctionDef`

##### Parameters

Name
Type
Description

`name`
`—`

`region_node`
`—`

##### Returns

`ast.FunctionDef`

`_make_region_block_function_stream_async`

2

`ast.AsyncFunctionDef`

▼

Async streaming block wrapper for region.

`def _make_region_block_function_stream_async(self, name: str, region_node: Region) -> ast.AsyncFunctionDef`

##### Parameters

Name
Type
Description

`name`
`—`

`region_node`
`—`

##### Returns

`ast.AsyncFunctionDef`

`_make_block_function_stream_async`

2

`ast.AsyncFunctionDef`

▼

Generate async streaming block: _block_name_stream_async(ctx, _blocks).

Mirror…

`def _make_block_function_stream_async(self, name: str, block_node: Block | Region) -> ast.AsyncFunctionDef`

Generate async streaming block: _block_name_stream_async(ctx, _blocks).

Mirrors _make_block_function_stream() but produces an async generator
function (async def + yield). Used when the template contains async
constructs (AsyncFor, Await).

Part of RFC: rfc-async-rendering.

##### Parameters

Name
Type
Description

`name`
`—`

`block_node`
`—`

##### Returns

`ast.AsyncFunctionDef`

`_make_render_function_stream_async`

2

`ast.AsyncFunctionDef`

▼

Generate async render_stream_async(ctx, _blocks=None) function.

Mirrors _make_…

`def _make_render_function_stream_async(self, node: TemplateNode, blocks: dict[str, Block | Region]) -> ast.AsyncFunctionDef`

Generate async render_stream_async(ctx, _blocks=None) function.

Mirrors _make_render_function_stream() but produces an async generator
for native async iteration over AsyncFor loops and Await expressions.

For templates with extends:

```
async for chunk in _extends_stream_async(parent, ctx, _blocks):
    yield chunk
```

For templates without extends:

```
yield chunks directly via async generator
```

Part of RFC: rfc-async-rendering.

##### Parameters

Name
Type
Description

`node`
`—`

`blocks`
`—`

##### Returns

`ast.AsyncFunctionDef`

`_make_line_marker`

1

`ast.stmt`

▼

Generate RenderContext line update for error tracking.

Generates: _rc.line = l…

`def _make_line_marker(self, lineno: int) -> ast.stmt`

Generate RenderContext line update for error tracking.

Generates: _rc.line = lineno

Uses the cached _rc local (set in preamble) instead of calling
_get_render_ctx() per node, avoiding repeated ContextVar.get() calls.

RFC: kida-contextvar-patterns

##### Parameters

Name
Type
Description

`lineno`
`—`

##### Returns

`ast.stmt`

`_compile_template_context`

1

`list[ast.stmt]`

▼

No-op: TemplateContext is a declaration, not code.

staticmethod

`def _compile_template_context(_node: Node) -> list[ast.stmt]`

##### Parameters

Name
Type
Description

`_node`
`—`

##### Returns

`list[ast.stmt]`

`_compile_node`

1

`list[ast.stmt]`

▼

Compile a single AST node to Python statements.

Complexity: O(1) type dispatch…

`def _compile_node(self, node: Node) -> list[ast.stmt]`

Compile a single AST node to Python statements.

Complexity: O(1) type dispatch using class name lookup.

For nodes that can cause runtime errors, injects a line marker
statement (ctx['_line'] = N) before the node's code. This enables
rich error messages with source line numbers.

##### Parameters

Name
Type
Description

`node`
`—`

##### Returns

`list[ast.stmt]`

`_get_node_dispatch`

0

`dict[str, Callable]`

▼

Get node type dispatch table.

`def _get_node_dispatch(self) -> dict[str, Callable]`

##### Returns

`dict[str, Callable]`

## Functions

`_fix_missing_locations_fast`

1

`ast.AST`

▼

Fill missing Python AST locations without recursive iter_fields overhead.

Mirr…

`def _fix_missing_locations_fast(node: ast.AST) -> ast.AST`

Fill missing Python AST locations without recursive iter_fields overhead.

Mirrors`fix_missing_locations`(): each child inherits the current
parent location unless it already carries its own value. Kida emits large
generated ASTs during compile, so avoiding`ast.iter_child_nodes()`saves
measurable compile time while preserving traceback line semantics.

##### Parameters

Name
Type
Description

`node`
`ast.AST`

##### Returns

`ast.AST`
