Writing Source
The syntax underneath everything else: what a line looks like, how to name things, how to write a number, and what may go in an expression.
A line
label: ld a, 1 ; a commentEvery part is optional. A label may stand on its own, an instruction needs no label, and ; starts a comment that runs to the end of the line.
Mnemonics and register names are case-insensitive. LD A,1, ld a,1 and Ld A,1 all assemble to 3E 01. Your own names are not — see below.
Labels
A label starts with a letter, an underscore or a period, and may end with a colon. The colon is optional and does nothing:
start:
ld a, 1
done retYour names are case-sensitive by default. Counter: and ld hl, counter is E1003, naming the spelling it could not find. -i or case_insensitive = true turns that off if you want it — see Command Line.
⛔ Some words are claimed, and where depends on what you are naming. A directive name — hex, word, message — is fine as a label and as a constant. A register or condition name — a, c, z — is fine as a label but not as a constant. Neither may name a MODULE, a macro or a macro parameter, which is E1062, E1060 and E1061. The full list is in Diagnostics; check it early when a name behaves strangely.
Local labels
A label beginning with . belongs to the nearest non-local label above it. That means the same spelling can be reused freely:
first:
.loop: djnz .loop
ret
second:
.loop: djnz .loop ; a different label; no clash
retThis is the ordinary way to write loops without inventing loop1, loop2, loop3.
Constants
Two spellings, identical in effect:
WIDTH equ 32
HEIGHT = 24
AREA equ WIDTH * HEIGHTA constant is fixed once. A label is an address the assembler works out; a constant is a value you state.
A constant may hold text, which db and dz assemble and SIZEOF measures:
BANNER equ __NAME__ + " v" + __VERSION__
dz BANNERUsing text where a number is wanted is E1075 — ld a, BANNER is refused rather than silently assembling something meaningless. __NAME__ and __VERSION__ come from The Project File.
Numbers
| Base | Forms |
|---|---|
| Hexadecimal | 0x12EF, $12EF, 12EFh (or H) |
| Binary | %10101010, 0b10101010, 10101010b (or B) |
| Decimal | 1234 |
| Character | 'A' — the ZX Spectrum character code |
⛔ A trailing-radix literal must start with a decimal digit. FFh begins with a letter, so it is a name, and using it gives E1003 Undefined label or constant: FFh. Write 0FFh. This catches people out precisely because the diagnostic talks about labels when you were thinking about numbers.
Digit separators
_ may sit between digits, in any base:
db 65_536 & 255
db 0xF_F
db %1010_1010Not at either end and not after a prefix — _100 is a label, 1000_ is not a number, and 0x_FF is a syntax error. An apostrophe separator, which sjasmplus accepts, is reported by name as E1076 rather than being mistaken for a bad label.
Characters and strings
A literal closes only on the quote that opened it, so neither of these needs escaping:
db "it's"
db '"'A backslash introduces an escape in both forms:
| Written | Produces |
|---|---|
\\ | a backslash, $5C |
\" | a double quote |
\' | an apostrophe |
\n | newline, $0A |
\r | carriage return, $0D |
\t | tab, $09 |
\0 | null, $00 |
⛔ Any other character after a backslash is an error (1072), not a literal backslash. A Windows path is written "C:\\tools". This used to be dropped in silence, which turned "C:\path" into C:path with nothing said.
Strings are translated to the ZX Spectrum character set as they are emitted. dz adds a terminating zero; db does not.
Expressions
Anywhere a number is wanted, an expression will do.
| Operators | |
|---|---|
| Arithmetic | + - * / |
| Bitwise | & ` |
| Comparison | < > — yielding 1 or 0 |
| Grouping | ( ) |
Precedence is respected: 2 + 3 * 4 is 14, and (2 + 3) * 4 is 20.
There is no modulo, no exclusive-or, no == or !=, and no logical &&. Comparisons give you 1 and 0, which is enough for IF and for arithmetic tricks, and that is the whole set.
Values you did not define
| Written | Is |
|---|---|
$, asmpc | The current program counter |
codesize | How much code has been assembled so far |
tstates | T-states accumulated since assembly started |
_page, _bank, _slot | Where the current address physically lives — see Memory and Banking |
⛔ The paging three need their underscore and the others must not have one. asmpc, codesize and tstates are ordinary names. Written without the underscore, page, bank and slot are read as labels of those names — so you get an undefined label rather than the paging value, and a label you really did call page is reached that way.
$ is what measures a block:
start:
ld a, 1
ret
here:
db here - start ; 3⛔ $ in a MODULE body is settled before the body is moved. A size computed from it can disagree with where the line ended up. W1073 reports that, naming both addresses.
Reaching names in other scopes
Inside a MODULE, a bare name is looked up in that module and nowhere else. Two forms reach out:
| Written | Looked up |
|---|---|
Gfx.One | Exactly as written, from anywhere |
@One | At global scope, ignoring every enclosing module |
@ means global, not “one scope out” — a nested module reaches its parent by qualified name, not by @. Forward references work in all three forms.
E1003 from inside a module usually means you want @name, and the diagnostic says so. See Modules for the whole picture, including nesting and --gc-modules.
What next
- Diagnostics — what the messages above mean, and the reserved-word list.
- Rhai Scripting — computing values instead of writing them.
- The Project File —
[defines], for constants that come from the build rather than the source.