/*
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 v4.06)
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 by default the logical operators have a high precidence.
Default operator precidence, highest to lowest.
()
StringSlice[S or S..F]
unary-, unary+,NOT,!
AND,&,OR,|,XOR,^
<<,SHL,>>,SHR
* / % MOD
+-
=,<>,>,<,>=,<=,==,!=
BUT there's a configuration option to lower the precedence of logical operators, as too
many assemblers nowadays are written by/for high-level programmers who are used to
the logical operators being low priority. See the history text for details.
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.
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 take these forms:
1.
123.345
123.45E10
123.45E-10
Floating point is calculated to lots of bits; more than single precision.
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!"
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 very 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 to tell the poor dumb things what's actually going on.
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.
Zeus supports dozens of output file formats, they're documented in the history text.
*/