zasm is an assembler for the Z-Machine. This document assumes familiarity with the specifics of the memory layout and intruction set of the Z-Machine. If you are not familiar with these things, you should take some time to read the Z-Machine Specification first.
zasm input files
zasm produces story files from input files. The input files for zasm are composed of a sequence of instructions and directives. Assembled instructions correspond directly to instructions in the resulting story file. Directives instruct zasm to do something other than assemble instructions.
A complete list of instructions can be found in the Z-Machine Specification. The current version of zasm supports only seven directives: byte, include, label, routine, string, word and zstring. Each is described in its own section below.
Before describing the instructions and directives in detail, it may be helpful to see a simple example. The input file below will cause zasm create a story file which simply prints a string and exits:
# # hello.zasm -- Hello world for zasm # # The entry point for all zasm programs is a routine labeled _start. routine _start 0 # We can use the print instruction to print a message print "Hello, world.\n" # The ret instruction will return from _start, exiting the # program ret 0
The above program contains one directive (routine) and two instructions (print and ret). The routine directive causes zasm to reserve space for a new routine beginning at the current position in the file. A routine must be declared with a label (_start in this case) and the number of local variables. (Parameters passed to routines are stored in a routine's local variables. See the Z-Machine Specification for more information on this). The _start routine is always the first routine executed in the resulting story file. Returning from the _start routine will cause the story to terminate execution. In this case, the value zero is returned from the _start routine.
Notice that comments begin with the hash symbol and proceed to the end of the line. Notice also that C-sytle escape sequences are used within strings.
Each instruction consists of an instruction opcode followed by zero or more instruction operands. Each operand can be a constant value, a variable or an address. Any of the three types of operands can be used with any instruction.
Constant values can be either decimal (base 10) or hexadecimal (base 16) values. Decimal values are written normally. Hexadecimal values are preceeded by the character sequence 0x. Here are some examples:
print_num 13 ret 0x4C
Variables can also be used in instructions. Variables are written with a dollar sign ($) followed by a hexadecimal variable number. The following are examples of instructions which use variables:
print_addr $0 push $3
Addresses are used to specify the location of a specific item as an operand to an instruction. If the item is a routine, the packed address of the routine is used. Otherwise the byte address of the item is used. zasm interprets a colon (:) followed by a label as an address. The following are examples of instructions using addresses:
call_2n :sort_routine $1 storew :status_pointer 0 :status_flag
Some instructions store the result of an operation. To assemble these instructions, a variable is required for storing the result. For these instructions, the variable syntax is used to specify the result location. The result location is specified following the operands of an instruction. The following are instructions which store their results in a variable:
mul 2 3 $1 read_char $2
Finally, there is a set of instructions which can be used to branch. Each of these instructions requires the location to branch to. When using zasm, the branch location is specified using the at-symbol (@) followed by the label specifying the location of the branch destination. To invert the sense of the branch, affix the branch instruction with an exclamation mark (!). Some examples of branch instructions include:
je $1 $2 @values_are_equal jg! $1 $2 @first_is_less_or_equal jump @unconditional_branch_destination
The byte directive
The byte directive causes zasm to reserve space in dynamic memory for a single byte of data. When using the byte directive, you must provide a label for the byte and an initial value, like this:
byte lives_left 9
The include directive
The include directive is used to assemble the contents of another file as if it were included at the current point in the current input file. It needs only the filename of the other file to include. It is used as follows:
The label directive
The label directive is used to declare a branch target at the current point in the input file. It requires a label to uniquely identify the position:
The routine directive
The routine directive is used to declare a routine entry point. It requires not only a label, but also the number of local variables for that routine. It can be used as follows:
routine _start 4
The string directive
The string directive is used store a NUL terminated ASCII string in dynamic memory. If you wish to store an encoded string, you should use the zstring directive instead. string can be used as follows:
string hello_world "Hello, world!\n"
The word directive
The word directive is used to store a word of information in dynamic memory. Like the byte directive, it requires a label for the word, and the initial value:
word zorkmid_count 1536
The zstring directive
The zstring directive is used to store an encoded string in the dynamic memory section of the resulting story file. Like the string directive, it requires a label and a string to encode:
zstring death_message "*** You have died ***\n"
The memory heap
zasm reserves all dynamic memory not already reserved for another purpose as a general purpose heap. This heap always begins as byte address 0x0040. (This is immediately following the story file header). The total size of the heap in byte is stored in the first word of the heap. The remaining bytes in the heap are initialized to zero.
See malloc.zasm for an implementation of routines using the memory heap.