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 linked in. 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 Macros may be invoked in two ways: one that looks like a directive, and one that looks like an instruction. 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.