Metaprogramming
Procedural macros — comptime code returns code
macro name(string payload) comptime { … } runs at compile time, receives a string, and returns the code that replaces the call.
since 0.1.0-alpha.1linuxwindowswasm
Description
A fixed-template macro (see lang.quasiquote) substitutes its argument into a template. A procedural
macro is the programmable counterpart: its body is ordinary comptime code that can branch, loop and build
a result, and it returns the code to put in place of the call.
macro name(string payload) comptime <body>
The macro takes exactly one string. The body returns an Ast, which it normally obtains from
meta::parseExpr(text), a compile-time parser for a source expression. The call name!(arg) is replaced by
that expression. In this program the macro builds a sum of as many + 1 terms as the payload has characters:
A macro that builds code
macro countChars(string payload) comptime {
var parts = ["0"];
for (int i in 0..payload.length()) parts = parts.add("+ 1");
return meta::parseExpr(parts.joinToString(" "));
}
int n = countChars!(`four`);
int m = countChars!("a longer payload");
console.writeln(n);
console.writeln(m);
5
17
A call has the form name!(argument). The argument is evaluated at compile time and must be a string. A
backtick string is accepted as a raw string in this position only, which is convenient for a payload that has
quotes in it, such as markup:
A raw backtick payload
macro choose(string payload) comptime {
if (payload == "<Text title=\"demo\"/>") return meta::parseExpr("10 + 1");
return meta::parseExpr("20 + 2");
}
int raw = choose!(`<Text title="demo"/>`);
int other = choose!("anything else");
console.writeln(raw);
console.writeln(other);
11
22
Rules
macro name(string payload) comptime <body>takes exactly onecomptime-evaluablestringparameter and must return anAst; any other parameter list is a compile error.meta::parseExpr(string)parses an expression andmeta::parseStmts(string)parses statements. Both are compile-time only: at run time they throw. A parse failure reports the macro call, shows the generated text and puts a caret at the position in it.- A call used as an expression needs an expression
Ast; a macro that returns statements there is a kind error. - A backtick string is accepted as a raw string only in the argument position of a macro call. It does not become a general expression.
- The body runs under the ordinary
comptimerules: it is hermetic and bounded by the step budget (seelang.comptime), andimport()is the one file input it may use. A call to an ordinary function of the same name inside the body is plain recursion at compile time and shares the same budget. - Generated code is never scanned again for macro calls: a generated
name!(...)is a compile error, and a macro must not expand into a call of another macro. - Every splice clones the generated tree and gives its nodes the span of the call site, so diagnostics point at the call.
- Hygiene is the macro author's job. A macro that declares locals in generated code should name them with
a reserved prefix such as
__<package>_so that they cannot collide with names at the call site. --expandprints only the program after expansion. The procedural macro body does not survive into the printed program.- A macro can be declared inside a namespace and is then imported like an attribute or a rule, with
uses.
Examples
A macro call is an expression, so it also works under comptime. Here the generated expression is folded at
compile time:
A macro under comptime
macro eval(string payload) comptime { return meta::parseExpr(payload); }
comptime int answer = eval!("6 * 7");
console.writeln(answer);
42
A macro declared in a namespace and imported with uses:
A macro in a namespace
namespace Gen {
macro repeatPlus(string payload) comptime {
string text = "0";
for (int i in 0..payload.length()) text = text + " + 2";
return meta::parseExpr(text);
}
}
uses Gen;
int n = repeatPlus!("abc");
console.writeln(n);
8
Notes
- A procedural macro takes only one payload in this release. To pass several values, encode them in the one string and parse them in the macro.
See also
- comptime — run the language at compile time —
comptimevariables, expressions andifstatements evaluate ordinary code during compilation and fold the result into the program. - Quasiquote templates and holes — Backtick-delimited templates hold the code a rule injects, and
$holes fill them from the match. - import() — comptime file inclusion —
std::import(path)reads a project file at compile time and folds to its contents as acomptimestring. - Compile-time metaprogramming — A map of the four compile-time layers, procedural macros, and the
--expandflag that shows what a rule produced. - parseExpr — Parse a string of source text into a single expression fragment.
- parseStmts — Parse a string of source text into a list of statements.