/* This is a simple demonstration file for the Zeus assembler. If you want more sustantial files they're available at www.desdes.com These are just some jottings to help, so they'll just touch on the subjects... (Updated for Zeus v1.7) Expressions and operators. Zeus understands expressions containing any reasonable mix of integers, floating point values and/or strings. Zeus supports textual and/or/xor/not for operators as well as &,|,^ and !... Zeus supports << and >> for shifts as well as shl, shr. Zeus supports $,* and . for assembly position; these are subtly different. See below. Zeus supports non-case sensitive string comparisons using '=' Basically zeus handles expressions algebraically, with operator precidence. If all values are integers the operations will be integer only and the result will be an integer. If any values are floating point then the result of any operation on that value will be floating point. Note that the logical operators have a high precidence. Operator precidence, highest to lowest. () StringSlice[S or S..F] unary-, unary+,NOT,! AND,&,OR,|,XOR,^ <<,SHL,>>,SHR * / % MOD +- =,<>,>,<,>=,<=,==,!= Symbols are case sensitive. Fred, FRED and FrEd are three different symbols. On the subject of case sensitivity, all internal names, like "nop" and "equ", must be used in either lower case "nop" or upper case "NOP"... You can't mix cases, so "Nop" is not recognised. Now, this is not an accident or feature, it's deliberately provided so that lables like "Ret" and "ScF" can be used in your source without Zeus thinking they are internal tokens. I have added this to mimic the behaviour of the real zeus, which did allow the use of labels "Ret", "IXl" and so on in Dark Star and Forbidden Planet. If you are a huge fan of CamelCase, as mixed case is called, ask me nicely and I'll make this optional. Personally, I detest mixed case in assembler tokens. Zeus is an assembler, so by default it will perform integer arithmetic. However, if any of the values involved in an expression (or sub-expression) are floating point Zeus will promote the result to a floating-point value. Actually, that's not quite true - it only changes mode to floating point as it actually performs a monadic or diadic oeration that involves a floating point value. I hope that's clear? No? I'll explain further down - see the section below on mixing integer and floating point values... Similarly if any values are strings it will try to return a string result. Zeus is quite cute about handling expressions, and can generally be relied upon to do the right thing(tm). Integer constants can take any of the following forms: 1234 <- decimal 1234d <- decimal 1234D <- decimal %0101 <- Binary %01 01 <- Binary (spaces are allowed in binary numbers) 0101b <- Binary 0101B <- Binary $1234 <- Hex #1234 <- Hex 0x1234 <- hex 01234H <- hex In common with C numbers starting with 0 are taken to be octal, but I detest this so Zeus will warn you if you do it. 0123 <- Octal (Zeus will warn you about this format) 123o <- Octal The last thing is an "Oh" 123O <- Octal The last thing is a capital "Oh" 123q <- Octal The last thing is a lower case q 123Q <- Octal The last thing is an upper case q Floating point constants can only take these forms: 1. 123.345 Floating point is calculated to lots of bits; more than single precision. I haven't supported exponents (yet), when I do the format will be standard. Characters take this form (for preference) 'a' <- The ASCII value of 'a' is 97 'A' <- The ASCII value of 'A' is 64 Strings take this form "Hello!" Note - for backwards compatibility Zeus will USUALLY allow you to use a single-character string eg "a" in place of a character. This is quite a pain to support - consider the following expressions "a"+"b" <- this is a string "ab" "a"+1 <- this is an integer 98 1+"a" <- so is this In future sources I recommend using 'x' instead of "x" for single character values. Assignments and EQUs Zeus supports three different assignment operators "EQU", "=" and ":=" these are used for assigning values to labels and variables. But there is an important difference between them - EQU is used for labels and the values it is given must be integers and must not change. Or to strictly accurate the values used with EQU cannot change on any given pass - they can change between passes as zeus works out where things live in memory. You need not be concerned with this. The other two, "=" and ":=" are used with variables and the values they assign can be integers, floating point or strings. The values can also change as and when you like. Mixing integer and floating point in expressions Consider the following expression: Fred = 10/3 Because both 10 and 3 are integers the expression is calculated using integer arithmetic and so the result is an integer. In this case 3 Now, consider these very similar expressions: Bert = 10.0/3 Bert1 = 10/3.0 Bert2 = 10.0/3.0 Because in all these cases Zeus spots a floating point value the expression is calculated using floating point and so the result is a floating point value, in this case 3.33333333 Now, suppose we have two labels, which are both integers, and we want to perform a divide of one by the other but using floating point instead of integer arithmetic. To do this use the flt() function to tell Zeus that they are to be treated as floating point values. Fred = 10 Bert = 3 Mary = Fred/Bert ; Mary becomes 3 Jane = flt(Fred)/Bert ; Jane becomes 3.33333 Note that sub-expressions are evaluated in order using the types of their values, so if you mix integer and floating point values be careful. Consider these expressions: Fred = 3.5/2 + 1 Bert = 7/2 + 1.5 In both cases the divide is performed first, because divide has a higher precidence than add. However notice the difference - in the first case zeus performs a floating point divide because one of the operands (to the divide) is a floating-point number. In the second case zeus performs an integer divide because both 7 and 2 are integers. This produces the result 3, which is then added (as a float) to 1.5, to give the answer 4.5... Functions Simple: abs() returns the absolute value int() returns the integer part of a float frac() returns the fractional part of a float round() returns the rounded value of a float as an integer flt() forces an integer to be treated as a float - this is useful to make zeus perform floating point arithmetic on labels Trig: sin() returns the sin cos() returns the cos tan() returns the tan asin() returns the arcsin acos() returns the arccos atan() returns the arctan Mathematical log() returns log10 ln() returns loge sqrt() returns the square root Strings: length() returns the length of a string There is a nice string chopping 'function' - for any string you can place square brackets after it and include either a single number to select a single character, or two numbers separated by a '..' to select a range of characters. i.e. "Hello"[3] is "l" i.e. "Hello"[2..4] is "ell" If the end value is larger than the string then only the parts of the string that exist will be included: i.e. "Hello"[2..10000] is "ello" Strings can be concatenated (joined together) with + i.e. "Hello"+" "+"there!" is "Hello there!" These string operations will become particularly useful when macro parameter support is added. Variables and Labels Any symbol defined in the form "name = expression" is a variable. Any symbol defined in the form "name EQU expression" is fixed. Any symbol defined automatically, eg "name NOP" is a label. Labels can be defined as often as you like, provided each definition has the same value. Variables can be altered as often as you like. The value and the type are both mutable. Fixed values cannot be changed. Passes. Don't be afraid of forward references. Zeus will perform as many passes as required to resolve them. Zeus is fairly smart in this regard, it will continue to perform passes until it has resolved every reference (and the values are stable) or they stop achieving anything. It will then perform a final pass to output code. Z80 instruction set(s) If you want bog-standard Z80 (without any extensions) use the pseudo-op "STRICT" If you want sensible extensions (use of ixl,ixh,iyl,iyh as 8-bit registers) then leave it alone (that's the default) or use the pseudo-op "EXTENDED". These can bracket a piece of code if you want. There are some other extensions and relaxations over standard Z80, notably lists on push/pop pop instructions and multiple statement on a single line. e.g. in EXTENDED mode (the default) the following are legal: ld a,ixl ; low half of IX loaded into A in a,(bc) ; Zeus understands (bc) as well as (c) in I/O push bc,de,hl ; Order as listed, left to right pop hl,de,bc ; Order as listed, left to right jr nz Fred ; Normally needs a ',' after the condition code nop:nop:nop ; Three nops. (See zeus_ex_ld16 for an extended 16-bit load set). Incidentally, I really don't recommend using ":" for multiple statements. There is a nasty pitfall - consider the following mistake: nop:nooop:nop ; Two nops and a nooop! That nooop is a mistake, but the assembler can't spot it because it thinks along the following lines "Hmm. The monkey wants two nops, one with a label "nooop" in front" In other words, many fingering mistakes will be turned silently into labels, if they happen to be followed by a ":" in a multi-statement line. So don't use 'em... Expression/Addressing mode confusion. Consider the following code: ld a,(4) ld a,(2+2) ld a,(2+1)+1 All the same instruction? Nope. The first one is a load from memory location 4, so is the second. The third one is an immediate load of 5 into A... Many, many Z80 assemblers get this wrong, they look at the bracket and take it as meaning the indirect addressing mode, when it's actually being used arithmetically as part of a simple expression. Zeus does not make this mistake - Zeus parses past the bracket and only treats brackets as parts of addressing modes if they surround the entire expression. If you use other assemblers get into the habit of putting a "0+" in front of bracketed expressions... Indexed addressing modes. All the following are valid in standard Z80 assembly language and Zeus: ld a,(ix) ld a,(ix+0) ld a,(ix-0) Zeus is relaxed and also accepts spaces. This is not a requirement of the language. ld a,( ix ) ld a,( ix + 0 ) And so on. Basically everything that should be there, is there. Everything that isn't valid should be reported as an error. Errors. Zeus should accurately report errors. Shouldn't need saying, but surprisingly often assemblers fail to give accurate error reports. Clicking on the errors in the lower window jumps to the line containing the error, or at least the line containing the statement that caused Zeus to report the error... (Note - after Zeus v1.2 you need to click near the start of the error line, if you click near the end of the error line it will not jump to the error but give the report window focus. This change is to allow the mouse to scroll the report window.) Award yourself points for wondering what happens if you use a zeusprint statement to put an invalid error report in there and then click on it. */ ; Let's have some code. Doesn't actually do anything, but never mind... org $6000 Start call Dummy ; jr Start ; Dummy push bc,de,hl ; nop ; pop hl,de,bc ; ret ; Fred equ 1 ; Fred equ 1 ; Not a problem, same value FRED equ 2 ; Different label ZEUSPRINT "Fred is ",Fred ; Show 'em ZEUSPRINT "FRED is ",FRED ; Crude, I know /* This next command tells Zeus where to put the code it generates. As a szx file... It has three parameters, the filename, where the stack should go and where to start Alter the path to suit your system, of course. */ output_szx "zt2.szx",$0000,Start ; The szx file /* The output_szx can appear wherever you like in the file. If there's more than one of them the last one will be the one used. */ /* If for some reason you want raw binary, include this line It has three parameters, the filename, start of block and length */ ; output_bin "c:\zt2.bin",$0000,$10000 ; The binary file /* Note that I've commented it out. Normally it wouldn't be, of course. The output_bin can appear wherever you like in the file. If there's more than one of them the last one will be the one used. If both output types appear, both files will be written. */