Each line of the source code is a declaration unit and is parsed in its own context. Such a source code line can be one of these constructs:
- A Z80 instruction, which can be directly compiled to binary code (such as
- A directive that is used by the preprocessor of the compiler (e.g.
- A pragma that emits binary output or instructs the compiler for about code emission (
- A compiler statement (or shortly, a statement) that implements control flow operations for the compiler
- A comment that helps the understanding of the code.
The Two Set of Symbols
The compiler works with two set of symbols. It uses the first set during the preprocessing phase in the
only in the directives. For example, with the
#define directive, you define a symbol, with
you remove it. Within the expressions you use in directives (such as
#if), you can refer only to these symbols.
The SpectNetIde option pages provide two options to declare your predefined symbols. When you compile
the code in the IDE, it will use these symbols as if you’d declare them with
You can declare multiple symbols and separate them with the
The other set of symbols are the one you declare as labels, or with the
You can use this set everywhere except directives.
This duality is related to the way the compiler works: in the first, preprocessing phase it only analyses directives. In the second, code emission phase, the compiler does not have any information about directives, and thus it does not accesses the symbols used in the preprocessor.
Assembly Language Flavors
I’ve designed the assembler with supporting multiple syntax flavors in mind. You do not have to explicitly declare the type of the syntax you intend to use, just use the flavor you prefer — or mix muliple flavors, as you wish.
For example, you can use several mnemonics for defining a series of bytes, such as
defb, and both lowecase or uppercase versions are welcome.
The assembler language uses a special way of case-sensitivity. You can write the reserved words (such as assembly instructions, pragmas, or directives) either with lowercase or uppercase letters, but you cannot mix these cases. For example, this instructions use proper syntax:
LD c,A JP #12ac ldir djnz MyLabel
However, in these samples, character cases are mixed, and do the compiler will refuse them:
Ld c,A Jp #12ac ldIR djNZ MyLabel
In symbolic names (labels, identifiers, etc.), you can mix lowercase and uppercase letters. Nonetheless, the compiler applies case-insensitive comparison when mathcing symbolic names. So, these statement pairs are totally equivalent with each other:
jp MainEx jp MAINEX djnz mylabel djnz MyLabel ld hl,ErrNo ld hl,errNo
In the future, I might implement a compiler option that allows turning off case-insensitivity.
SpectNetIDE support two types of comments: end-of-line and block comments.
En-of-line comments start with a semicolon (
;) or with a double forward slash (
//). The compiler takes the rest of the line into account as the body
of the comment. This sample illustrates this concept:
; This line is comment-only line Wait: ld b,8 ; Set the counter Wait1: djnz Wait1 // wait while the counter reaches zero
Block comments can be put anywhere within an instruction line betwen
*/ tokens, until they do not break other tokens. Nonetheless, block comments cannot span multiple lines, they must start and end within the same source code line. All of the block comments in this code snippet are correct:
SetAttr: ld b,32 fill: /* block */ /* b2 */ ld (hl),a inc /* b3 */ hl djnz /* b4 */ fill /* b5 */ ret
However, this will result in syntax error:
/* This block comment spans multiple lines, and thus, it is invalid */ SetAttr: ld b,32
If you need multi-line comments, you can add single-line comments after each other. The Z80 assembly in SpectNetIDE does not have separate multi-line comment syntax.
The language syntax provides these types of literals:
- Boolean values. The following tokens represent Booleans:
- Decimal numbers. You can use up to 5 digits (0..9) to declare a decimal number. Examples: 16, 32768, 2354.
- Floating point numbers. You can use the same notation for floating point numbers as in C/C++/Java/C#. Here are a few samples:
.25 123.456 12.45E34 12.45e-12 3e+4
- Hexadecimal numbers. You can use up to 4 hexadecimal digits (0..9, a..f or A..F) to declare
a hexadecimal literal. The compiler looks for one of the
$prefix, or one of the
Hsuffixes to recognize them as hexadecimal. If you use the
Hsuffixes, the hexadecimal number should start with a decimal digit
9; otherwise the assembler interprets it as an identifier (label). Here are a few samples:
#12AC 0x12ac $12Ac 12ACh 12acH 0AC34H
- Binary numbers. Literal starting with the one of the
0bprefix, (or, alternatively with the
Bsuffix) are taken into account as binary literals. You can follow the prefix with up to 16
1digits. To make them more readable, you can separate adjacent digits with the underscore (
_) character. These are all valid binary literals:
%01011111 0b01011111 0b_0101_1111 0101_1111b
- Octal numbers. You can use up to 6 digits (0..7) with an
Qsuffix to declare an octal number. Examples: 16, 327, 2354.
You can use negative number with the minus sign in front of them. Actually, the sign is not the part of the numeric literal, it is an operator.
- Characters. You can put a character between single quotes (for example:
- Strings. You can put a series of character between double quotes (for example:
You can use escape sequences to define non-visible or control characters, as you will learn soon.
.tokens. These literals are equivalent; all represent the current assembly address.
You can use identifiers to refer to labels and other constants. Identifiers must start with
a letter (a..z or A..Z) or with one of these characters:
#. The subsequent ones can be digits and any of the start characters except backtick. Here are a few samples:
MyCycle ERR_NO Cycle_4_Wait `MyTemp @ModLocal IsLastLine?
There are strings that can be both identifiers or hexadecimal literals with the
FADH. The assembler considers such strings as identifiers. To sign a hexadecimal literal, use a
0FADHis a hexadecimal literal, while
FADHis an identifier.
Theoretically, you can use as long identifiers as you want. I suggest you to make them no longer than 32 characters so that readers may read your code easily.
As you will later learn, the SpectNetIDE assembler supports modules that work like namespaces in other languages (Java, C#, C++, etc.) to encapsulate labels and symbols. To access symbols within modules, you can use scoped identifiers with this syntax:
::? identifier (
:: token means that name should start in the outermost (global) scope. The module and identifier segments are separated with a dot. Examples:
::FirstLevelModule.Routine1 NestedModule.ClearScreen FirstLevelModule.NestedModule.ClearScreen
Characters and Strings
You have already learned that you can utilize character and string literals (wrapped into single, or double quotes, respectively), such as in these samples:
"This is a string. The next sample is a single character:" 'c'
ZX Spectrum has a character set with special control characters such as AT, INK, PAPER, and so on.
The SpectNetIde assembler allows you to define them with special escape sequences:
Observe, some of these sequences have different values than their corresponding pairs in other languages, such as C, C++, C#, or Java.
To declare a character by its binary code, you can use the
\xHH sequences (
H is a hexadecimal digit). For example, these
escape sequence pairs are equivalent:
"\i" "\x10" "\C by me" "\x7f \x62y me"