Macro Language - wgrzegorz/c5xtools GitHub Wiki
Macro Language
Normative source: SPRU018D, chapter 6 (Macro Language).
A macro lets you name a block of source statements once and emit it wherever you need it, instead of retyping the block. Using a macro is three steps: define it, call it by its name in the opcode field, and let the assembler expand the call back into the stored statements. When the assembler reads a macro definition it puts the macro name into its opcode table; that name then overrides any earlier macro, library entry, directive, or instruction mnemonic of the same name, so a macro can extend or replace a built-in instruction as well as add a new one.
| Step | What you do | What the assembler does |
|---|---|---|
| Define | Write the definition before the first use (in the file, in an .include/.copy file, or in a macro library) | Records the name in the opcode table |
| Call | Use the macro name as an opcode | Matches the name against the macro table |
| Expand | (nothing) | Binds arguments to parameters, substitutes the body, assembles it |
By default the expanded body is printed in the listing; .mnolist turns that
off (see "Formatting the listing" below).
1. Defining a macro
A definition runs from a .macro line to an .endm line:
macname .macro [parameter1] [,parameter2] ... [,parametern]
model statements or macro directives
[.mexit]
.endm
| Element | Meaning |
|---|---|
macname |
The macro name, placed in the label field. The first 32 characters are significant. It replaces any instruction or earlier macro of the same name. |
.macro |
Marks the first line of the definition; goes in the opcode field. |
| parameters | Optional substitution symbols, listed as operands of .macro. |
| model statements | Instructions or directives assembled on each call. |
| macro directives | Directives that control the expansion. |
.mexit |
Acts like a jump to .endm; handy for bailing out when a test shows the expansion cannot succeed. |
.endm |
Ends the definition. |
All parts of a macro must live in the same file, though definitions may be nested and may call other macros.
Example - define, call, and expand a three-parameter macro that sums p1, p2 and p3 into p3:
add3 .macro p1, p2, p3
lac p1
add p2
add p3
sacl p3
.endm
add3 a, b, c ; call
; expands to:
lac a
add b
add c
sacl c
To attach a comment that should NOT appear in the expansion, start it with an exclamation point. A comment that should appear in the expansion starts with an asterisk or a semicolon.
2. Macro parameters and substitution symbols
Macro parameters are substitution symbols: named stand-ins for a character
string. A valid substitution symbol is up to 32 characters, begins with a
letter, and otherwise uses letters, digits, underscores, and dollar signs.
Symbols used as parameters are local to their macro; you may define up to 32
local symbols per macro (parameters plus any .var symbols together).
On expansion, each argument is passed by variable: its character-string value is bound to the matching parameter. Rules for the binding:
- A parameter with no matching argument is set to the null string.
- If there are more arguments than parameters, the final parameter receives all the leftover arguments as one string.
- To pass an argument list, a comma, or a semicolon to a single parameter, wrap the argument in quotation marks.
The assembler replaces the symbol with its string first, then assembles the resulting line.
Directives that set substitution symbols
.asg assigns a string; .eval assigns the string form of an evaluated
expression:
.asg ["]character string["], substitution symbol
.eval well-defined expression, substitution symbol
With .asg the quotation marks are optional; without them the assembler reads
up to the first comma and trims leading and trailing blanks. .eval evaluates
its expression and stores the result as a string; if the expression is not well
defined, it errors and assigns the null string. Use .eval whenever a value
must be computed from an expression (for example a running counter); .asg
only copies a string.
.asg 1, counter
.loop 100
.word counter
.eval counter + 1, counter
.endloop
Built-in substitution symbol functions
These functions return a value usable in expressions, and are most useful in conditional-assembly tests. In the table, a and b are substitution symbols or character-string constants, and "string" means the string value of the parameter.
| Function | Return value |
|---|---|
$symlen(a) |
Length of string a |
$symcmp(a,b) |
< 0 if a < b, 0 if a = b, > 0 if a > b |
$firstch(a,ch) |
Index of the first occurrence of character constant ch in a |
$lastch(a,ch) |
Index of the last occurrence of character constant ch in a |
$isdefed(a) |
1 if a is defined in the symbol table, else 0 |
$ismember(a,b) |
Assigns the top member of list b to a; 0 if b is a null string |
$iscons(a) |
1 binary, 2 octal, 3 hexadecimal, 4 character, 5 decimal constant |
$isname(a) |
1 if a is a valid symbol name, else 0 |
$isreg(a) |
1 if a is a valid predefined register name, else 0 |
Recursive, forced, and subscripted substitution
When the string bound to a symbol is itself a symbol, the assembler substitutes again, and keeps going until it reaches a non-symbol token or a symbol it has already seen in this evaluation (which it treats as infinite recursion and stops).
The forced substitution operator - a pair of colons around a symbol, with no spaces - makes the assembler substitute a symbol's string even where it would not normally be recognized. Forced substitutions are expanded before other substitutions, may be used only inside macros, and may not be nested:
:symbol:
Inside a macro you can also reach individual characters of a symbol with a
subscript. :symbol(expr): yields the one character at that position;
:symbol(expr1,expr2): yields the substring starting at position expr1 with
length expr2. Substring indexing starts at 1.
Local variables: .var
.var creates up to 32 local substitution symbols per macro (counted together
with the parameters). Each starts as the null string, is not passed in as a
parameter, and is discarded when the expansion finishes:
.var sym1 [,sym2] ... [,symn]
Example - redefine add with .var, a subscript, and a conditional so it picks
the right opcode for a short immediate or an indirect operand:
addx .macro a
.var tmp
.asg :a(1):, tmp
.if $symcmp(tmp,"#") = 0
addk a
.elseif $symcmp(tmp,"*") = 0
add a
.else
.emsg "Bad Macro Parameter"
.endif
.endm
addx #100
addx *+
3. Macro libraries
A macro library is an archive (built with the archiver) whose members are
unassembled source files, one macro definition per member. The member name and
the macro name must match, and each source file must have the .asm extension
(for example macro add3 in file add3.asm). Point the assembler at a library
with:
.mlib macro library filename
On .mlib the assembler opens the archive, builds a table of its members, and
enters each member name into the opcode table as a library entry, overriding any
existing opcode or macro of the same name. A library macro is pulled in and
loaded into the macro table only when it is actually called, and only once. Put
only macro definitions in a macro library; object code or stray source files may
cause undesirable results. Listing of library-entry expansions is controlled by
.mlist.
4. Conditional assembly in macros
Two directive groups do conditional assembly; they nest up to 32 levels.
The .if group assembles exactly one block:
.if well-defined expression
[.elseif well-defined expression]
[.else]
.endif
.elseif and .else are optional, and .elseif may appear more than once.
When both are absent and the .if expression is false (zero), the assembler
skips to after .endif.
The .loop group assembles a block repeatedly:
.loop [well-defined expression]
[.break [well-defined expression]]
.endloop
The .loop expression is the loop count; if it is omitted, the count defaults
to 1024 unless a .break with a true (nonzero) expression stops it sooner. The
.break directive is optional: while its expression is false the loop
continues; the loop ends when the .break expression is true or when .break
has no expression, after which assembly resumes past .endloop.
.asg 1, x
.loop ; "infinite" loop
.break (x == 10) ; stop when x reaches 10
.eval x + 1, x
.endloop
5. Unique labels in macros
Every label must be unique, so a label written plainly inside a macro would
collide when the macro expands more than once. Append a question mark to the
label and the assembler substitutes a unique number for the question mark at
each expansion. The listing shows the label with its question mark, not the
number; such a label cannot be declared global. Syntax: label?.
MIN .macro a, b
lac a
sub b
blz m1?
lac b
b m2?
m1? lac a
m2?
.endm
The maximum label length shrinks to leave room for the suffix: 126 characters when the macro expands fewer than 10 times, 125 characters for 10 to 99 expansions. The suffixed label appears in the cross-listing file.
6. Producing messages
Three directives emit your own assembly-time messages; the error and warning counts appear on the last line of the listing.
| Directive | Effect |
|---|---|
.emsg |
Sends an error message; increments the error count and stops object-file generation. |
.wmsg |
Sends a warning message; increments the warning count but still generates the object file. |
.mmsg |
Sends an assembly-time message; does not touch the error count and does not stop object-file generation. |
TEST .macro x, y
!
! This macro checks that both parameters are present.
!
.if ($symlen(x) == 0 | $symlen(y) == 0)
.emsg "ERROR - missing parameter in call to TEST"
.mexit
.else
.endif
.endm
7. Formatting the listing
Macros, substitution symbols, and conditional directives can hide code from the listing. Four directive pairs control what the expanded listing shows; the default member of each pair is noted.
| Directive | Effect |
|---|---|
.mlist |
List macro and .loop/.endloop expansions (default). |
.mnolist |
Suppress macro and loop expansions. |
.fclist |
List false conditional blocks (default). |
.fcnolist |
Suppress false conditional blocks; drop the .if/.elseif/.else/.endif lines. |
.sslist |
Expand substitution symbols in the listing, printed below the source line. |
.ssnolist |
Turn off substitution-symbol expansion (default). |
.drlist |
List all directive lines (default). |
.drnolist |
Suppress listing of .asg, .eval, .var, .sslist, .mlist, .fclist, .ssnolist, .mnolist, .fcnolist, .emsg, .wmsg, .mmsg, .length, .width, and .break. |
8. Recursive and nested macros
A macro definition may call other macros (nested, up to 32 levels) or call itself (recursive). Parameters use dynamic scoping: a called macro sees the environment of the macro that called it. In a nested call, a parameter in the inner macro with the same name as one in the outer macro hides the outer one, while the outer parameters it does not shadow remain visible.
Example - a recursive macro that emits code to compute a factorial. fact sets
up the first step and hands off to fact1, which calls itself until the counter
reaches 1:
fact .macro n, loc ; loc memory address = n!
.if n < 2 ; 0! = 1! = 1
lack 1
sacl loc
.else
lack n
sacl loc
.eval n - 1, n
fact1 ; continue with current environment
.endif
.endm
fact1 .macro
.if n > 1
lt loc
mpyk n
pac
sacl loc
.eval n - 1, n
fact1 ; recursive call
.endif
.endm
9. Macro directive summary
Creating macros
| Directive | Syntax | Description |
|---|---|---|
| .macro | macname .macro [parameter1] ... [,parametern] |
Define a macro |
| .mlib | .mlib filename |
Identify a library containing macro definitions |
| .mexit | .mexit |
Jump to .endm |
| .endm | .endm |
End a macro definition |
Manipulating substitution symbols
| Directive | Syntax | Description |
|---|---|---|
| .asg | .asg ["]character string["], substitution symbol |
Assign a character string to a substitution symbol |
| .eval | .eval well-defined expression, substitution symbol |
Perform arithmetic on a numeric substitution symbol |
| .var | .var substitution symbol1 [,...,substitution symboln] |
Define local macro symbols |
Conditional assembly
| Directive | Syntax | Description |
|---|---|---|
| .if | .if well-defined expression |
Assemble the block if the condition is true |
| .elseif | .elseif well-defined expression |
Assemble the block if the .if was false and this condition is true (optional) |
| .else | .else |
Assemble the block if the .if was false (optional) |
| .endif | .endif |
End the .if block |
| .loop | .loop [well-defined expression] |
Begin repeatable assembly of a block |
| .break | .break [well-defined expression] |
End the .loop if the condition is true (optional) |
| .endloop | .endloop |
End the .loop block |
Producing assembly-time messages
| Directive | Syntax | Description |
|---|---|---|
| .emsg | .emsg |
Send an error message to standard output |
| .wmsg | .wmsg |
Send a warning message to standard output |
| .mmsg | .mmsg |
Send an assembly-time message to standard output |
Formatting the listing
| Directive | Syntax | Description |
|---|---|---|
| .drlist | .drlist |
Enable listing of all directive lines (default) |
| .drnolist | .drnolist |
Inhibit listing of .asg, .eval, .var, .sslist, .mlist, .fclist, .ssnolist, .mnolist, .fcnolist, .emsg, .mmsg, .wmsg, .length, .width, and .break |
| .fclist | .fclist |
Allow false conditional code block listing (default) |
| .fcnolist | .fcnolist |
Inhibit false conditional code block listing |
| .mlist | .mlist |
Allow macro listings (default) |
| .mnolist | .mnolist |
Inhibit macro listings |
| .sslist | .sslist |
Allow expanded substitution symbol listing |
| .ssnolist | .ssnolist |
Inhibit expanded substitution symbol listing (default) |
License
MIT - (c) 2026 Grzegorz Worona [email protected]. See LICENSE.