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

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

A 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  BANNER

Using text where a number is wanted is E1075ld 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 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_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), 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

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

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:

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

Documentation