/* This is a simple demonstration file for the Zeus assembler. If you want more sustantial files they're available at www.desdes.com Macros From v1.4 onwards Zeus supports parameterless macros. From v1.7 onwards Zeus provides full macro support including parameters. From v1.8 onwards Zeus allows string to token conversion so that instructions can be assembled from expressions and macro parameters. Macro support took a reasonable amount of effort, despite which I'd advise you all to avoid using them unless they really help - one of the beauties of assembler is the way that everything is clearly specified and visible - used carelessly macros can defeat this. I have seen them add great amounts of confusion to a source when used badly... So what sensible uses are there for macros? Well, usually it's adding things that look like instructions for occasions where the Z80 doesn't actually have that instruction. For example, take negate. The Z80 can negate the accumulator, but that's it. For example, the following is a valid macro definition for a macro called NegHL() : NegHL macro() ; This doesn't take parameters ld a,l ; Code to negate HL cpl ; ld l,a ; ld a,h ; cpl ; ld h,a ; inc hl ; mend ; Macro end Then Zeus will replace the macro NegHL() with that source, wherever it occurs. So, if we had the following (rather pointless) code: ld hl,$0001 ; Make HL 1 NegHL() ; Plant the code to negate it NegHL() ; And again... Zeus would actually generate: ld hl,$0001 ; ld a,l ; Code to negate HL cpl ; ld l,a ; ld a,h ; cpl ; ld h,a ; inc hl ; ; At this point hl would equal $FFFF ld a,l ; Code to negate HL cpl ; ld l,a ; ld a,h ; cpl ; ld h,a ; inc hl ; ; And hl is back to $0001 And so on. Note: The macro definition does not have to occur before it is used, though it will usually save a pass if it does. Note: The macro definition may include local labels. For example: PauseA macro() ; Lp dec a ; This "Lp" is local to the macro jr nz Lp ; mend ; In general, all labels defined inside a macro are local to that macro. Macro definitions cannot be nested, i.e. you cannot define one macro inside another, but macros can use other macros, and they can recursively call themselves. InvL macro() ; No parameters supported (yet) ld a,l ; Code to invert L cpl ; ld l,a ; mend ; Macro end InvH macro() ; No parameters supported (yet) ld a,h ; Code to invert H cpl ; ld h,a ; mend ; Macro end ; This macro includes other macros NestedNegHL macro() ; No parameters supported (yet) InvL() ; Invert L InvH() ; Invert H inc hl ; +1 mend ; Macro end NestedNegHL() would produce exactly the same code as NegHL() Macros with parameters. From version 1.7 onwards Zeus supports macro parameters. This is the source for a macro that takes a single parameter, and which just prints that parameter. print macro(p1) zeusprint p1 mend I hope that is easy to understand - the p1 is just a name used inside the macro to describe whatever we pass to it. These parameter names are local to the macro. If that macro exists we can use it as follows: print(42) print("fred") Note that the macro can exist anywhere in any of the sources included in the project. It can be declared before or after use. But we can pass much more interesting things than just numbers and strings to macros. Let us also declare a macro that repeats whatever it is passed a number of times duplicate macro(count,object) ; We're given two parameters loop count ; Assume the count is an integer and loop that many times object ; Just 'do' whatever we're given each time lend ; Loop end mend ; Done Now, look at the 'object' parameter. We don't care what it is, we just give it to zeus inside the loop and let zeus figure it out each time. So, this call to the macro, for instance, repeats an "inc a" instruction 8 times duplicate(8,inc a) Do see how it works? When zeus interprets the macro "duplicate" Zeus replaces the word "count" with the text "8" and the word "object" with the text "inc a" and since that is inside the loop statement it is used 8 times. Now, suppose we wanted to use this to repeat the instruction "ld a,b" eight times? Well, you might think to try duplicate(8,ld a,b) - but that won't work - there's a pesky comma in the instruction "ld a,b" and zeus will look at that as separating three different parameters. Since the macro doesn't want three parameters this will be reported as an error. Well, perhaps we could try enclosing the ld a,b in quotes? duplicate(8,"ld a,b") perhaps? Nope. That won't work, but for a different reason - zeus is happy with the number of parameters, and the macro will repeat "ld a,b" eight times, but the problem is that "ld a,b" is a string and zeus doesn't automatically look inside strings to see if they're valid instructions. However, there is a way to group text so that it can be passed to macros and still considered as instructions; by using the curly braces like this: duplicate(8, { ld a,b } ) Until version 1.7 of zeus curly braces were a variety of comment marker. I think this is a rather better use for them. The spaces are optional, I added them to make the text clearer. Same again, but this time we pass several instructions in the block. Note the use of ':' here to separate the instructions. duplicate(8, { ld a,(hl):ld (de),a:inc hl:inc de } ) But remember that zeus doesn't care much about spaces and line ends, so why use ":" when we can just have end of lines do the job? duplicate(8, { ld a,(hl) ld (de),a inc hl inc de } ) I gave a simple parameterless negate macro above, but a more powerful one might be written as follows: First we write a macro to invert any reasonable register. Invert means to change the state of every bit. This wants to do the following, first if the register is a it should just use the cpl instruction. If the register is one of the other 8-bit registers it should load that register into a, cpl it and load it back. If the register is one of bc,de or hl it should invert both 8-bit registers separately. This code will do that. It relies on zeus's ability to treat lexical items as strings when they occur inside expressions (zeus only allows this inside macros, to avoid errors) which allows you to write simple conditional statements like "if reg=a" to test the reg parameter... // Invert a register Inv macro(reg) if reg=a cpl elseif (reg=b) or (reg=c) or (reg=d) or (reg=e) or (reg=h) or (reg=l) ld a,reg cpl ld reg,a elseif reg=bc Inv(c) Inv(b) elseif reg=de Inv(e) Inv(d) elseif reg=hl Inv(l) Inv(h) else zeuserror "Inv - this parameter '",reg,"' isn't supported" endif mend Note that Zeus's string comparison is not case-sensitive, so this will work with both upper and lower case register names. Note also the "zeuserror" statement. This is used to tell zeus of an error condition. It takes the same parameters as a zeusprint statement, but it generates an error message. Also, notice the recursive calls in there - it calls itself to invert the halves of the register pairs. This following Neg macro then uses the Inv macro to handle the inversion part of the negate process: // Negate a register Neg macro(reg) if reg=a neg elseif (reg=b) or (reg=c) or (reg=d) or (reg=e) or (reg=h) or (reg=l) ld a,reg neg ld reg,a elseif (reg=bc) or (reg=de) or (reg=hl) Inv(reg) inc reg else zeuserror "Neg - this parameter '",reg,"' isn't supported" endif mend Now, this all looks reasonable, but we can improve it slightly. What we would like to do is take something like, say, hl and extract the h and the l parts for use in other instructions. This is where the "\" operator comes in - it expects to be followed by a string and it effectively tells Zeus to look at the text inside the string and use that as if it was typed into the source. So, for example all the following lines generate a "ld a,b" instruction (when used inside a macro): ld a,b ; Obviously ld a,\"b" ; Take the string "b" and use it as a register name. ld a,\"bc"[1] ; Take the first character of the string "bc", ie "b" ; and use it as a register, ie b ld a,\b ; Zeus converts register b to a string and then back again So, we could write a more complete, yet shorter, version of the negate code as follows. These two macros can both be given either register names or strings as parameters. // Invert a register. This macro is written so it can handle registers OR strings as parameters Inv macro(reg) if reg=a cpl elseif (reg=b) or (reg=c) or (reg=d) or (reg=e) or (reg=h) or (reg=l) or (reg=ixl) or (reg=ixh) or (reg=iyl) or (reg=iyh) ld a,\reg cpl ld \reg,a elseif (reg=bc) or (reg=de) or (reg=hl) Inv(reg[1]) Inv(reg[2]) elseif (reg=ix) or (reg=iy) Inv(reg+"l") Inv(reg+"h") else zeuserror "Inv - this parameter '",reg,"' isn't supported" endif mend Note the recursive calls to itself to handle the halves of 16-bit register pairs. These calculate the register names on the fly by chopping up the register names. // Negate a register. This macro is written so it can handle registers OR strings as parameters Neg macro(reg) if reg=a neg elseif (reg=b) or (reg=c) or (reg=d) or (reg=e) or (reg=h) or (reg=l) or (reg=ixl) or (reg=ixh) or (reg=iyl) or (reg=iyh) ld a,\reg neg ld \reg,a elseif (reg=bc) or (reg=de) or (reg=hl) or (reg=ix) or (reg=iy) Inv(reg) inc \reg else zeuserror "Neg - this parameter '",reg,"' isn't supported" endif mend So, to use that, you would put "Neg(a)" or "Neg(ix)" or whatever in your source. Incidentally, you might be wondering why I haven't spread the long (reg=blah) or (reg=blah) (etc) expression over two lines? Well, think about it... No? Look at it like this. Do you think that these two mean the same thing? if fred or bert halt endif and if fred or bert halt endif Well, what about that "or bert" ? Is it part of the expression, or is it an OR instruction that is conditional on the previous if statement? It's the second case. It has to be - if zeus allowed multi-line expressions then any z80 OR, AND and XOR instruction that happened to follow a line that had an expression would be joined with that expression. Consider this, for example: ld hl,1 or 2 If Zeus let expressions spread across lines then that would be: ld hl,1 or 2 ; -> ld hl,3 ...which is certainly not what the programmer intended. Returning to that macro, splitting the long conditional across two lines might give, say: elseif (reg=b) or (reg=c) or (reg=d) or (reg=e) or (reg=h) or (reg=l) or (reg=ixl) or (reg=ixh) or (reg=iyl) or (reg=iyh) Which is actually an "elseif (reg=b) or (reg=c) or (reg=d) or (reg=e) or (reg=h) or (reg=l)" followed by an OR instruction "or 0" or "or 1" depending on the parameter. Take a few moments to grok this because the problem is that it's a silent failure. Zeus cannot tell you that this is wrong - it isn't. It just doesn't mean what you think it means (and no, I don't have six fingers... forget it... that isn't important right now) Now, as a special case, added at the last minute and at great expense, zeus will ignore line ends in expressions that are contained inside brackets. So the following source does what you would expect (and works): elseif ( (reg=b) or (reg=c) or (reg=d) or (reg=e) or (reg=h) or (reg=l) or (reg=ixl) or (reg=ixh) or (reg=iyl) or (reg=iyh) ) But I'd tend to avoid this form unless you're a crayon-monger and won't forget the brackets around the expression... Going back to the Neg macro. There is one other gotcha here that I should mention, mainly because it got me ;) What if you try "Neg( (HL) ) ? Would this pass (hl) and try to negate that? Well, no, not exactly... It'll negate hl. The reason is that as far as Zeus is concerned when it is processing the text (hl) in the macro that's a hl inside brackets, and zeus not unreasonably thinks, hmmm, I think the monkey has given me a string expression contained in brackets - I'll strip out the brackets... Now, it's not going to do that in normal source, it wouldn't be much use as a z80 assembler if it did, but inside macros, or to be accurate inside expressions that are inside macros, the distinction between lexical items and strings is relaxed. Note also that the '\' string-to-token operator is only allowed inside macros. Basically, some of the lexical rules inside macros are relaxed; this makes them more powerful at the risk of encouraging sloppy code. Also note that zeus dynamically expands macro source. It isn't just a string replacement carried out before the file is processed - it is carried out while the file is processed. This means that the parameters can dynamically change between or even during passes. */