Code-Generating Macros
Macros can appear at statement position — i.e., as standalone statements rather than as sub-expressions. This lets macros generate entire blocks of code that are injected at the call site.
Statement-Position Invocation
macro:init_buffers = () {
int32:buf_a = 0i32;
int32:buf_b = 0i32;
println("buffers ready");
};
func:main = int32() {
init_buffers!(); // statement position — injects all three statements
exit(0);
};
func:failsafe = int32(tbb32:e) { exit(1); };
The macro body is inserted directly into the function body at the call site. All statements in the body execute in sequence.
Generating Control Flow
Macros can generate if, loop, and pick blocks:
macro:repeat3 = (body_expr) {
loop(0i32, 3i32, 1i32) {
body_expr;
}
};
func:main = int32() {
repeat3!(println("tick"));
exit(0);
};
func:failsafe = int32(tbb32:e) { exit(1); };
Expression vs Statement Position
The same macro can be used in either position, but the compiler determines context from the call site:
macro:compute = (x) {
x * 2i32
};
// expression position — result is assigned
int32:n = compute!(5i32);
// statement position — result discarded
compute!(5i32);
Code Generation Pattern
A common pattern is using macros to reduce boilerplate for repetitive initialization or teardown sequences:
macro:setup_logger = () {
string:log_prefix = "[APP]";
println(`&{log_prefix} starting`);
};
macro:teardown_logger = () {
println("[APP] done");
};
func:main = int32() {
setup_logger!();
// ... app logic ...
teardown_logger!();
exit(0);
};
func:failsafe = int32(tbb32:e) { exit(1); };
Limitations
- Macros cannot
passa value (they are not functions) - Macro bodies cannot use
failsafeorexitdirectly unless generating top-level code - Statement-position macros that produce expressions must ensure the expression is valid in that context