Metaprogramming
Runtime Compilation with compiler.eval()
The std.compiler module provides eval() for compiling and executing Braid source code at runtime. This enables dynamic code generation, plugin systems, and just-in-time compilation of domain-specific expressions.
import std.compiler
fn dynamic_calc(expr: string) -> int {
let source = "fn dyn_fn() -> int { return " + expr + " }"
let result = compiler.eval(source)
return result
}
fn main() {
let r1 = dynamic_calc("2 + 2") // 4
let r2 = dynamic_calc("10 * 5") // 50
let r3 = dynamic_calc("42") // 42
print_int(r1)
print_int(r2)
print_int(r3)
}compiler.compile() for Bytecode Generation
The compiler.compile() function compiles Braid source to bytecode (.bx format) without executing. This is useful for precompiling hot paths or generating modules on the fly.
import std.compiler
fn generate_kernel(kernel_name: string, size: int) {
let src = "fn " + kernel_name + "(x: int) -> int { return x * " + size + " }"
let bc = compiler.compile(src)
// bc is a bytecode module ready for execution
return bc
}
fn main() {
let mul_by_5 = generate_kernel("mul5", 5)
let result = compiler.exec(mul_by_5, [10])
print_int(result) // 50
}Supercompilation
Braid's compiler includes a supercompilation pass (run_supercompiler) that performs compile-time evaluation and specialization. The supercompiler analyzes the AST, evaluates constant expressions, unfolds recursive calls when profitable, and specializes generic code for concrete types. It runs during the optimization phase (Phase 5) of the C pipeline.
// Supercompiler evaluates this at compile time:
fn factorial(n: int) -> int {
if n <= 1 {
return 1
}
return n * factorial(n - 1)
}
let result = factorial(5)
// Supercompiler unrolls: 5 * 4 * 3 * 2 * 1 = 120
// Compiled output: result = 120 (no runtime call)Autocatalytic Recursion
Braid supports autocatalytic recursion, where a function can generate and execute new code that recursively calls itself with transformed logic. This pattern enables self-optimizing programs that adapt their behavior based on runtime feedback.
import std.compiler
fn self_optimizing(n: int) -> int {
if n <= 1 {
return 1
}
// Generate specialized code for this n
let src = "fn fast_path(x: int) -> int { return x * " + n + " }"
let specialized = compiler.eval(src)
return specialized(n)
}Compile-Time Code Generation
The std.dts module can be combined with std.compiler to generate code from data structures. This is useful for serialization, protocol buffers, and type-safe database queries.
import std.compiler
import std.dts
fn make_getter(field: string) {
let src = "fn get_" + field + "(obj) { return obj." + field + " }"
return compiler.eval(src)
}
fn main() {
let get_name = make_getter("name")
let user = User { name: "Alice", age: 30 }
let name = get_name(user)
print_string(name) // "Alice"
}Macro-Style Code Templates
By combining string building with compiler.eval(), Braid supports macro-like code generation patterns, enabling metaprogramming without a separate macro expansion phase.
fn make_comparator(field_name: string) {
let src = "fn compare(a, b) -> int { if a." + field_name
+ " < b." + field_name + " { return -1 } "
+ " else if a." + field_name + " > b." + field_name + " { return 1 } "
+ " else { return 0 } }"
return compiler.eval(src)
}
fn main() {
let by_age = make_comparator("age")
let alice = Person { name: "Alice", age: 30 }
let bob = Person { name: "Bob", age: 25 }
print_int(by_age(alice, bob)) // 1 (Alice older)
}