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 fndeclarations for compile-time-only functions.meta { ... }blocks as explicit compile-time statement forms.expr { ... }typed runtime-expression builders that produceExpr[T].%{ ... }quote expressions.$exprsplice expressions in staged contexts.$(...)inline runtime splices that evaluate a meta expression and requireCode[...].- pattern
matchover staged values:- direct type patterns for
typevalues (for examplestruct { x: i64 }) - quote patterns
%{ ... }for code values
- direct type patterns for
- 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;
- construction:
Common aliases by convention:
Expr[T]for typed runtime expression generation.Unitaliases the empty tuple typeTuple[].
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.
$expris parsed in expression positions and must be resolved before runtime lowering.- Runtime cannot consume
typevalues directly. - Type-structure matching supports direct type patterns in
matcharms.
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;
}