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 comment

Every 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    ret

Your 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
        ret

This 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 * HEIGHT

A constant may hold text, which db and dz assemble and SIZEOF measures:

BANNER  equ __NAME__ + " v" + __VERSION__
        dz  BANNER

Using 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

BaseForms
Hexadecimal0x12EF, $12EF, 12EFh (or H)
Binary%10101010, 0b10101010, 10101010b (or B)
Decimal1234
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_1010

Not 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:

WrittenProduces
\\a backslash, $5C
\"a double quote
\'an apostrophe
\nnewline, $0A
\rcarriage return, $0D
\ttab, $09
\0null, $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) | flag

Every 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

WrittenIs
$, asmpcThe current program counter
codesizeHow much code has been assembled so far
tstatesT-states accumulated since assembly started
_page, _bank, _slotWhere 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
_lineThe 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:

WrittenLooked up
Gfx.OneExactly as written, from anywhere
@OneAt 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.

← Documentation