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.
Line Structure
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 Labels.
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 macro or a macro parameter, which is E1060 and E1061. A MODULE and a !library take any of them. 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
Syntax: <name> equ <expression> — <name> = <expression>
A constant binds name to the value of expression, once. The two spellings are identical in effect. A label is an address the assembler works out; a constant is a value you state.
WIDTH equ 32
HEIGHT = 24
AREA equ WIDTH * HEIGHTA 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 symbol `FFh` . Write 0FFh. This catches people out precisely because the diagnostic talks about a symbol when you were thinking about a number.
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) rather than a literal backslash, so a Windows path is written "C:\\tools". It 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. Tightest binding first — everything on one row binds equally and groups leftwards, so 1 - 2 - 3 is (1 - 2) - 3.
- + ! | unary sign, unary not |
* / | multiply, divide |
+ - | add, subtract |
<< >> | shift left, shift right |
< <= > >= | less than, at most, greater than, at least |
== != | equal to, not equal to |
& | bitwise and |
^ | bitwise exclusive or |
| | bitwise or |
&& | logical and, short-circuiting |
|| | logical or, short-circuiting |
( and ) group, overriding all of it. So 2 + 3 * 4 is 14 and (2 + 3) * 4 is 20. Where three of the bitwise operators meet, the rows above decide:
db mask & $0F ^ high | flag ; ((mask & $0F) ^ high) | flagEvery comparison yields 1 or 0, so it is a value like any other — db here == $8000 lays down one byte. The logical operators do too, whatever their operands were worth.
== is equality and = is an accepted alias, as is equ. = is also how a constant is assigned, which is why == is the one to write in an expression.
The same set serves an annotation. !assert and !debug take exactly these — see Debugging a Build. They did not: seven of them could be written in an annotation and nowhere else, so the same comparison was spelt two ways depending on where it sat.
bit is not available as a name there, being the BIT instruction, so a constant called bit is E1039 where it is defined. The reserved-word table is in Diagnostics.
Unary ! needs a bracket or a space — !(FOO) or ! FOO, never !FOO. A bang against a bare name is a misspelt directive, and the two cannot be told apart by shape.
&& does not excuse an undefined name. Short-circuiting decides which operand is evaluated; a condition naming a symbol the assembler does not have is refused before that, if being unable to wait for a name defined below it. Use ifdef to ask whether a name exists. Where short-circuiting shows is an operand that cannot be worked out at all — if 0 && (1 / 0) is false rather than a division by zero.
There is no modulo and no unary ~. Complement by exclusive-or with a mask of the width wanted, value ^ $FF for eight bits.
Built-In Values
| 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 |
_page(name), _bank(name), _slot(name), _window(name) | Where that name lives — see Memory and Banking |
sizeof(name) | The size of a struct, or of an included binary |
_line | The line number being assembled |
$ 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, so a size computed from it can disagree with where the line ended up. W1073 reports that, naming both addresses.
⚠ Four of them have no bare spelling; the other three take either. asmpc, codesize and tstates answer to _asmpc, _codesize and _tstates as well, so the underscore is optional there. _page, _bank, _slot and _line have only the underscored form: bank, page and slot are directive names, so a bare arm would answer for a label of that name, and line follows them. Written without it you get an undefined label rather than the value — and a label you really did call page is reached that way.
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 rather than “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.
Further Reading
- 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.