7.5.19. Macro Tutorial 19: Custom module options
Most options are built into the compiler — gen2,
persistent_heap, rtti, and so on. A module can also define its
own option so that any file requiring it may set it, and a macro in the
module can read it to change behavior. This is the mechanism behind built-in
module options such as shader_like in daslib/validate_code.
The flow has two halves:
The module registers an option name and type with
add_module_option. After that, the parser acceptsoptions <name> = ...in any file that requires the module.A pass macro in the module reads the flag with
find_argand acts on it.
7.5.19.1. Registering the option
Full source: add_module_option_mod.das
add_module_option(module, name, type) records an option name and its
type on a module. It must run while the module’s macros are being compiled,
so it lives in a macro_function guarded by
is_compiling_macros_in_module:
[_macro, macro_function]
def register_options {
if (is_compiling_macros_in_module("add_module_option_mod")) {
this_module() |> add_module_option("trace_compile", Type.tBool)
}
}
Type.tBool declares the option as boolean; Type.tInt /
Type.tString register integer and string options. this_module()
returns the module being compiled — the one that owns the option.
7.5.19.2. Reading the option
A [lint_macro] runs once per module compiled after this one. It reads
the flag off the program options and, when set, prints a per-module note at
compile time:
[lint_macro]
class TraceCompileLint : AstPassMacro {
def override apply(prog : ProgramPtr; mod : Module?) : bool {
let on = prog._options |> find_arg("trace_compile") ?as tBool ?? false
if (!on) return false
let cm = compiling_module()
var nfun = 0
cm |> for_each_function("") $(var func : FunctionPtr) {
nfun++
}
let name = empty(cm.name) ? "<main>" : string(cm.name)
print("[trace_compile] module '{name}' — {nfun} function(s)\n")
return false // lint passes never modify the AST
}
}
Key points:
prog._options |> find_arg("trace_compile")looks the option up in the options of the module being linted.?as tBoolextracts the boolean and?? falsesupplies the default when the option was never set.[lint_macro]options are per-module: the flag is onlytruefor the module that actually set it, so the note prints once for that module.print(...)from a macro outputs at compile time — the note appears before any runtime output.
7.5.19.3. The usage file
Full source: 19_add_module_option.das
require add_module_option_mod
options trace_compile = true
def greet(name : string) {
print("Hello, {name}!\n")
}
def add(a, b : int) : int => a + b
[export]
def main() {
greet("world")
print("2 + 3 = {add(2, 3)}\n")
}
The options trace_compile = true line is accepted only because
add_module_option_mod registered the name — without the require, the
parser would reject it as an unknown option. Flip the flag to false (or
delete the line) and the note vanishes; the program still compiles, because
the option name stays registered.
7.5.19.4. Output
[trace_compile] module '<main>' — 3 function(s)
Hello, world!
2 + 3 = 5
The first line is emitted at compile time by the lint pass; the root script
module is unnamed, so it reports as <main>, with its three functions
(greet, add, main).
See also
Full source:
19_add_module_option.das,
add_module_option_mod.das
Previous tutorial: Macro Tutorial 18: with_ — locked binding of container slots
Standard library: daslib/validate_code.das registers shader_like
the same way.
Language reference: Macros — full macro system documentation