Headers, Libraries, and Macros
In this chapter we will split away parts of our Hello
World
program into reusable header files and libraries.
We will also abstract away our string printing technique into a
macro which may be invoked at will, on arbitrary strings. We will
then multiply the output of our program tenfold.
Header files and libraries
The prelude to our program—the PRG
information and the BASIC program—are going to be the same
in many, many programs. Thus, we should put them into a header
file to be included later. The .include
directive will load a file and insert it as source at the
designated point.
A related directive, .require, will include
the file as long as it hasn't been included yet elsewhere. It
is useful for ensuring a library is present somewhere in the
final code.
For pre-assembled code or raw binary data,
the .incbin directive lets you include the
contents of a binary file directly in the output. This is handy
for linking in pre-created graphics or sound data.
If you only wish to include part of a binary
file, .incbin takes up to two optional
arguments, naming the file offset at which to start reading and
the number of characters to read.
As a sample library, we will expand the definition of
the chrout routine to include the standard
names for every KERNAL routine. Our header file will
then .require it.
We'll also add some convenience aliases for things like reverse
video, color changes, and shifting between upper case/graphics
and mixed case text. We'd feed those to
the chrout routine to get their effects.
Since there have been no interesting changes to the prelude, and
the KERNAL values are standard, we do not reproduce them here.
(The files in question are and .) The c64kernal.oph
header is likely to be useful in your own projects, and it is
available in the platform/ directory for easy
inclusion.
Macros
A macro is a way of expressing a lot of code or data with a
simple shorthand. It's also usually configurable. Traditional
macro systems such as C's #define mechanic
use textual replacement: a macro is
expanded before any evaluation or even parsing occurs.
In contrast, Ophis's macro system uses a call by
value approach where the arguments to macros are
evaluated to bytes or words before being inserted into the macro
body. This produces effects much closer to those of a
traditional function call. A more detailed discussion of the
tradeoffs may be found in .
Macro definitions
A macro definition is a set of statements between
a .macro statement and
a .macend statement.
The .macro statement also names the macro
being defined.
No global or anonymous labels may be defined inside a macro:
temporary labels only persist in the macro expansion itself.
(Each macro body has its own scope. A label map will trace
back through macro expansions to describe were a label inside
a macro body came from.)
Arguments to macros are referred to by number: the first is
_1, the second _2, and so on.
Here's a macro that encapsulates the printing routine in our
Hello World
program, with an argument being the
address of the string to print:
.macro print
ldx #0
_loop: lda _1, x
beq _done
jsr chrout
inx
bne _loop
_done:
.macend
Macro invocations
The most common way to invoke a macro is to backquote the name
of the macro. It is also possible to use
the .invoke command. These commands look
like this:
`print msg
.invoke print msg
Arguments are passed to the macro as a comma-separated list.
They must all be expressions that evaluate to byte or word
values—a mechanism similar to .alias
is used to assign their values to the _n
names.
Example code
expands our
running example, including the code above and also defining a
new macro greet that takes a string argument
and prints a greeting to it. It then greets far too many
targets.