Macros
Macros are the key feature that separates a Lisp dialect from many conventional programming languages. A macro is a mechanism to dynamically generate code during compilation.
Macros are subdivided into two categories in GDLisp: functional macros and symbol macros. Functional macros are far more common, so this documentation will often refer to them simply as “macros” with the “functional” part understood.
A (functional) macro declaration looks similar to a function
declaration, except that the former uses defmacro rather than
defn.
(defmacro macro-name (args ...)
body ...)
(defmacro macro-name (args ...) public-or-private
body ...)
A macro’s argument list takes the form of a ordinary lambda list, which means that macros can take optional and variable arguments.
When a macro declaration is encountered, all names that it references, either directly or indirectly, must always be fully defined. That is, while two functions can mutually depend on each other, regardless of the order in which they’re defined in a file, a macro cannot depend on a function or constant defined later in the current file.
Macros are defined immediately, and are available to the compiler. The name of the macro is bound, in the function namespace, to the given macro object for the current module. A macro is a function-like object that is invoked during compilation.
Specifically, whenever the compiler is expecting a declaration or an
expression and encounters a proper, nonempty list (foo args ...),
it will first check whether it is aware of a macro defined in the
current scope with the name foo. This process is referred to as
macro expansion.
If there is a macro with that name, then rather than interpreting the
code as an expression or declaration, the macro will be invoked with
the given arguments. Note carefully that the macro’s arguments are
passed in unevaluated. That is, if there is a macro called
example and it is called as (example arg), then the literal
symbol arg will be passed in, not the value of a variable called
arg. Likewise, if it is called as (example (+ 1 1)), then the
literal three-element list (+ 1 1) will be passed in, not the
number 2.
A macro must return an S-expression, which will replace the macro call in the current position of the code. If a macro is called in declaration context, then the result of the macro call will be treated as a declaration. If a macro is called in expression context, then the result of the macro call will be treated as an expression.
The result of a macro call is itself subject to macro expansion. A macro call can return an S-expression which itself calls another macro, or even the same macro recursively. The behavior is undefined if a macro exhibits infinitely recursive behavior. That is, the following macro will exhibit undefined behavior if it is ever invoked.
(defmacro recursive ()
'(recursive))
Symbol Macros
(define-symbol-macro macro-name value)
(define-symbol-macro macro-name value public-or-private)
The second kind of macro is a symbol macro. Symbol macros work like functional macros except that they occupy the value namespace, not the function namespace.
When a symbol macro is declared, it binds a name in the value namespace of the current module. Note that symbol macros cannot take arguments. The same caveats with regards to definedness apply for symbol macros: all names referenced (directly or indirectly) by a symbol macro must be fully available at compile-time, when the symbol macro is first defined.
Macro expansion for symbol macros occurs when a symbol literal foo
is evaluated in declaration or expression context. In this case,
before compiling the symbol literally, GDLisp checks whether a symbol
macro with the given name exists in the current scope. If it does,
then that symbol macro’s body is evaluated in a new lexical scope, and
the return value of the symbol macro is used in place of the symbol
literal. Like functional macros, symbol macros can expand recursively
into other macro calls.
Note that the “body” of a symbol macro is a single expression, not a
collection of zero or more expressions. Symbol macros are designed to
have the same syntax as defconst. To write a symbol macro whose
body consists of multiple statements, wrap the body in the progn
special form.
Technical Limitations
Macros can reference other GDLisp source files freely. However, due to technical limitations, macros cannot currently interface directly with GDScript source files or other resource types (such as packed scenes or textures). This limitation may be lifted in the future.
Additionally, care must be taken if files are dynamically loaded via
the load function. GDLisp performs name mangling during macro
expansion in order to consistently load macros into the runtime. The
GDLisp compiler understands use directives and preload calls
(both of which must refer to a statically-known filename) and will
translate these names accordingly, but GDLisp will not attempt to
translate the argument to a load function. The built-in macro
contextual-load can be helpful to perform such dynamic loading
inside of macros.