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:

  1. AST-to-AST: Generateast.Module, not source strings
  2. StringBuilder: Output viabuf.append(), join at end
  3. Local caching: Cache_escape, _str, buf.appendas 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

        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)
    

Classes

Compiler 47
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 anast.Module, then compiles it to a code object ready forexec(). The generated code defines arender(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 = Nbefore 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]
_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 35
_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
_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:

  1. Top-level FromImport and Import nodes (so render_block has macros in scope)
  2. 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 9 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_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_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.
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]