Metaprogramming

This chapter defines the current staging model used by RukaLang.

Goals

  • Keep runtime and compile-time behavior clearly separated.
  • Keep local type inference and require explicit type annotations only at boundaries.
  • Support explicit dynamic behavior through tagged unions, not implicit dynamic typing.
  • Enable future self-hosting/compiler-in-language workflows with typed staged code.

Phase Model

  • Runtime code executes normally.
  • Compile-time code executes only in explicit staged contexts.
  • Types are compile-time values (type) and do not flow as runtime values.

Context-Sensitive Surface Forms

RukaLang intentionally reuses several syntactic forms across runtime and staged contexts.

Syntax notes:

  • Name[...] is used in type/meta contexts for type application and constructor forms.
  • name(...) is used in expression contexts for function/meta-function calls.
  • Resolution is context-driven and validated in semantic passes.

Ownership markers appear in both spaces:

  • type-level: T, &T, @T
  • expression-level: &x, <-x

Supported First-Pass Syntax

Current parser/evaluator support covers:

  • meta fn declarations for compile-time-only functions.
  • meta { ... } blocks as explicit compile-time statement forms.
  • expr { ... } typed runtime-expression builders that produce Expr[T].
  • %{ ... } quote expressions.
  • $expr splice expressions in staged contexts.
  • $(...) inline runtime splices that evaluate a meta expression and require Code[...].
  • pattern match over staged values:
    • direct type patterns for type values (for example struct { x: i64 })
    • quote patterns %{ ... } for code values
  • code type constructor usage in type position: Code[T].
  • typed expression constructor usage in type position: Expr[T].
  • quoted/runtime struct operations in expressions:
    • construction: Name { field: value, ... }
    • field read: value.field
    • field update statement: value.field = expr;

Common aliases by convention:

  • Expr[T] for typed runtime expression generation.
  • Unit aliases the empty tuple type Tuple[].

Example

meta fn choose(flag: Bool, yes: Expr[i64], no: Expr[i64]) -> Expr[i64] {
  match flag {
    true => yes,
    false => no,
  };
}

fn main() -> Unit {
  meta {
    choose(true, expr { 4 }, expr { 9 });
  };
  0;
}

Inline runtime splice example:

meta fn make_message() -> Expr[String] {
  expr { "hello" };
}

fn main() -> String {
  $(make_message())
}

Current Limitations

  • Parser support comes first; some elaboration/type rules remain partial.
  • $expr is parsed in expression positions and must be resolved before runtime lowering.
  • Runtime cannot consume type values directly.
  • Type-structure matching supports direct type patterns in match arms.

Validation Rules

  • Runtime values cannot be used in compile-time-only contexts.
  • Compile-time-only values cannot escape into runtime expressions.
  • Splice insertion must be type-correct at the quote site.

Phase Boundary Behavior

Meta evaluation is strict: it can only read values introduced in meta contexts (meta-function parameters, meta let bindings, and meta call results). Runtime bindings are unavailable in the meta phase.

Valid meta-phase access:

meta fn use_int(k: i64) -> Expr[i64] {
  expr { k };
}

fn main() -> Unit {
  meta {
    use_int(4);
  };
  0;
}

Invalid runtime-to-meta access:

meta fn use_int(k: i64) -> Expr[i64] {
  expr { k };
}

fn main() -> Unit {
  let runtime_k = 4;
  meta {
    use_int(runtime_k); // error: runtime binding unavailable in meta phase
  };
  0;
}