ZSM, ZLINK, ZLIB, ZPATCH, ZC - Z80 assembler package Background. I originally wrote this assembler with a view to using it as part of a C compiler, however I don't know when, if ever, I'll write the compiler. As for the assembler, I chose to write my own because the only other one I've worked with is Microsoft's M80, and while this is a good assembler, I find that it is a bit to restrictive in the way it deals with external labels. In addition, I wanted to make library management easy, hence the production of ZLIB, and the .lib directive. Usage. Assuming that you have used an editor to produce your source file, A>ZSM FILENAME.Z -LSX -TB is the command that will turn it into an object file. Source files must have the .Z extension, and object files will have a .O extension (or .L: see libraries and the .lib directive). Since the .Z must be on the source file, it is optional on the command line. The only four flags recognised by ZSM are -L -S -X and -TX. As implied above, they can be handed to in groups: -LSX has the same effect as -L -S -X. The -T flag can be given in a group, but if this is done is is best made the last one, as it requires a following character. The -L flag generates a list file: in the above example it would have the name FILENAME.LST. List files are full of all sorts of goodies: in addition to the source, a fairly comprehensive copy of the object code is produced; ZSM also inserts page breaks in the right places, so you don't get a line of listing over the place where the paper tears; finally a cross reference page is produced at the end showing all the labels, variables and macros defined in the assembly. The -S flag generates a symbol table file called FILENAME.SYM, in the right format for use with ZSID (Digital Research's Z80 Symbolic Interactive Debugger). These symbols will only be correct if FILENAME.O is linked on it's own because, as will be seen later, linking in other files will throw some of the references off. Note also that since ZSID hasn't latched onto the fact that there is such a thing as lower case, all labels are converted to upper case before being written to the .SYM file. As a result, a little care and attention with labels will make work with ZSID a lot easier. The -X flag is used when a single source file is being assmbled and linked, this causes a linker built into ZSM to produce a .COM file directly, in addition to the .O file. This can represent a fair saving of time on a big file (source in the order of 32K), since ZLINK is a two pass linker (1st. pass sets up certain addresses and values); whereas the linker in ZSM only needs one pass as it already has evaluated the necessary addresses and values. The -X flag will be ignored if - A: the source contains external references (except #end - see ZLINK below), B: there are any errors in the source, C: the source produces a library. The -TX flag tells ZSM to place it's temporary files on drive X: usually ZSM places it's temporary files on the same drive as the source, by doing this, the temporary files are placed elsewhere. This sometimes allows assemblies to be done on otherwise almost full disks, or by putting the temporary files on a ramdisk, speed of assembly can be improved. As an alternative to supplying the arguments on the command line that invokes ZSM, it is possible to assemble several files with ZSM, start by giving it no arguments, then after it has signed on, it will prompt with a '=', and a command line typed in will be processed, after which ZSM will prompt again for another command line. This process will repeat until an empty line is given, allowing multiple assemblies without the overhead of loading ZSM each time. Now that FILENAME.O has been produced: A>ZLINK FILENAME.O -O OUTNAME LIB.L -S -TX is the sort of thing that will link it. FILENAME.O is a single object file, and always gets linked in. In the absence of a -O flag ZLINK gives the output .COM file the same name as the first file it encounters on the command line, however in the above example the output file will be called OUTNAME.COM, because of the -O OUTNAME present in the command line. Note that the name referenced by the -O flag can be a .O or .L file: A>ZLINK ARX.O -O PROGRAM.O LIB.L -S is perfectly acceptable, giving PROGRAM.COM from ARX.O & PROGRAM.O etc. The -TX flag works just like in ZSM - it places ZLINK's temporary files on drive X. LIB.L is a library: this is best considered as a list of .O files (or modules) all joined together, however instead of linking them all in, as happens with .O files in the command line, a .O module in a library only gets linked in if it will resolve a currently undefined label. Note also that the following is possible: if LIB1.L and LIB2.L both contain .O modules that would resolve the undefined external label "input", then the linkage: A>ZLINK PROGRAM.O LIB1.L LIB2.L would link in the module from LIB1.L to resolve "input", and leave the corresponding module in LIB2.L out. The -S flag has a similar effect to ZSM: it generates a symbol table file for ZSID, however it only contains labels defined as external in the assembly, internal labels will not show up. Note that the same restrictions apply regarding upper & lower case. ZLINK also recognises three other flags: -CXXXX, -DXXXX, -UXXXX. Under normal conditions, ZLINK puts all the code first, then the data immediately following the code, then the uninitialised data areas; and the addresses are set up so that the code starts at 100 hex: for a standard CP/M program. However by using the -C, -D and -U flags, it is possible to move these sections around, so to give an example: A>ZLINK PROM.O -C0 -U4000 would produce a .COM file where the data followed right after the code (no -D) but the code started at 0 hex, not 100 hex, and the uninitialised data started at 4000 hex, typically this would be used to produce code to burn into a prom in a dedicated environment, where the prom code starts at zero, and there is a ram chip at address 0x4000. Alternatively: A>ZLINK BIOS.O -CF400 would link up code for a CP/M bios, starting at 0xf400, with data and uninitialised data following right after. Note also that ZLINK creates one external label itself when linking code: "#end": this is defined as the address of the first byte after the end of the uninitialised data. Like ZSM, ZLINK can be made to prompt for its arguments, however the action taken by ZLINK in response to multiple line input is a little different. Instead of processing each line on it's own, ZLINK internally joins everything together and does one big linkage. This is especially useful when large numbers of .O and .L files are being linked, as it allows the files to be entered in groups. Note that several files can be given in response to one '=' prompt. While doing the linkage, in regards to order of files, the files are scanned from left to right on a given line, and first line first. One important thing to note in both ZSM and ZLINK: termination of input occurs when a line containing no arguments is given, in particular a single space is considered as no arguments, the practical upshot of which is that exit from ZSM and ZLINK under SUBMIT and XSUB can be achieved gracefully (c.f. PIP). ZSM itself (as will be noted) does not complain about undefined labels, it just notes them and passes them right along to ZLINK, however if ZLINK cannot resolve a reference, then it will complain, in addition it will complain if a label is defined more than once. Libraries. ZLIB, the librarian is invokes as follows: A>ZLIB LIBRARY.L and as in ZSM, since the .L is mandatory on the file being processed, it is optional on the command line. If LIBRARY.L does not exist, it is created, and if it does, then it is saved in LIBRARY.LBK to provide a backup. Once ZLIB has signed on, it will prompt for each command, once a command is given, ZLIB will prompt further for the other arguments necessary. Commands available to ZLIB are: 1. L(ist) - Print a list of all .O modules currently in the LIBRARY file. .O modules are tagged, depending on what action is to be taken: 'k' means the file will be killed, '>' means it will be output to a .O disk file, '<' means it has been input from a disk .O file, ' ; output: will create MODULE.O on disk 4: MODULE4.O k> ; extract: does both of the above 5: MODULE5.O < ; input disk file MODULE5.O to library 6: MODULE6.O ZPATCH PROGRAM.COM PATCHES.O is a typical command line invoking ZPATCH. PROGRAM.COM is the file being patched, and PATCHES.O is the patch file - like ZLIB, since there is no ambiguity of file extensions they are optional on the command line: if not present then they will be added by ZPATCH. PATCHES.O would be created by assembling a .Z source file with ZSM, and there are several limitations imposed on the source: first there can be no .dseg or .useg code or references (see section on directives for meaning of .dseg & .useg); also there can be no external references. The behavior of ZPATCH depends on what it has just encountered in the .O file: the only two significant items are .org directives (including ds opcodes) and "real" code (i.e. instructions or db / dw opcodes). If ZPATCH finds a .org directive (or a ds opcode), then it will start copying bytes over without modification, however when it encounters code or data in the .O file it replaces the bytes in the old version of the file with the bytes specified in the .O file. To give an example, consider the following .Z source file: .org 0x0100 start: jp patch .org 0x0862 entry: .org 0x2000 patch: ld de,string ld c,9 call 5 ld hl,entry ld (start + 1),hl jp start .org 0x2860 string: db 'Signon message\r\n$' The first line sets the label start: at 100 hex, which is the start of a CP/M program. The second line is a jump that will replace the original jump so that the code at patch: gets executed on startup. The third and fourth lines set the label entry: to 862 hex which was the original entry point of the program. The fifth line sets 2000 hex as being the address where part of the patch will go - this simply prints a signon message resets the jump at the start of the program and starts again at 100 hex. The final .org might be there because there isn't enough room at 2000 hex to include the signon string, so another patch area has to be used: 2860 hex in this case. If the .O file finishes it's work before the end of the .COM file then the only changes will be the inclusion of the patches, however if the .org's in the .O file extend beyond the end of the .COM file then the intervening spaces will be filled with zero bytes. When ZPATCH has finished it's work, it will have created a new .COM file, however it leaves the original in a .CBK file to provide a backup. Doing it all at once: ZC is based loosely on UNIX CC: the C compiler, with some MAKE thrown in (make is a program that does the minimum amount of work needed to bring a multi source executable up to date when you edit one or more of the sources). In particular, on the same disk as ZC.COM you should have ZSM.COM, ZLINK.COM, ARX.O and friends (see ARX.DOC), and LIB.L. A typical usage would be: A>ZC B:FILE.Z which would firstly invoke ZSM to assemble B:FILE.Z to B:FILE.O, and then invoke ZLINK to link ARX.O, B:FILE.O, and LIB.L together to give B:FILE.COM. By specifying several sources on the command line, ZC can be made to glue them all together into one: A>ZC B:FILE1.Z B:FILE2.Z would assemble the two sources, and then link them with ARX.O and LIB.L. As well as handling .Z files, ZC knows about .O files and .L libraries, so: A>ZC B:FILE3.Z C:FILE4.O C:LIBRARY.L is fair game: it would assemble B:FILE3.Z to B:FILE3.O, and then link everything together. ZC has a whole host of options: -S: generate a .SYM symbol table file - this is just passed along to ZLINK. -X: alter which ARX.O header is used: by saying -XSTR, STRARX.O is used instead of the regular ARX.O (for a list of the ARX.O files available and what they do see ARX.DOC). As a special case, just saying -X with no following string causes no ARX.O module to be used at all. When a string is provided, ARX.O is added to the end of it, and the resulting filename is used, so -XB: would use B:ARX.O, and -XC:Q would use C:QARX.O etc. -L: add extra libraries, or disable LIB.L. This works much like -X, except that these are additive: so -LB:X would include B:XLIB.L as well as LIB.L. Note that this option is position sensitive, in effect -LX is a shorthand that gets expanded to XLIB.L, in the same place as the -LX was in the command line. Again -L is a special case, it causes LIB.L not to be included, but any other libraries selected with other -L options still remain. Note that in normal usage, the selected ARX.O file always is the first thing to be linked, and LIB.L always follows all other files and libraries. -TX: move temporary files around - this is simply passed straight along to ZSM and ZLINK, and acts as explained above. -PX: look elsewhere for programs and ZLINK files: normally ZC looks on the current drive for programs (ZSM & ZLINK) and ZLINK files (ARX.O etc. & LIB.L etc.). This option tells it to look on drive X instead - so if B: were the current drive: B>A:ZC XYZ.Z -PA would cause ZSM etc. to be pulled from A: as opposed to the default drive B: -O: behaves just like the -O flag in ZLINK - it alters the name of the output .COM executable. As in ZLINK, the -O can be followed by a plain FILENAME, in which case the name is used, or it can be placed in front of a .Z, .O or .L file, in which case the name is transferred to the output file, as well as using the specified file for the assembly / linkage. -B: in a case such as: A>ZC FILE1.Z FILE2.Z FILE3.Z -B the -B causes the .Z files to assembled only if corresponding .BAK files exist. This is the 'Make' portion of ZC: by using this command option, only those files that need assembly have ZSM invoked for them. As an addition, -BE has the same effect, but in addition to doing the assembly, if the assembly succedes, the .BAK file is erased. -A: when ZC is invoked, it usually links the output .O files together. This causes the ZLINK phase to be bypassed, stopping with production of the .O files only. This is most useful for assembling a collection of .Z files, without linking them: A>ZC B:FILE1.Z B:FILE2.Z B:FILE3.Z -A just assembles the three .Z files and stops. -CXXXX, -DXXXX, -UXXXXX: these options are simply passed straight through to ZLINK, where they have the effects noted above. Features. ZSM recognises standard ZILOG neumonics, along the lines of ld a,(hl) or inc (ix+0x76) etc. etc. - these are not covered here as there are many good text books covering writing assembly language on the Z80. Comments start with a ';' and continue to the end of the line, to define a label, put the label name at the start of the line and follow it with a ':'. A typical line of code might then be: setup: add a,17 ; add the offset to a Line length is limited to 100 characters - anything else is simply truncated, the only time this is likely to cause an assembly error is if a long string is given to a 'db' statement. Case is not significant in opcodes, reserved operands or directives, so ld a,(hl) LD A,(HL) Ld a,(hL) are all legal and all produce the same thing. Targets of jp and call opcodes are covered later, but in the case of jr and djnz opcodes, the target can be one of three things: 1. jr label simply jump to the specified label; 2. jr label + expression jump to expression bytes offset from label: to give an example jr input + 3 ....... input: ld hl,buffer call getdata in this case, the jr would jump to the call, bypassing the ld hl instruction; 3. jr expression jump the specified number of bytes, for example dec a jr nz,-3 would implement a timing loop. In addition the '@' term is allowed in jr targets (see expressions) so the above loop could also be implemented as dec a jr nz,@ - 1 db: define a list of bytes, note that strings enclosed in quotes are legal, and as in C, the backslash is used as an escape character: to give an example: string: db 'Can\'t find file' by preceding the quote in the string with a backslash, it becomes a character in the string rather than a string delimiter. There are a total of 9 cases where the backslash has a significant effect: \r generates a carriage return (0x0d) \n generates a linefeed (0x0a) \b generates a backspace (0x08) \t generates a tab (0x09) \f generates a formfeed (0x0c) \e generates an escape (0x1b) \\ generates a backslash (0x5c) \' generates a single quote (0x27) \OOO generates the octal character OOO in this last case OOO can be at most 3 octal digits, to give a few examples: '\033' generates an escape, as does '\33', '\0' generates a null, '\177' generates a delete char. To briefly explain, when ZSM finds a backslash followed by an octal digit ('0' to '7') it will continue scanning until either it finds a character which is not a legal octal digit, or it has scanned three characters: so '\0034' will generate 0x03 0x34, the \003 gives the 0x03, and the 4 gives the 0x34. Note that a single backslash preceding any other character is ignored: 'stringX' and 'string\X' generate exactly the same thing. Typical use of the \r type sequences might be in a case such as db 'Data error:\r\nTry again please\r\n' which is somewhat easier than db 'Data error:',0xd,0xa,'Try again please',0xd,0xa although both would generate the same thing. Alternatively, to ring the bell on a video screen a string such as db 'ERROR\007' or db 'ERROR\7' could be used. As is shown above, multiple strings can be given, they are simply concatenated and output in the order given. Note also that expressions are legal, so db 'This "', 'c' & 0x1f, '" is a control C' would be perfectly legal, generating a control C in the middle of the text (even though ' ..."\003"... ' would do the same thing) dw: generates words of data, arranged in the right order (low byte first) dw 0x1234, label dw label - 0x1000 unlike db's, dw "operands" can be relocatable, so a jump table might be implemented as follows: dw l1,l2 dw l3,l4 dw 0 with the zero word flagging the end of the table. Note also that while up to 100 bytes can be generated with just one db statement, the limit is 2 words (4 bytes) with one dw statement - the table above could not be implemented as: dw l1,l2,l3,l4,0 ds: generate a space of the given size: ds 0x1000 if given in the .cseg or .dseg sections of a program, then the space is guaranteed to be zero filled, if given in the .useg section, then the contents will be garbage at the start of program execution (see section on directives for an explanation of .cseg, .dseg, .useg) Finally, ZSM does not need a .end directive to make it stop (indeed there is no provision for recognising such a thing) - it simply keeps on going till it falls off the end of the source file. Identifiers. An identifier is a collection of seven or fewer characters, given that the first must be a letter (ZSM considers the underscore and hash characters '_' and '#' as letters), and the remaining six must be letters or numbers. ZSM is case sensitive regarding identifiers, but all characters following the seventh are ignored, so Foo: foo: are different labels, whereas long_label long_label1 are not. Opcodes, reserved operands & directives cannot be used as identifiers: you cannot have a variable called 'hl' etc. etc. Expressions. ZSM recognises two types of expression: absolute or relocatable. An absolute expression is one composed of variables and constants, i.e. one that can be evaluated fully by the assembler during pass 1 - these can be used anywhere. In addition, if an opcode has a word operand such as call address ld hl,(address) dw address1, address2 then the address can be considered as a relocatable expression, which is simply a label, followed by an optional absolute expression: for example call setup ld hl,(table + 0x0100) dw swtab - 2, 0x1234 In the first of the above examples 'setup' is a label, in addition to using a label, the term '@' refers to the start of the current instruction: so jp p,@ + 6 ld hl,..... will only do the 'ld hl' if the 'm' flag is set. In absolute expressions, there are seven levels of operator precedence: 1. || &&: logical or / logical and. x && y is true (1) iff both x and y are true (non-zero), x || y is true iff either x or y or both are non-zero. 2. == != >= <= > <: relational operators: in order shown equal, not equal, greater than or equal, less than or equal greater than, less than. Note that expressions are unsigned, so -1 < 0 is false as -1 is considered to be 65535 3. >> <<: right shift, left shift: 1 << 3 is 8, 0x8000 >> 15 is 1, and not 0xffff: right shift is zero fill 4. & | ^: bitwise and, bitwise or, bitwise exclusive or. Note that 1 & 2 is zero, whereas 1 && 2 is 1 5. + -: binary addition and subtraction 6. * / %: binary multiply, divide, modulus. Note that division by zero does not generate an error: it just returns zero, and the % operator gives remainder on division: so 10 % 3 is 1, again anything % 0 gives zero, without an error 7. ! - + ~ [] {}: unary logical not, unary minus, unary plus, unary bitwise not, brackets to force order of evaluation, braces to evaluate a relocatable difference. ! 0 is 1 ! 1 == ! 76 == 0 as is ![any non-zero value] ~ 0 == 0xffff ~ 0xc000 == 0x3fff Square brackets are used to force order of evaluation as they solve problems in potentially ambiguous cases such as ld hl,('0' << 8) + ' ' In an expression, a term with the form {label1 - label2} will be evaluated as the difference between label1 and label, typically it can be used to determine the size of a table, or the length of a section of code. Certain restrictions apply to the use of such terms: they can be used in both relocatable and absolute expressions: ld bc,{endtbl - table} ; get size of table to bc in this case the only limitation on endtbl and table is that they be defined in the current source code file, and that they be in the same segment. However in a case such as .var ident {label - base} or ds {end - start} these are absolute expressions, and as such they must be evaluated immediately on being encountered, hence the labels they contain must already have been defined otherwise an error will result. This is to prevent paradoxes such as this ocurring table: ...... ds {endtbl - table} ....... endtbl: Note also that '@' can be used as a legal "label" in { } terms, so code such as label: ......... .var ident {@ - label} * 2 or ld bc,{foo - @} are both legal. Constants are assumed in base 10, unless there is a leading 0X / 0x for hexadecimal (both 'A' - 'F' and 'a' - 'f' are allowed), 0 for octal, or 0B / 0b for binary, in addition a single character enclosed in quotes is considered a legal constant by the expression parser, so db 0x4D ; hexadecimal db 77 ; decimal db 0113 ; octal db 0b1001011 ; binary db '\113' ; octal character db 'M' ; ascii character all produce the same thing Directives There are 14 directives: they are introduced by starting a line with a period: 1. .cseg, .dseg, .useg: define code, data or uninitialised data segments. These are the segment directives that are used when generating standard CP/M .COM files: .cseg is aimed to hold all the executable code, .dseg holds initialised data, and .useg holds uninitialised data. These can be freely intermixed in the source file: by the time ZLINK has finished all the .cseg code will be contiguous in the .COM file, followed by the .dseg data. After the .dseg data is finished, .useg space begins. Two things to note: ZSM starts assuming .cseg; and .useg space can only contain label definitions, ds opcodes, and .org directives (q.v.) .useg is best used for allocating space for large tables that are to be filled in at run time. To give a couple of examples: .cseg ld de,string .dseg string: db 'Hello World!\r\n$' .cseg ld c,9 call 5 jp 0 will print 'Hello World!' because the string is pulled out of the middle of the code; similarly .cseg ld hl,table .useg table: ds 0x0100 .cseg ld de,table+1 ld bc,0x00ff ld (hl),0xff ldir will fill a table with 256 bytes all -1, further the table will not occupy any space in the .COM file, .useg can only be used to create labels for reference purposes: before the ldir is executed the table will be full of junk. 2. .org: origin, which allows holes to be left in code, which will be zero filled in .cseg & .dseg, junk filled in .useg. Note that .org's refer to the base of the current segment, and .org's only move forward, so .org 0x4000 followed by .org 0x3000 will create a hole of 60K in the output file. In the absence of any .orgs, ZLINK puts the .COM file together to run at 0x0100 so it will work right under CP/M. The main use for these is putting code in the right place with ZPATCH, because results of using this directive in multi-source files is not particularly useful, and moving things around with ZLINK is far easier with the -C, -D and -U flags. 3. .extern: define a label as external: .extern label By default all labels are defined internal, unless a .extern is used, however when used, if a label is not defined in the current file, it is automatically assumed to be defined externally in another module which will be linked in by ZLINK. The exception to this is jr targets: these must (a) be defined in the current file; and (b) exist in the same segment. As a result of all of this, ZSM will never complain about undefined labels, it just notes them and leaves it for ZLINK to sort it all out (in much the same way as a C compiler handles procedure definition and usage). 4. .incl: include a .I file: .incl "file" causes FILE.I to be included: these are most useful to hold macro definitions and variable declarations (qq. v.), see STDHDR.I and BDOS.I for typical examples. 5. .macro, .endm: macro definition: .macro load r1,r2,offset ld r2,(ix+offset) ld r1,(ix+[offset]+1) .endm is a typical macro definition: in usage a line of the type: load h,l,10 generates the following: ld l,(ix+10) ld h,(ix+11) As regards macro arguments, when the macro is defined, they must be legal identifiers, however when the macro is used they can be any eleven characters except ',' and ';' as these two are reserved for delimiting. A macro may have any number of arguments up to eight, if a macro is invoked with too many arguments, then the extra ones are ignored, and if there are too few arguments, then the undefined ones are left blank. Macro usages can nest up to a maximum depth of five, and recursive macros are only detected by the excessive nesting depth. 6. .var: set a variable: .var variable expression the above line defines variable if it doesn't already exist, and assigns it the value of the expression. To give a real life example: .var bdos 5 allows a line of the type call bdos to get into CP/M. There can be no duplication of names, i.e. no two of: foo: .var foo .... .macro foo can appear in the same .Z file, however the line .var foo expression can be included many times, each occurence changing the value of foo. 7. .lib: direct library production - to give an example: .lib "strcpy" ; generating STRCPY.O in the output .L file .extern _strcpy ; _strcpy is the only external label _strcpy: ; here it is - note that it could be anywhere call #arg2 ; from here to the next .lib l1: ld a,(de) ; and the code .... etc. etc. is the start of a typical library module source: in the above example ZSM will produce a .L file directly, and the code segment given would behave just as if ZLIB had been used to insert a file STRCPY.O. Note that ALL .externs in a library source must come immediately after a .lib, and that .lib automatically does a .cseg. .lib has to must be the first thing in the file (with the exception of .macro's and .var's) so that ZSM latches onto the notion of producing a .L file before it actually does anything. 8. .if, .else, .elif, .endif: conditional assembly. These directives allow assembly of different sections of code depending on the value of expressions: .if test1 code1 ; assembled iff test1 is true .elif test2 code2 ; assembled iff test1 false and test2 true .else code3 ; assembled iff both test1 and test2 false .endif To explain more fully, .if starts a section of conditional code, .else (optional) "reverses" the test, only code on one side of the .else is used, and .endif closes the .if down. .elif has exactly the same result as .else .if test2 except that using .elif saves a level of nesting. Only one .elif is shown above, they can be chained together as many times as are needed following an initial .if. As with macro usage, .if's nest, up to a maximum depth of five. Errors There are 16 error codes: when an error occurs, ZSM prints the offending line, including (among other things) a letter code in the thirty third character explaining what went wrong: a bad arguments in macro invocation c constant error: illegal digit in constant (e.g. 059 or 0b011020) d directive error: e.g. misplaced .lib's, .endm's, etc., or bad syntax e expression error: something wrong with expression syntax f .incl file not found g segment error: typically code in .useg i .if error: misplaced .else, .elif, .endif l jr length error or jr to undefined label m multiply defined label / macro n macro's / if's nested too deep o opcode error: unknown opcode p error with formal parameters in macro definition q missing trailing ' in db string s syntax error: something wrong with the operands u undefined variable in expression v value error e.g. rst 77 or im 10 Only the first error on a line will be reported, for example in the case of a multiply defined label AND a syntax error, only the 'm' error will get reported. In addition ZSM will exit if there is a symbol table overflow, or macro / symbol definitions run it out of memory, but as there are some 1000 symbol table slots, it requires typically in excess of 64K of source to approach these kinds of limits.