Macros
A macro is a function that runs at compile time and returns code. It is called
as name!(...), and the code it returns replaces the call.
macro power(x: expr, n: i64) -> expr { let e = quote(1) let i = 0 while i < n { e = quote($e * $x) i = i + 1 } return e}
macro expect(cond: expr) -> expr { return quote(report($cond, $(stringify(cond))))}
fn main() { let y = 3 print(power!(y + 1, 2)) // expands to ((1) * (y + 1)) * (y + 1): 16}Declaring a macro
Section titled “Declaring a macro”macro name(params) -> kind { body } declares a macro. The body is ordinary
jihoo, run on the VM while compiling (crates/jihoo-sema/src/macros.rs), and
the result kind says what code it returns:
| result | built with | used |
|---|---|---|
expr |
quote(expression) |
as an expression |
stmts |
quote { statements } |
as a statement of its own |
items |
quote items { items } |
at the top level of a module |
Parameters of type expr receive the arguments as code, unevaluated. Integer,
bool and str parameters receive values computed at compile time. A last
parameter of type [expr] receives the remaining arguments, any number of
them, as a list of code:
macro sum(rest: [expr]) -> expr { let e = quote(0) for x in rest { e = quote($e + $x) } return e}
print(sum!(1, x, x * 10)) // ((0 + 1) + x) + x * 10print(sum!()) // 0Macros are module items like functions: pub macro exports one, and another
module calls it as alias.name!(...). They are type checked even when unused,
and are never part of the compiled program.
Quoting code
Section titled “Quoting code”quote(template) builds code. The template is checked when the macro is
parsed, so it must be valid code of its kind. $x and $(e) are holes,
filled in when the macro runs:
- An
expris inserted in parentheses, so precedence cannot change, unless it is one operand already (a name, a number, or code in parentheses). So a macro that passes its argument on, even to itself, does not nest it deeper at every step. - Integers, bools and strings are inserted as literals.
stringify(e)gives the source text of code, as astr.
Scope and hygiene
Section titled “Scope and hygiene”The code a macro returns is compiled where the macro was called, but macros are hygienic: the names a template writes and the names the caller wrote are kept apart.
- Code from holes (
$x,$(e)) is the caller’s, and means what it means where the macro was called. - Locals the template declares (
let, parameters of anonymous functions, pattern bindings) belong to that one expansion. They cannot capture the caller’s variables of the same name, and the caller cannot see them after the macro. A template cannot use the caller’s locals by name either: only through a hole. - Other names the template writes (functions, constants, types, macros,
module aliases) mean what they mean where the macro is defined. So a
pub macrocan use its own module’s private functions and types and the modules it imports, wherever it is called. A name the macro’s module does not have is looked up among the caller’s items (not its locals), which lets item macros refer to the items they produce. - Fields the template names (
p.x,P { x: 1 },P { x, .. }) are used as the macro’s module uses them: the template may use that module’s private fields (Modules), while the fields of code from holes are checked where the macro was called. - To declare a name the caller sees, put it in a name position through a
hole:
let $name,fn $name, with astror anexprthat is a name.
macro add_tmp(x: expr) -> stmts { return quote { let tmp = 100 // the macro's own `tmp` print($x + tmp) // `$x` is the caller's code }}
macro define(name: str, v: i64) -> stmts { return quote { let $name = $v } // a name for the caller}
fn main() { let tmp = 1 add_tmp!(tmp) // 101 print(tmp) // 1 define!("answer", 42) print(answer) // 42}fn helper(x: i64) -> i64 { return x * 10 } // privatepub macro scaled(x: expr) -> expr { return quote(helper($x)) // `util`'s helper, wherever it is used}Enum variant names, item names (fn f in an items template) and the
built-in types are not affected. How it works is in
Compiler.
Errors in produced code point at the call and say which macro produced it. Expansion stops 64 levels deep, which catches a macro that expands to itself.
Macro bodies may use str even in native and freestanding programs, since they
only run on the VM; a str inserted into code becomes a string literal.
Statement and item macros
Section titled “Statement and item macros”macro swap(a: expr, b: expr) -> stmts { return quote { let tmp = $a // the macro's own: `swap!(tmp, x)` works too $a = $b $b = tmp }}
macro adders(n: i64) -> items { let out = quote items {} let i = 1 while i <= n { out = quote items { $out fn $("add" + to_str(i))(x: i64) -> i64 { return x + $i } } i = i + 1 } return out}
adders!(3) // at the top level: defines add1, add2, add3
fn main() { let x = 1 let y = 2 swap!(x, y) // on a line of its own: three statements in this block print(add3(x)) // 5}- A
stmtsmacro is used as a statement of its own. The variables it declares through name holes (let $name) stay visible after it; the ones its template names itself do not. Anitemsmacro is used at the top level of a module. - A hole’s position decides what it takes: in an expression, code
(parenthesized) or a literal value; in a name position (
fn $name,let $v, a struct name), astrthat must be an identifier; as a statement of its own,stmtsorexpr; as an item of its own,items. - Item macros are expanded before the program is analyzed, in rounds: each round analyzes lazily what the macros need, runs them, and adds the produced items to the module that called the macro. Produced code may call item macros, so this repeats, up to 16 rounds.
Functions on code
Section titled “Functions on code”A function whose parameters or result are code (expr, stmts, items, or a
type holding them, such as [expr] or a struct or enum with a field of code,
like meta.Node) runs only while compiling, like a macro. Macros use such
functions to build their code in parts:
fn twice(e: expr) -> expr { return quote($e + $e)}
fn nest(n: i64, e: expr) -> expr { if n == 0 { return e } return nest(n - 1, twice(e))}
macro quad(x: expr) -> expr { return nest(2, x) // ((x + x) + (x + x))}- Only macros and other functions on code may call one or use it as a value;
the compiled program never contains it. Its body follows the rules of macro
bodies:
quoteworks, andstrdoes even in freestanding programs. - A struct or enum may have fields of code; it then only exists while compiling too, and code that runs in the program may not use it.
- Its templates are hygienic as part of the macro that called it: the locals
they declare belong to that expansion, so a
let vin one function and avin the macro’s own template are the same variable. Other names they write mean what they mean where the macro is defined, so a function on code that is meant for macros in other modules must not name its own module’s private items.
Types of code
Section titled “Types of code”type_of(e) gives the type that code e would have where the macro was
called, as text: "i64", "[str]", "fn(i64) -> bool". A hole in a type
position, let x: $t = ..., takes such text:
// The sum of `a` and `b`, with an accumulator of their type.macro add(a: expr, b: expr) -> expr { let t = type_of(a) return quote({ let s: $t = $a s += $b s })}- The macro may compare the text, as
t == "f64". A struct or enum comes back as#type:N, a name that means that type wherever the code goes. - The code is typed in the caller’s scope, before the macro’s own code runs:
type_ofsees the caller’s variables, not the ones the macro’s templates declare. - Typing the code does not run it, and leaves nothing in the program but an
unused copy of any anonymous function in it. The compiler works a type out
when a macro first asks for it and then runs the macro again from the
start, so a macro should not depend on how many times it runs;
uniquegives the same names each time.
Macro builtins
Section titled “Macro builtins”| builtin | gives |
|---|---|
quote(...), quote { ... }, quote items { ... } |
code from a template |
stringify(e) |
the source text of code e, a str |
unique(prefix) |
a name that is new in the whole compilation, a str, for items or variables a macro makes for the caller |
ident(name) |
a name as code, for expression positions |
to_str(x) |
a number or bool as a str, handy for building names |
code_text(e) |
the text of code e with the hygiene marks of its names, for code that reads code |
to_code(s) |
text, such as a part of what code_text gave, as code again |
type_of(e) |
the type code e has where the macro is called, as a str for a type hole (Types of code) |
type_shape(t) |
the parts of the struct or enum type t names where the macro is called, as text (struct a b, enum V/2 E/0); meta.shape reads it |
to_items(s) |
text as items, which an item macro may return |
To look inside code, use the meta
library rather than these two: meta.node(e) tells a call from a name, a
literal or an operator, and gives the parts as code.
See examples/macros.jh.