brick

Brick Macro System

Macros provide compile-time code generation. They are hygienic, typed in output only, and integrate with build {} and emit {} for powerful metaprogramming.

Basic Macro

macro swap(a, b) {
    __tmp = $a
    $a = $b
    $b = __tmp
}

fn main() {
    x = 10; y = 20
    swap(x, y)             // expands body with x→$a, y→$b
    print("{0} {1}", x, y) // "20 10"
}

The __tmp variable inside the macro gets a unique name (__tmp__1) to prevent collisions with user variables (hygiene).

Macro Definition

macro name(param1, param2, ...) {
    // body — any Brick code with $ interpolation
}

$ Interpolation

Use $param to insert a parameter into the generated code:

macro assert_eq(a, b) {
    if $a != $b {
        error("assertion failed")
    }
}

fn main() {
    assert_eq(3 + 4, 7)   // expands to: if 3 + 4 != 7 { error(...) }
}

$macro() Explicit Syntax

Macros can be called with explicit $ syntax:

$swap(x, y)          // same as swap(x, y)
$assert_eq(5, 5)     // same as assert_eq(5, 5)

Both syntaxes are equivalent. The $macro() form makes macro calls visually distinct from function calls.

Hygiene (Gensym)

Variables starting with __ inside macros get unique identifiers:

macro twice(x) {
    __result = $x * 2     // __result → __result__1
}

fn main() {
    __result = 0          // user's __result (NOT the macro's)
    twice(5)              // uses __result__1
    print("{0}", __result)// "0" — no collision!
}

Without hygiene, nested or repeated macro calls would shadow variables.

Varargs (args…)

macro print_all(values...) {
    $values[0]    // first argument
    $values[1]    // second argument
}

fn main() {
    print_all(10, 20, 30)  // expands to:
                           // 10
                           // 20
                           // (30 is unused)
}

Varargs capture zero or more arguments into a list-like parameter.

build {} — Compile-Time Computation

build {} blocks execute at compile time. Variables inside are temporary and don’t exist in the final binary.

build {
    x = 42                          // temporary variable
    emit { z = x }                  // generates: z = 42
}

Inside build:

emit {} — Code Generation

emit {} generates code into the surrounding scope:

macro vec2_op(name, op) {
    emit {
        fn $name(x1, y1, x2, y2, out_x, out_y) {
            $op(x1, x2, out_x)
            $op(y1, y2, out_y)
        }
    }
}

macro add_fields(a, b, out) {
    emit {
        $out = $a + $b
    }
}

vec2_op(add_vec2, add_fields)

fn main() {
    add_vec2(1.0, 2.0, 3.0, 4.0, &rx, &ry)
}

build + emit — Combined

macro create_struct(name, fields...) {
    build {
        __fields = $fields
        __body = "    i32 " ++ __fields[0]  // build-time string ops
        emit {
            struct $name {
                $fields[0]    // First field
                $fields[1]    // Second field
            }
        }
    }
}

create_struct(Point, x, y)
// Generates:
// struct Point {
//     i32 x
//     i32 y
// }

The build block processes $fields at compile time, then emit generates the struct declaration.

Type Reflection (inside build)

Expression Returns Example Output
T.name Type name as string "i32"
T.size Size in bytes 4
T.fields Field names as string array ["x", "y"]
macro inspect(T) {
    build {
        name = T.name
        size = T.size
        emit {
            print("type: {0}, size: {1}", name, size)
        }
    }
}

inspect(MyStruct)
// Generates: print("type: {0}, size: {1}", "MyStruct", 16)

Macro Rules

Situation Behavior
Wrong argument count Compile error
$ outside macro/build Compile error
Recursive macro > 64 levels Caught, compile error
I/O inside build Not allowed (compile error)
$ in non-macro code Compile error
$(expr) Compile-time eval of expr

Patterns

Generic Data Structure

macro create_list(T) {
    emit {
        struct List_$T {
            $T[] items
        }

        fn List_$T.push(self, $T value) {
            self.items.append(value)
        }

        fn List_$T.get(self, i32 index) -> $T {
            return self.items[index]
        }
    }
}

create_list(int)

fn main() {
    List_int my_list @global
    my_list.push(42)
    int val = my_list.get(0)
}

Performance Wrapper

macro measure(name, body) {
    build {
        __start = clock()   // build-time simulation
        $body
        __elapsed = clock() - __start
        emit {
            print("'{0}' took {1} cycles", name, __elapsed)
        }
    }
}

macro dbg(expr) {
    emit {
        print("debug: {0} = ", $expr)
        print($expr)
    }
}

fn main() {
    dbg(3 + 5)    // prints: debug: 3 + 5 = 8
}

See Also