A macro is code that runs while the program compiles. Macros are how Refingere embeds other languages, builds strings with values in them, and writes the repetitive code that would otherwise be written by hand. Every macro starts with #, or is an attribute starting with @. This page covers using macros and writing your own. The Macros reference has every builtin one and the full rules.
A macro is used in one of three shapes:
#name, optionally with arguments. The builtin namespaces work this way: #builtin reaches anything built in by its full name, #comptime runs code while compiling, and #runtime keeps it for when the program runs. #name"...", with no space in between. The string is the macro's input. See String Macro. #name {{{ ... }}}, with exactly one space. The body can hold any text - the Refingere parser skips it, and the macro reads it. See Macro With Body. #format is the string macro you'll use most. Each #{ expression } in the string is replaced by that value's toString(), and any expression works inside. A string ends at its first unescaped closing quote, so when a placeholder needs quotes of its own, write the string in the other quote style. \#{ writes a literal #{. A placeholder whose value can't become text is a compile error.
SQL, HTML, CSS, shaders (WGSL, GLSL, HLSL, Metal and SPIR-V assembly), SVG, JSON, YAML, TOML, CSV, regular expressions, Markdown, shell commands, HTTP requests and Vue templates are written as body macros: #SQL, #HTML, #CSS, #WGSL, #GLSL, #HLSL, #MSL, #SPIRV, #SVG, #JSON, #YAML, #TOML, #CSV, #REGEX, #MARKDOWN, #SHELL, #HTTP and #VUE. Each body is checked by that language's own parser while compiling, so a syntax error in the SQL is a compile error, and each gives back a typed value, not a string. A short regular expression can be a string macro instead: #regex"[0-9]+".
Values go in through #{ }, and each language puts them in the one way that's safe for it: a bound parameter in SQL, escaped text in HTML, a single quoted argument in a shell command. A value can't change the structure of the code around it, so injection attacks aren't possible. See Embedded languages.
A macro of your own is an implement macro block. It looks like a function, and its body is ordinary Refingere - it just runs while compiling. Its first parameter is its input, and what it returns goes where it was used. An attribute says which of five kinds it is, and so how it's used:
| Kind | Used as | Gets | Returns |
|---|---|---|---|
@bodyMacro | #name {{{ ... }}} | the body | what goes in its place |
@stringMacro | #name"..." | the string | what goes in its place |
@deriveMacro | @derive(Name) | a struct or enum | implement blocks to add |
@attributeMacro | @name(...) | the definition it's on | the definition to use instead |
@programMacro | never by name | the whole program | code to add |
A macro is visible in the whole project, like an implement block, and its name can't be one a builtin macro already uses. See Implement a Macro.
The input parameter's type picks the form the input arrives in: a String of the text as written, an Array<Token> of it split into tokens, or a syntax tree - an Ast.Expression, an Ast.Struct, and so on. The return type works the same way:
String is Refingere source, read as if it had been written where the macro was used.#lines returns. Since a String result is source code, a macro that means to produce a string value returns Ast.literal(text), as #shout does. Every Ast node is on Syntax Tree.
A derive macro writes an interface implementation from a type's definition. It's used with @derive, alongside the builtin ones like Equatable, and returns the implement block as source. Building source with #format, as here, is usually the simplest way to write one.
An attribute macro is used like an attribute, and replaces the definition it's on with the one it returns - here, the same function with timing added around its body. The body is parsed back from source with Ast.parse. Parameters named arguments, attributes and site give it the attribute's own arguments, the other attributes beside it, and where it was used.
A macro that returns its input unchanged is a check: here, a compile error for any floating point in the function, pointing at the exact expression through with node.span(). @phase picks when a macro runs - Lex, Parse, Analyze or Typecheck. A later phase knows more, like the type of every expression this macro needs, and an earlier one can change more. Without @phase, a macro runs as early as its input exists. See @phase.
A @programMacro isn't used anywhere by name. It runs once with the whole program, selects what it's interested in - every struct with an attribute, every function in a folder - and returns new definitions to add. It's how code gets generated for every entity or every event type without registering each one. See Generating code for the whole program.
What a macro stands for can be written into the source instead: rfn --expand-in-place replaces every macro use in the project by what it gives, --expand-in-place=main.rfn those in one file, and --expand-in-place=main.rfn:12 those on one line - or, when none starts there, the one the line is inside. In the editor it's Expand Macro, on the line the cursor is on (also a quick action, Ctrl+.), and Expand All Macros in File - see Editor Support.
It goes one level deep: a use in what a macro gave stays a use, to expand next. A string or body macro, and #comptime(...), is replaced by the source it gave - a value by its constant. An attribute macro's definition is replaced by the one it gave, without the attribute. A derive macro's implement blocks go after its definition, and its name comes out of @derive. A program macro has no use to replace: what it gives is written to .refingere/expanded/<folder>/<macro>.rfn, to read, and the macro stays.
The builtin macros expand too - #format into the ++ and toString() it stands for, @serve and @parallel into the functions they generate, and the embedded languages into the #builtin calls the compiler makes them into. The project has to compile first: the macros run to give what they stand for.
Reach for a function first, and a macro only when a function can't do it: when the input is another language, when code has to be generated from a type's definition, or when something has to be checked while compiling. To compute a value early rather than generate code, use #comptime(...) - see Compile-time Code.