# defmacro Define a compile-time macro that transforms code before evaluation. Macros operate on AST forms (unevaluated SExprs), not values. They expand at compile time — zero runtime cost. ## Syntax
defmacro name { |params| body }
## Parameters Macros support the same parameter syntax as functions: | Syntax | Description | |--------|-------------| | `a` | Simple positional parameter | | `a^string` | Typed parameter | | `a="default"` | Parameter with default value | | `@rest` | Collect remaining args as a list (must be last) | ### Variadic `@rest` The `@` prefix mirrors the `@{...}` vector literal — it signals "collect remaining args into a list". The name after `@` is bound verbatim; pick whatever reads best at the call site (`@rest`, `@items`, `@args`, …):
defmacro greet {|greeting @names|
    [each $names {[echo [str $greeting " " $it "!"]]}]
}

[greet "Hello" "Alice" "Bob" "Charlie"]
The rest-collector works in both `defmacro` and `def` (functions), and combines with defaults:
def f {|a b=10 @rest| ($a + $b + [len $rest])}
## Code Generation Macros substitute `$param` references with the matching argument's AST. Most macros don't need anything beyond this direct substitution — reach for `macro/quote` only when generating brand-new code from scratch. ### `macro/quote` / `macro/unquote` `[macro/quote ...]` builds code while preserving `$param` references as literal symbols (they are not substituted). `[macro/unquote $expr]` is the escape: inside a quote, it substitutes the parameter's value. Both forms must use `[...]` brackets — deft's `( ... )` parens are infix-only and are not function calls.
defmacro unless {|cond body|
    [macro/quote [if [macro/unquote $cond] nil [macro/unquote $body]]]
}

[unless false { echo "ran" }]
# expands to: [if false nil { echo "ran" }]
The quoted body becomes the macro expansion and is compiled in place. ### `macro/splice` / `macro/unquote-splice` `[macro/splice $list]` (also written `[macro/unquote-splice $list]`) inlines the elements of a list into the parent form — useful for building calls with a dynamic number of args. Again, use `[...]` brackets:
defmacro call-with {|func @args|
    [macro/quote [macro/unquote $func [macro/splice $args]]]
}

[call-with str "a" "b" "c"]   # expands to [str "a" "b" "c"]
Without `macro/splice` the whole list would land as a single argument:
defmacro call-with-broken {|func @args|
    [macro/quote [macro/unquote $func $args]]
}

[call-with-broken str "a" "b" "c"]   # expands to [str @{"a" "b" "c"}] — wrong!
## Hygiene Deft macros are not hygienic by default — a symbol introduced by a macro will collide with a same-named symbol at the call site. Two tools fix this: ### `[macro/gensym "prefix"]` Generate a unique keyword value of the form `:prefix#N`. Each call returns a distinct value; useful as a unique map key or opaque token.
defmacro with-token {|body|
    set tok [macro/gensym "tok"]
    [echo $tok]
    [macro/unquote $body]
}
Note: `macro/gensym` runs at runtime and returns a value, so it can be used as a key/token but cannot become the literal name of a new variable (deft `set` takes a literal name, not a value). For compile-time symbol hygiene inside generated code, use `macro/syntax-quote`. ### `[macro/syntax-quote ...]` Like `macro/quote`, but with two differences aimed at hygienic code generation: 1. `$param` references are substituted (acts like the normal macro body) — you don't need `[macro/unquote ...]` for parameters. 2. Bare symbols (e.g. `tmp`) are automatically renamed to unique `tmp#N` names, so they cannot collide with caller variables.
defmacro swap {|a b|
    [macro/syntax-quote {
        set tmp $a
        set $a $b
        set $b $tmp
    }]
}
Here `tmp` is auto-gensym'd to `tmp#42` (or similar), so it won't capture a caller variable named `tmp`. `$a` and `$b` are substituted normally. Use `[macro/unquote $expr]` inside `macro/syntax-quote` for the rare case where you need to force substitution of something that isn't a `$param` reference. ### `begin` `[begin e1 e2 ... en]` evaluates each expression in order and returns the value of the last. Multi-statement macro bodies (and `{...}` blocks with more than one statement) are wrapped in `begin` by the parser, so explicit use is rare — but it's useful inside `macro/quote`/`macro/syntax-quote` when you want to emit a multi-step expansion as a single expression.
defmacro inc-and-return {|x|
    [macro/quote [begin [set $x ($x + 1)] $x]]
}
## String Types in Macros Backtick strings (`` `...` ``) are ideal for macro template strings: they support `$var` substitution but treat `[...]` as literal characters (no embedded expression evaluation).
defmacro debug {|expr|
    [macro/quote {
        set result [macro/unquote $expr]
        echo `DEBUG: $result`
    }]
}
## Compile-time conditionals — `macro/compile-when` `[macro/compile-when cond body]` evaluates `cond` during compilation (through the VM). If truthy, `body` is compiled in place; if falsy, `body` is discarded entirely — the call site emits `push_null`, producing zero runtime code. This is how level-filtered logging achieves zero cost: filtered-out calls never enter the bytecode.
def log-level ([os/env "DEFT_LOG_LEVEL"] or "debug")

defmacro debug {|@args|
    [macro/compile-when [in? @{"debug"} $log-level]
        [log/debug [macro/splice $args]]]
}
The condition must be evaluable at compile time — literals, globals from `require`d modules, and pure functions of those. It cannot reference runtime locals or closure captures. See macro/compile-when for full details. ## Notes - Macros must be defined before use (evaluated top-to-bottom) - Macros can shadow builtins (checked before builtins in `compileCall`) - Self-referential macros (calling themselves) are detected and fall through to the builtin - Maximum 32 fixed parameters (plus one `@rest`) - Maximum expansion depth: 64 levels ## See Also `def`, `macro/quote`, `macro/unquote`, `macro/syntax-quote`, `macro/gensym`, `macro/splice`, `begin`, `macro/compile-when`