(macro <name> [<params>] <body>...)
(macro <name> [<params> & <rest-param>] <body>...)
(macro <name> [&form &env <params>] <body>...)
<name> must be a symbol<params> is a vector of symbols or destructuring patterns& <rest-param> captures remaining arguments as a list&form binds the original invocation form&env binds a read-only macro environment snapshot<body> is one or more expressions evaluated at macro expansion time(quote <expr>)
null => null(syntax-quote <template>)
`<template>
(quasiquote <template>)
Reader transforms: ` => syntax-quote, ~ => unquote, ~@ => unquote-splicing.
Within a template quote:
(unquote <expr>) or ~<expr> evaluates the expression(unquote-splicing <expr>) or ~@<expr> evaluates and splices elements into the enclosing listsyntax-quote is hygienic and attaches resolved binding metadata. quasiquote is the raw non-resolving template form.
Nesting: each nested syntax-quote or quasiquote increments depth. Unquote decrements depth. Evaluation only occurs at depth 0.
Outside quasiquote: ~ is the bitwise NOT operator. ~@ is a parse error.
Within a template quote at depth 0, symbols ending with # are replaced with unique generated symbols. All occurrences of the same foo# within the same template share the same generated symbol.
`(let (tmp# ~value) (use tmp#))
;; Both tmp# map to the same gensym, e.g. tmp_42
Nested quasiquotes get a fresh auto-gensym scope.
(gensym) ;; => GensymSymbol with name like "g_0"
(gensym "prefix") ;; => GensymSymbol with name like "prefix_1"
Returns a GensymSymbol object. When used in (unquote ...) within a quasiquote, becomes an S-expression symbol (not a string literal).
(macro ...) forms in the expression list are registered in the environmentMAX_EXPANSION_ITERATIONS)Within a single expansion:
Maximum recursive expansion depth: 100 (configurable via maxExpandDepth).
Macro arguments are raw forms by default. Nested macro calls remain raw until the macro explicitly expands or evaluates them.
Explicit macro-time evaluation is provided by:
%eval%macroexpand-1%macroexpand-allif, cond, let, var, quote, syntax-quote, quasiquote are handled as special forms during macro-time evaluation.
Defined in environment.ts with % prefix:
| Name | Signature | Behavior |
|---|---|---|
%first | (coll) | First element; handles vector prefix |
%rest | (coll) | Elements after first; handles vector prefix |
%nth | (coll, index) | Element at index |
%length | (coll) | Element count |
%empty? | (coll) | True if empty or null |
All handle null/undefined gracefully (return nil/0/true as appropriate).
| Name | Signature | Behavior |
|---|---|---|
list? | (value) | True if S-expression list |
symbol? | (value) | True if S-expression symbol |
name | (value) | String name of a symbol |
Non-primitive function calls are evaluated via a lazy singleton Interpreter:
%-prefixed primitives go directly to compiler environmentNamed function definitions ((fn name [...] ...)) encountered during expansion are registered in a persistent interpreter environment, making them available to subsequent macros.
After expansion, updateMetaRecursively traverses the expanded AST and updates _meta on each node to point to the macro call site. This ensures error messages reference the user's source location, not the macro definition.
Update criteria: no existing metadata, different source file, or same file but earlier line (macro definition before call site).
Manages system-level macros (from embedded .hql files):
defineSystemMacro(name, fn) -- register a system macroisSystemMacro(name) -- check if system macrohasMacro(name) -- check if any macro definedgetMacro(name) -- retrieve macro functionimportMacro(from, name, to, alias?) -- import system macro between filesmarkFileProcessed(path) / hasProcessedFile(path) -- track processed filesThree .hql source files are embedded at build time via embedded-macros.ts:
core.hql -- logic, conditionals, type predicates, utility, collections, threading, pattern matchingutils.hql -- doto, if-not, when-not, xor, min, max, with-gensymsloop.hql -- while, repeat, forThese are compiled into EMBEDDED_MACROS object. To modify, edit the .hql files and run deno run -A scripts/embed-packages.ts.
The utility macro library provides common convenience forms:
(doto x & forms) -- Execute forms with x as the receiver, return x. Method calls (.method) are transformed to (js-call x method args...).
(doto (new HashMap)
(.set "a" 1)
(.set "b" 2))
;; Equivalent to:
;; (let tmp (new HashMap))
;; (js-call tmp "set" "a" 1)
;; (js-call tmp "set" "b" 2)
;; tmp
(if-not test then else) -- Inverse of if. Swaps the then/else branches.
(if-not (isEmpty coll)
(first coll)
"empty")
;; Expands to: (if (isEmpty coll) "empty" (first coll))
(when-not test & body) -- Inverse of when. Executes body when test is falsy.
(when-not (isEmpty items)
(process items))
;; Expands to: (when (not (isEmpty items)) (process items))
(xor a b) -- Logical XOR with short-circuit evaluation.
(xor true false) ;; => true
(xor true true) ;; => false
Maps directly to Math.min / Math.max:
(min 1 2 3) ;; => Math.min(1, 2, 3)
(max 1 2 3) ;; => Math.max(1, 2, 3)
(with-gensyms [names...] body) -- Hygiene helper that binds each name to a fresh gensym for use in macro templates.
(macro swap [a b]
(with-gensyms [tmp]
`(let (~tmp ~a) (= ~a ~b) (= ~b ~tmp))))
;; tmp gets a unique name (e.g. tmp_42) to avoid variable capture
The transpiler handles quote and quasiquote after macro expansion:
Quote:
IRStringLiteralIRNumericLiteralIRBooleanLiteralIRNullLiteralIRStringLiteralIRArrayExpression with recursively quoted elementsQuasiquote:
.concat() call on arraysgensym or auto-gensym (foo#) to avoid variable capture.gensym counter never resets (monotonically increasing). No practical impact.map produces JS arrays, not S-expressions. Use recursive macros for list processing instead.super.method() calls not supported in HQL classes generated by macros (only (super args...) for constructors).quote produces JavaScript values at runtime ('foo → "foo", '(a b) → ["a","b"]), not Lisp symbols/lists. This is fine because macros operate on S-expressions at compile time, before quote transformation.