# SjASMPlus 1.24.0 Documentation [2026-08-26]

## Introduction

### License

SjASMPlus is licensed under BSD license.

### What is it?

SjASMPlus is Z80 Assembly Language Cross Compiler. It is available for Win32, Linux and FreeBSD (mainly 5.x) systems. It is based on SjASM source code by Sjoerd Mastijn (http://xl2s.tk).

### Main Features

- Full source of assembler available under BSD license, modify and extend as you wish

- Z80/R800/Z80N/i8080/LR35902 documented and undocumented opcodes support

- Macro language, defines, array of defines

- Built-in Lua scripting engine

- Conditional assembly, block repeating

- Modules (namespaces), local and temporary labels

- Source and binary file inclusion, include paths

- Multi file output, file updating, various types of exports

- Structures to work easily with structured data in memory

- Relocation data generator to support SymbOS-like relocation of executables

- Virtual device mode for common machines: ZX 128, ZX Next, Amstrad CPC, … (pseudo op DEVICE)

- ZX Spectrum specific directives and pseudo ops (SAVESNA, SAVETAP, SAVEHOB, INCHOB, INCTRD…)

- ZX Spectrum Next specific features and directives (Z80N, 8ki memory paging, SAVENEX)

- Amstrad CPC 464/6128 specific directives (SAVECPCSNA)

- Correctness is assured by GitHub Actions CI with 512+ automated tests (that's also 512+ examples of usage!)

- Fake instructions as LD HL,DE (LD H,D:LD L,E) and more

- Code inlining through colon (LD A,C:INC A:PUSH AF:IFDEF FX:LD A,D:ENDIF…)

- Very fast compilation: 1 million lines by 2-3 seconds on modern computer

- Multiline block comments and user’s messages

### Credits

Special thanks to Sjoerd Mastijn, the author of SjASM.

Special thanks to Aprisobal, the author of early versions of sjasmplus.

Peter Helcmanovsky (Ped7g) - programming and maintenance since v1.11.0.

Thanks to:

- Kurles/HS/CPU, Alexander Kovalenko, Dart Alver, Oli Wilkinson, Laurent Carlier, Jone Jones, Pleumann, Ricardo Bittencourt - additional programming;

- Krystian Wlosek <kwlosek(at)gmail.com> - bug fix patches, Linux makefile;

- Ric Horne <Ric.Hohne@eads-ts.com> - bug fix patches.

- breeze <breeze@tut.by> - bug fix patches.

- psndcj <psndcj.tbk@gmail.com> - bug reporting, beta-testing.

- elfh <elphecy@gmail.com> - bug reporting.

- bugsy <bugsy@ya.ru> - bug reporting.

- skrju <sq-@mail.ru> - bug reporting.

- Tygrys, UB880D, Cizo, mborik, z00m, otis - compilation errors and warnings clean up, makefiles, testing.

- Antipod, boo_boo, PulkoMandy, Busy, Liniya, LVD, Janko-jj - bug fix patches, testing.

- CKirby - SLD export and support in his tools.

- Simon Brattel - for creating mother of all assemblers Zeus and bringing all the good and peace to the mankind, giving infinite inspiration to sjasmplus contributors who stole his tremendously amazing features without even crediting him - till now, fixed (not sure what the particular list of features was copied from Zeus, I guess DG/DH, I believe some other similarities are rather copied from the ALASM which had many features before Zeus, but who knows, too late to track it all precisely, what happened in 90's and when).

Big thanks to all people, who helped on development of the compiler!

### Feedback

WWW: https://github.com/z00m128/sjasmplus (newer versions maintained by z00m and others)

WWW: https://sourceforge.net/projects/sjasmplus/ (original Aprisobal's source)

E-Mail: zoom@centrum.sk, my@aprisobal.by

### What's new?

26.8.2026 - 1.24.0

      - Added named arguments emit syntax for macros (by Ricardo Bittencourt)
      - `MACRO` keyword itself is now disabling substitutions for remainder of line
      - New operator `pair` to put together two 8 bit values as 16 bit value
      - New operator `u16` to truncate value to low 16 bits
      - Added new example examples/sj_tutorial/tutorial.asm as quick guide for sjasmplus syntax
      - `SAVETRD` optional "autostart_BASIC_line" argument accepts -1 as OFF
      - Added special directive `#line` to fake reported source position

13.6.2026 - 1.23.1

      - `SETBP` optional "conditional" argument works also for MAME list type
      - CI: migrating from Cirrus CI (ending 1st of June 2026) to GitHub Actions
      - tiny changes to get rid of some lintian/spellcheck warnings (Debian packaging preparations)
      - docs: minor improvements

23.4.2026 - 1.23.0

      - Added `SIZEOF` operator for labels
      - Added `$$$label` and `$$$$label` operators for labels
      - refactoring device - page numbering, error values changed from -1 to 0x7F00+ (`get_page_at`)
      - small internal bug fixes (edge case with negative blocks, `_` glue, SLD export of EQU adjusted)
      - CI: binary reproducibility tested weekly

15.3.2026 - 1.22.0

      - Added substitution glue operator `_` to concatenate substitution results
      - Added string literals concatenating operator `..`
      - Added `DP` (alias `DEFP`) directive to define Pascal strings
      - `BLOCK` (`DS`) can take set of values for init (like DB)
      - `>` can be used to indent labels
      - Intel HEX output format added: `SAVEHEX`, `HEXOUT`, `HEXEND`, `--hex`
      - Added option `--cleanonerror` to remove binary outputs upon any error (as output may be corrupted)
      - `BPLIST` supports now also FUSE
      - `SETBP` has optional string argument for "conditional" breakpoints (depends on bplist type)
      - `DEVICE` ZX-like devices can now init with all-zeroed memory by using RAMTOP -1
      - CI: you can download executables from recent builds (windows, macos, freebsd, linux), for testing
      - refactored: relocation example has more size-optimized relocator code
      - docs: added new chapter Assembling Process
      - docs: minor fixes/extensions

16.1.2026 - 1.21.1

      - `BPLIST` supports now also MAME
      - `SAVENEX CLOSE` can append multiple files
      - numeric constants can separate/group digits also by underscore
      - JR and DJNZ added to "smart" SMC offset instructions: `abc+*: jr $`
      - `SLDOPT` extended with swapon/swapoff command to adjust SLD files
      - Lua: version upgrade to 5.5.0 (from 5.4.7)
      - Lua: minor extensions and modifications to API
      - refactored: temporary labels must match flow only in last pass (earlier passes can differ)
      - refactored: macros now handle large amount of macros defined better (like 64ki of macros)
      - fix invalid BASIC line length in `SAVETAP` loader
      - fix local labels inside macros in modules
      - fix local-label struct name usage
      - warnings: added -Wfileorg when ORG is used to jump addresses while file output is active
      - docs: minor fixes/extensions

15.3.2025 - 1.21.0

      - C++17 standard is now required minimum (to compile sjasmplus sources) (GCC9 for example)
      - `INCBIN`, `INCHOB` and `INCTRD` support now include paths priority (angle <filename> vs "filename")
      - also `SAVENEX` will respect include paths for source files (BMP, append)
      - include paths which can't be opened before assembling are now reported in error message
      - include paths which start with literal tilde '~' are reported in error message
      - include options `-i, -I, --inc` can take path from next CLI argument (new recommended syntax)
      - option `--fullpath` has now three states including full absolute paths
      - added Amstrad CPC device AMSTRADCPCPLUS - by Laurent Carlier
      - added Amstrad CPC+ `SAVECPR` to save CPR cartridge - by Laurent Carlier
      - ZX Next CSpect emulator v2.19.9.1: `break` opcode changed to `FD 00`
      - ZX Next CSpect emulator v3.0.1.5b: new opcodes `setbrk` and `clrbrk`
      - fix: windows cmd.exe console state after running sjasmplus.exe
      - Lua: fix sj.insert_label to apply current namespace to name
      - Lua: new sj.get_modules to get current modules namespace
      - Lua: minor version upgrade to 5.4.7 (from 5.4.6)
      - Lua: bundled Lua is now built and linked as C++ code (fixes memory leaks in edge cases)
      - minor bugfixes in specific edge cases
      - refactored lot of code related to file names and manipulation (to use C++17 filesystem library)

23.6.2023 - 1.20.3

      - added alias `--define` for `-D`
      - added string-literals suffixes Z and C to add zero or set high bit of last char
      - end of line backslash continues source line (limited support, not recommended)
      - Lua: minor version upgrade to 5.4.6 (from 5.4.4)
      - minor updates to Makefile

14.2.2023 - 1.20.2

      - added optional second argument for `DUP` to have index variable
      - option `--exp` will create file even when no `EXPORT` is used
      - fixing variable name-clash when compiling against musl-clib
      - LuaBridge updated, CMake and Makefile updated a bit
      - minor bugfixes/improvements in parser in specific edge cases

29.7.2022 - 1.20.1

      - parse decimal numeric constants with warning (for easier Lua 5.4 life)
      - added `SAVEAMSDOS` (like SAVEBIN with AMSDOS header)
      - added "smart" SMC offset syntax for self-modify-code labels: `abc+*: or 123`
      - added `DEFDEVICE` to define custom devices
      - Makefile cleanup

13.6.2022 - 1.20.0

      - Lua: upgrade to 5.4, replacing tolua++ bindings with LuaBridge2.6 library, extending some bindings
      - Lua: bindings slightly modified (required by upgrade), refreshed docs, added test coverage
      - Lua: the 3rd party extensions (BTW not working for many years) are obsolete in 5.4 and removed
      - Lua: more accurate errors/warning location reported even in complex cases
      - warnings: added -Wall, --help=warnings shows on/off status, rdlow off by default
      - Added HIGH mode to relocation data generator (MSB-only relocation mode)
      - many open-file "fatal" errors become "non-fatal", assembling will continue
      - deprecated features removed: --syntax=m, label `abs` in expressions
      - `--color=auto` will stay no-color when env.var. `NO_COLOR` is defined
      - refactorings, improving some error messages and parsing, small fixes in parsing logic
      - fix listing of Lua's sj.parse_code (eol-comments), minor memory leaks fixed
      - fix relocation of temporary labels in expressions
      - invalid CLI options are reported as regular errors (also changing exit code)
      - errors are colored similarly to gcc (only keyword has color), console input name is `<stdin>`

31.3.2022 - 1.19.0

      - added Amstrad CPC devices ("AMSTRADCPC464", "AMSTRADCPC6128") - by Oli Wilkinson
      - added Amstrad CPC save snapshot and CDT (`SAVECPCSNA`, `SAVECDT`) - by Oli Wilkinson
      - added `SAVE3DOS` (like SAVEBIN with +3DOS header)
      - the deprecated "ok" warning suppression is removed, use "<warning-id>-ok" comment or -Wno-...
      - new temporary label suffix syntax "_b" and "_f", enabling them for all expressions
      - fix `--longptr` mode to keep 32b address when `DS 0` is used
      - added fake instructions adc|add|sbc|sub de,bc|de|hl|sp
      - dec|inc|pop|push will accept also single-comma multiarg in --syntax=a mode
      - DUP/REPT will now accept also zero count (skipping the block)
      - DEFL labels can be defined even as late as in last pass
      - bugfixes (macros, listing, file names in errors, SLD reversepop data)

See CHANGELOG.md for full list of changes.

## Where to get and how to use

### Packages

The latest release of this sjasmplus variant is available at https://github.com/z00m128/sjasmplus/releases/latest - there is available zip archive with windows binary (toward bottom of the page), and zip archives with full source code of the sjasmplus.

Win32 package has:

- sjasmplus.exe - the Win32 executable.

- examples directory - some examples of use

- documentation directory - documentation in various formats

You may want to download also the full source package, as it contains more than 400 automated tests used to verify correctness of executable, and these can be often helpful to better understand how specific feature of sjasmplus works, and how to use it effectively.

Linux, Unix, MacOS and BSD version can be built from the full source archive. You can compile it using GCC and included Makefile. There is an option to use CMake for compilation. See INSTALL.md for details.

Windows binaries are compiled with MinGW environment and included Makefile.win file.

You can grab older (up to v1.07) binaries and sources at SourceForge project page: https://sourceforge.net/projects/sjasmplus/

### Command line

Usage:

    sjasmplus [options] sourcefile(s)

Option flags as follows:

      -h or --help[=warnings|=syntax]
      --version                Basic info (with --nologo only raw version string)
      --zxnext[=cspect]        Enable ZX Next Z80 extensions (CSpect emulator has
      extra "exit", "break", "clrbrk" and "setbrk" fake instructions)
      --i8080                  Limit valid instructions to i8080 only (+ no fakes)
      --lr35902                Sharp LR35902/SM83 CPU instructions mode (+ no fakes)
      --outprefix=<path>       Prefix for save/output/.. filenames in directives
      Note: if prefix is folder, it must already exist and add trailing slash. Prefix
            is applied only to filenames defined in source (not to CLI arguments).
      -i <path> or -I <path> or --inc= <path> (--inc only to empty the list)
                               Include path (later defined have higher priority)
      --lst[=<filename>]       Save listing to <filename> (<source>.lst is default)
      --lstlab[=sort]          Append [sorted] symbol table to listing
      --sym=<filename>         Save symbol table to <filename>
      --exp=<filename>         Save exports to <filename> (see EXPORT pseudo-op)
      --hex=<filename>         Intel HEX saved to <filename> (- is STDOUT)
      --raw=<filename>         Machine code saved also to <filename> (- is STDOUT)
      --sld[=<filename>]       Save Source Level Debugging data to <filename>
                               Default name is: "<first input filename>.sld.txt"
      --cleanonerror           Remove newly created binary outputs upon error
                               Keeps listings, symbol, export and similar files
      Note: use OUTPUT,LUA/ENDLUA and other pseudo-ops to control output
     Logging:
      --nologo                 Do not show startup message
      --msg=[all|war|err|none|lst|lstlab]  Stderr messages verbosity ("all" is default)
      Note: "lst" or "lstlab" will turn STDERR into listing file (this will clash with
      `--lst`, use only one of the options) and some diagnostic messages of "all"
      are omitted, as they are not part of listing files. "lstlab" does sort symbols.
      --fullpath[=on|rel|off]  Input file paths: full | CWD relative | name only (*)
      Note: --fullpath without value is "rel" (relative to current working directory)
      --color=[on|off|auto]    Enable or disable ANSI coloring of warnings/errors
      Note: auto detection checks for existence of environment variable TERM, and its
    		content is scanned for sub-string "color" (like "xterm-256color")
     Other:
      -D<NAME>[=<value>] or --define <NAME>[=<value>]
                               Define <NAME> as <value> (--define since v1.20.3)
      -W[no-]<warning_id>      Enable/disable warning(s), see "--help=warnings"
      -                        Reads STDIN as source (even in between regular files)
      --longptr                No device: program counter $ can go beyond 0x10000
      --reversepop             Enable reverse POP order (as in base SjASM version)
      --dirbol                 Enable directives processing from the beginning of line
      --nofakes                Disable fake instructions (same as --syntax=F)
      --dos866                 Encode from Windows codepage to DOS 866 (Cyrillic)
      --syntax=<...>           Adjust parsing syntax, read details below.

The option `--lstlab=sort` will cause also other symbol dumps being sorted (along listing): `--sym`, `CSPECTMAP` and `LABELSLIST`. The `--msg=lstlab` sets always sorting "on" (no "unsorted" option).

(since v1.21.0) Include path options `-i, -I, --inc=` now accept path after space, ie. `-I some/path/` - this is new and recommended syntax. If you get error "error: include path starts with ~ (check docs)", it means your shell did not substitute the ~ character with $HOME path, probably because you didn't put space between option and ~, so shell considers it to be single string not beginning with ~. Insert space between, like: `-I ~/include/path`

Value for `--syntax` option may consist of multiple letters, omitting letter for particular feature will use the default setting:

      a      Multi-argument delimiter ",," (default is ",") (but dec|inc|pop|push accept also ",")
      b      Whole expression parentheses are legal for memory access only (default = immediate or memory)
      B      memory access brackets [] required (default = relaxed syntax, [] allowed as extra)
      f F    Fake instructions: warning / disabled (default = enabled)
      i      Case insensitive instructions/directives (default = same case required)
    † l L    Keyword labels (registers, instructions, ...): warning / error (default = silent)
      M      Alias "m" and "M" for "(hl)" to cover 8080-like syntax: ADD A,M
      s      Use only "simple" whole-word substitutions, no sub-words
      w      Warnings option: report warnings as errors
      m      *REMOVED* in v1.20.0, use -Wno-rdlow

† work in progress: options "l" and "L" are not implemented yet, following example is then not working correctly either.

I.e. `--syntax=faBil` will modify parser to process source line

    hl:  Ld a,(hl),,de,hl

in a way to produce warnings about keyword "hl" being used for label, about fake instruction being used (ld de,hl) and assemble `(hl)` as numeric expression, not memory access. Warnings on fake instructions can be suppressed for particular line by adding any end-of-line comment containing string "fake", i.e. "`ld de,hl ; fake DE=HL`" will assemble without warning. The "F" option is identical to "--nofakes" and preferred.

The recommended setup for new projects is `--syntax=abfw` which makes syntax less relaxed, so some typos and mistakes are easier to catch, for example:

            OPT reset --syntax=abfw
    label:  dw 15
            ld b,(label)
            sub a,b

will produce "error: Illegal instruction (can't access memory): (label)" message for the `ld b,(label)` and the `sub a,b` will produce only the `sub b` instruction (to give the `sub` multi-argument with syntax option "a" the line would have to be `sub a,,b`).

The assembler will also read the environment variable `SJASMPLUSOPTS` (if available), and process its content as part of command line options (before the actual options), so you can pre-configure certain options in your environment, for example:

    export SJASMPLUSOPTS="--zxnext=cspect --msg=war"
    sjasmplus --lst --lstlab example.asm

will execute the assembling as if command line "`sjasmplus --zxnext=cspect --msg=war --lst --lstlab example.asm`" was used. Known issue: parser of environment variable delimits each option on any white-space character, so option containing space character will be incorrectly parsed (like "-I file-path with space" = fails and there is no way to escape/quote the path in the SJASMPLUSOPTS variable to make it work).

### Warnings with id

Some warnings have own "id" - these can be switched off or suppressed for particular line. The line suppression mechanism requires you to add end-of-line comment with "id-ok" string somewhere in the comment (exception: for fake instructions and --syntax=f mode the word "fake" is enough without -ok suffix) (the "ok" comment suppression mechanism from previous versions of sjasmplus is removed since v1.19.0).

The id-warning is emitted together with its id in brackets:

        ld a,(10-2)    ; read memory address 8 - emits warning "rdlow"
        ld a,(10-2)    ; same instruction, but suppress warning: rdlow-ok

    file.asm(1): warning[rdlow]: Reading memory at low address: 8

To list all warning-ids, their on/off state and short description of each use:

    <command line>$ sjasmplus --help=warnings

Most of the extra warnings are by default enabled, but to enable/disable all of them you can use (also in source with OPT directive):

    <command line>$ sjasmplus -Wall
    <command line>$ sjasmplus -Wno-all

To disable particular warning globally you can use option `-Wno-<id>`, either on command line, or from source by using `OPT`:

    <command line>$ sjasmplus -Wno-out0

    ; in source code:
        OPT -Wno-trdext -Wno-trdext3 ; disable "trdext" and "trdext3" warnings
            SAVETRD "image.trd", "file.txt", 30000, 100
        OPT -Wtrdext3                ; re-enable "trdext3" warning for following code
    ; ...

Warnings without id can't be suppressed and generally should happen only when the source code has actual bug (in our opinion) and can be modified to fix the warning, or when you are hitting limits of sjasmplus architecture and/or implementation.

If you believe you are hitting warning without id on correct code, which can be safely ignored (the machine code produced is stable and as expected), open issue on github with example source, so we can assign the warning new id (or explain why it is problematic :) ).

Warnings with id usually signal bug in the code (in common scenarios), but may be misleading in more specialized cases - the decision is left to the author, whether to modify the code to avoid the warning or suppress it and use the resulting machine code "as is".

### Source file format

Lines in the source file should have the following form:

    Label Operator Operand Comment

Labels must start at beginning of line (no whitespace). Lines without label must start with whitespace. All fields are optional. End of line backslash continues source line (since 1.20.3, limited support, not recommended). Operators and operands can be inlined with colon:

          Operator Operand:Operator Operand:Operator Operand... Comment

For some instructions operands can be inlined with comma (multi-argument) to produce same instruction with different operands (this is legacy default and it is strongly recommended to switch syntax to double-comma for multi-arg):

          Operator Operand1, Operand2, Operand3... Comment

Comments should start with '`;`' or '`//`'. Comment blocks start with '`/*`' and end with '`*/`' (work in "nested" way, i.e. comment block started inside comment block must be also ended, before main block ends).

    ; comment
    // comment
     ld /* comment */ a,80
    /*
     comment /* nested comment block */
    */
     ld /*
     but this won't be ld a,3!
     */ a,3                       ; errors on both lines

    label: ld e,c,d,b:inc hl,de   ; DE=BC, ++HL, ++DE
           ld de,bc               ; fake instruction (no warning)
           sub a,b                ; it's a trap! Does produce two instructions: sub a : sub b

           OPT reset --syntax=abf ; STRONGLY RECOMMENDED for new sources
           ; multi-arg double-comma, warn on fake instructions, warn on invalid memory parentheses
           ld e,c,,d,b:inc hl,de  ; some "obvious" instructions still support single-comma multi-arg
           sub a,b                ; `sub b` with relaxed syntax (to get two subs: `sub a,,b`)
           ld de,bc               ; warning, not a real instruction
           ld d,(12+3)            ; error, can't load D from memory
           ld d,+(12+3)           ; works, unary plus makes it clear it's just immediate value

(since v1.22.0) You can indent a label at the beginning of a line by using the indentation character '>'. The label must start immediately after the "greater-than" character (it works by offsetting the logical start of line):

    ClassicLineLabel:      nop
       >IndentedMainLabel: ld b,123    ; '>' must be first non-whitespace character on line
           >.loop:         djnz .loop

## Assembling Process

### The Three-Pass Model

sjasmplus processes the entire source code three times sequentially. Between passes, the symbols table is preserved (and Lua script global context is maintained), but all other data is discarded before the next pass begins.

Pass 1 and 2: No machine code is emitted. Instead, the assembler evaluates the size of code and drives symbol evaluation, updating the symbol table content. After Pass 2, all labels should have their final values.

Pass 3: Machine code is finally emitted. During this pass, the symbol table (computed in earlier passes) is used to resolve forward-referenced symbols correctly. If a symbol evaluates to a different value in Pass 3 compared to Pass 2, a warning or error is reported, indicating the source is too complex to settle within two passes.

### Per-Line Processing

Within each pass, source lines are processed sequentially. A single source line undergoes the following stages:

1. Substitution (defines, defarrays, macro arguments)

2. Label parsing

3. Directive or macro invocation parsing

4. Instruction parsing

Lines split by colons (`:`) are processed as multi-line source code, each segment having its own substitution and parsing process.

### Substitution and Defines

Substitution replaces identifiers in the source text with their defined values. The process is text-based and iterative: after each replacement, the line is re-scanned for further substitutions (up to ~30 iterations) until no more replacements occur. Only then does the assembler proceed to parse the resulting text.

Single iteration of substitution happens from left to right, with macro arguments taking precedence over other defines, followed by regular defines and defarray elements (with index expression evaluation). After keywords [`DEFINE+, DEFINE, UNDEFINE, DEFARRAY+, DEFARRAY, IFDEF, IFNDEF, MACRO`] only macro arguments are substituted, other defines are not substituted till end of line.

Sub-word matching: By default, each underscore (`_`) in an identifier is treated as a sub-word boundary. The substitution engine attempts various combinations of adjacent sub-words to match against defined keys. Defines and macro arguments starting with underscore (`_`) are restricted to substitution only at the beginning of an identifier, preventing accidental mid-word matches; for example, a define `_VERSION` will not match inside `MY_VERSION`. The order of matching is deterministic, but depends on the implementation; there is no explicit priority like from longest to shortest match. Use the `--syntax=s` option to disable sub-word matching to use only simple whole-word substitutions.

The glue operator (`_`): (since v1.22.0) When a whitespace-enclosed underscore appears adjacent to a substitution result, it signals that the whitespace should be removed after substitution is complete. This allows concatenating results of substitution into single string (sub-word matching leaves the boundary `_` as part of result, the glue operator is discarded).

### Macros and Expansion

Macro bodies are stored as unparsed chains of text lines and are not substituted or evaluated when defined. When a macro is invoked, its stored lines are injected into the source stream as if they were part of the original source at that location. During macro expansion, the macro's arguments become temporary defines, which participate in the substitution process for each injected line. After the macro is fully expanded, these temporary defines are discarded, and the substitution environment returns to its prior state.

### Comprehensive Example

The following example demonstrates substitution with iteration, the glue operator, macro argument substitution, and the multi-pass nature of symbol resolution:

docs_examples/s_assembling_process_example.asm

        ORG $4000
        DEFINE FULL part1? _ part2? _ .x    ; glue parts together with .x suffix
        MACRO  ExampleMacro part1?, part2?
            ld hl,FULL
        ENDM
        DEFINE part2? "global def"          ; can be overshadowed by macro argument
        ; can't be done ahead of MACRO definition in this case
        ; it would substitute macro argument name in definition with quoted string

        ExampleMacro forward, Label
        ; emits `ld hl,FULL` which is further processed by substitutions:
        ; -> `ld hl,part1? _ part2? _ .x` define FULL applied
        ; -> `ld hl,forward _ Label _ .x` args applied, `_` becomes glue operators
        ; -> `ld hl,forwardLabel.x` final line to be parsed
        ; In pass 1 this evaluates to `ld hl,0`, forwardLabel.x is not known yet
        ; In pass 2 and 3 the symbol `forwardLabel.x` has value $4003

    forwardLabel.x:         ; label to be forward-referenced from within macro
        DB part2?           ; "global def" string emitted here (not `Label`)

## Labels

Labels are case-sensitive and may be of any reasonable length, that is: up to about 70 characters. Label definitions must start on the beginning of a line (since v1.22.0 you can indent them by leading ``>``), but don't have to be followed by a colon ':'. Generally labels should start with a letter or a underscore ('_'), the following characters may be chosen from letters, numbers and the following special symbols: '_', '.', '!', '?', '#' and '@'. Note that the '.' has special meaning, as it is used between module names, labels and local labels. The following are all legal and distinct labels:

    Kip
    KIP
    Kip@@
    MAIN.loop?

It is possible to use mnemonics, pseudo-ops and register names as labels but it is not advised to do so (expression operator keyword used as label will emit warning, and is also not advised). Also note that the identifiers defined with the DEFINE pseudo-op use another name space.

Right after label (before the optional colon) you can write extra "SMC offset" (Self Modify Code offset) in the form of plus sign and single decimal digit. The defined label will become equal to current address plus the SMC offset:

        ; SMC-offset syntax is very strict, no spacing, single '+', single digit
    answer+1:   ld      a,13        ; "answer" points to second opcode byte
        ; this helps to keep the SMC-label together with the target instruction
                ld      hl,answer
                ld      (hl),42     ; fix immediate in `ld a,*` to correct answer
        ; compare with classic syntax:
    classic equ $+1 : ld a,7        ; often even split to two lines

(since v1.20.1) Smart SMC labels: you can write "plus asterisk" to let sjasmplus adjust self-modify label by most likely offset for following instruction. This works only for instructions with "obvious" target immediate value:

        ; smart-SMC syntax is very strict, no spacing, single '+', single '*'
    answer+*:   ld      a,13        ; "answer" points to second opcode byte
        ; this helps to keep the SMC-label together with the target instruction
                ld      hl,answer
                ld      (hl),42     ; fix immediate in `ld a,*` to correct answer
        ; few more examples which byte the "smart SMC" will target in the opcode
    jptarget+*: jp      nz,12345    ; "jptarget" points at second opcode byte (word value 12345)
    ixvalue+*:  ld      ix,12345    ; "ixvalue" points at third opcode byte (word value 12345)
    nxtrval+*:  nextreg $44,123     ; "nxtrval" points at fourth opcode byte (byte value 123)
        ; unsupported situations, will cause error
    invalid1+*: inc     (ix+123)    ; IXY displacements are not considered (not significant enough)
    invalid2+*: cpl : ld a,14       ; only first instruction after label is considered

### Local labels

When there is a module definition all labels (except those starting with a '@') are local to that module. To use a label from outside the module use modulename.labelname, in this example: 'vdp.Cls'. Labels starting with a '.' are local to the previous non-local label.

If you start the label with an ! (exclamation mark), it will not affect following local labels (same syntax as sjasm).

Local label inside macro prefixed with @ (at sign) acts as regular local label, otherwise (older behaviour) the local label is local to each macro's instance.

docs_examples/s_local_labels.asm

        MODULE main             ; module "main"
    Main:                       ; main.Main
            CALL SetScreen      ; SetScreen
            CALL vdp.Cls        ; main.vdp.Cls
    .loop:                      ; main.Main.loop
            LD A,(.event)       ; main.Main.event
            CALL ProcessEvent   ; label not found: main.ProcessEvent
            DJNZ .loop          ; main.Main.loop

            MODULE vdp          ; module "main.vdp"
    @SetScreen:                 ; SetScreen
    .loop:                      ; SetScreen.loop (since v1.21.0)
                RET
    Cls:                        ; main.vdp.Cls
    !KeepClsForLocal:           ; main.vdp.KeepClsForLocal
    .loop:      DJNZ .loop      ; main.vdp.Cls.loop
                RET
            ENDMODULE

    Main.event DB 0             ; main.Main.event
        ENDMODULE

local labels inside macro

      macro test
    .kip0       ; this is local to every instance of macro
    @.kip1      ; syntax added in v1.18.3
      endm

      module main
    hoi test    ; produce labels: main.hoi, 0>kip0, main.hoi.kip1
      endmodule

### @ Labels

Labels starting with a '@' are not touched by the label processing and used 'as-is'. See 'SetScreen' in the previous example code.

docs_examples/s_at_labels.asm

        MODULE xxx
    Label      ; xxx.Label
    .Local     ; xxx.Label.Local
    @Label     ; Label
    .Local     ; Label.Local
    @yyy.Local ; yyy.Local
    yyy.Local  ; xxx.yyy.Local

### Temporary labels

To keep the number of used labels reasonable it is possible to use numbers as labels. To target these labels in all expressions use the number followed by an '_F' for forward search or a '_B' for backward search. Temporary labels must be defined in the same order during last passes of assembling (can reach across macro or repeating blocks).

Before v1.19.0 the syntax was using only letters 'B' and 'F' and was enabled only for branching instructions (the old syntax still works for those, but only for those).

docs_examples/s_temp_labels.asm

            ADD A,E
            JR NC,1_F   ; old syntax "1F"
            INC D
    1       LD E,A
    2       LD B,4
            LD A,(DE)
            OUT (152),A
            DJNZ 2_B    ; old syntax "2B"

            MACRO zing
                DUP 2
                    JR    1_F
    1               DJNZ  1_B
                EDUP
            ENDM

            .4 zing

            ; since v1.19.0
            ld hl,1B        ; *old* HL = binary 1, NOT temporary label
            ld hl,1_B       ; *new* HL = previous temporary label 1
            ld hl,1_B*3     ; *new* works also in expressions

### SIZEOF labels (operator)

(since v1.23.0) Operator SIZEOF returns "size" of the label. This is usually a distance between two labels of the same or greater "level". For example from main label to main label or from local label to local/main label. Nested labels (inside INCLUDE or MACRO) are "invisible" to the operator, ie. it counts on source-level where the label is defined. Some situations may abruptly stop all counting, like ORG directive (when nested, it will warn about parent-scope labels being interrupted, but on "current level" it just stops the count, as programmer can visually see the ORG as natural boundary for previous label).

The size-counting can be also stopped by boundary tags `::` as main-label level and `:.:` as local-label level. These also split line as regular colon for inlining more instructions on single line and can't be at beginning of line, even with `--dirbol`. And size-counting does not follow across MODULE boundaries, which stops counting (even nested in includes/macros, then it stops with warning). For more exhaustive list of all intricacies check test sizeof_label.asm.

::
:.:

For structure members SIZEOF returns zero at this moment (feels like returning member size makes some sense, but wasn't implemented). For the main name of the structure the SIZEOF returns size of structure, this works also for any instance of structure. For EQU and DEFL labels the SIZEOF returns zero size and will warn about type of label. But EQU/DEFL labels still do count as distance boundary. Temporary labels are neither counted as boundary, nor can be SIZEOF counted, they are invisible to SIZEOF feature.

The distance between labels and/or boundaries is counted over memory where machine code is emitted, inside DISP block the displacement of addresses is ignored, ie. you can SIZEOF also that DISP block you plan to move at runtime elsewhere or count across the DISP entry/exit.

basic SIZEOF usage

    ; example 1
    CHKCHARS:       ldir
                    ret
    .charlist1:     db  "ABCD"
    .charlist2:     db  "EFG"
    .charlist3:     db  "HIJKLMNO"
    BAR:            ; stops counting of main label CHKCHARS
        ASSERT 3 + 4 + 3 + 8 == SIZEOF(CHKCHARS)    ; code + three strings
        ASSERT 4 == SIZEOF(CHKCHARS.charlist1)
        ASSERT 3 == SIZEOF(CHKCHARS.charlist2)
        ASSERT 8 == SIZEOF(CHKCHARS.charlist3)

    ; example 2
    L2:                 ; SIZEOF(L2) is 16 (10 + 1 + 5)
    .locL1: ds  10 :.:  ; SIZEOF(L2.locL1) is 10, tag `:.:` to end local-counting
            db  0       ; counts to main label "L2"
    .locL2: ds  5 ::    ; SIZEOF(L2.locL2) is 5
            db  0       ; does not count to any label after `::` tag

    ; example 3
            MACRO MyMacro
    @NewGlobalLabel:    ; nested main label inside macro
              xor a
            ENDM        ; SIZEOF(NewGlobalLabel) is 1, ENDM is nesting-boundary ending its count
    MyRoutine:
            MyMacro     ; +1 ; does *NOT* end counting of parent-scoped MyRoutine
            rld ::      ; +2
            ASSERT 3 == SIZEOF(MyRoutine)

## Constants, expressions and other features

### Numeric constants

Numeric constants should always start with a digit or $, # or %. The following formats are supported:

    12     decimal
    12d    decimal
    0xc    hexadecimal
    $c     hexadecimal
    #c     hexadecimal
    0ch    hexadecimal
    0b1100 binary
    %1100  binary
    1100b  binary
    0q14   octal
    14q    octal
    14o    octal

    12'345 digit separator ' (same as C++)
    1_3_7q digit separator _ (common in other languages/tools)

Optional single quotes (') or underscore (_) may be inserted between the digits as a separator (example: `ld a,%11'01'11'00 : ld e,$40_DD` ). They are ignored by the assembler.

(v1.20.1) decimal parts are now parsed and ignored (with optional warning), instead of causing syntax error - this should make life easier when formatting values from Lua script. It is also possible to fix all Lua scripts to produce only integer values, but if you prefer to silently ignore the extra decimal part, you can.

### Character and string constants

Character constants are characters surrounded by single quotes. It is possible to use double quotes in some cases, but in general it is better to use single quotes. String constants are characters surrounded by double quotes. When double quotes are used, the following escape sequences are recognized:

    \\ 92
    \? 63
    \' 39
    \" 34
    \0 0
    \A 7
    \B 8
    \D 127
    \E 27
    \F 12
    \N 10
    \R 13
    \T 9
    \V 11

Inside single quotes two quotes after each other are parsed as the apostrophe itself.

Adding Z or C after quotes/apostrophes will make string zero-terminated or set top bit of last character (since v1.20.3).

String literals can be concatenated using the ".." operator (similar to Lua syntax). Both operands must be string literals (since v1.22.0).

        BYTE "stringconstant\n" ; escape sequence assembles to newline
        BYTE 'stringconstant\n' ; \n assembles literally as two bytes: '\', 'n'
        LD HL,'hl'  ; hl = 0x686C = 'l', 'h'
        LD HL,"hl"  ; hl = 0x686C = 'l', 'h'
        LD A,"7"    ; not recommended (but works)
        LD A,'8'    ; recommended
        LD A,'\E'   ; warning + truncating value to 'E' (0x45)
        LD A,'"'    ; A = 0x22
        LD A,"'"    ; A = 0x27
        LD A,''''   ; A = 0x27
        BYTE "AB"Z, "CD"C, 'E'Z, 'F'C ; hex: 41 42 00 43 C4 45 00 C6
        DEFINE BIN_EXT ".bin"
        INCBIN "path" .. '/' .. "name" .. BIN_EXT  ; includes "path/name.bin" file

### Expressions

Expressions are evaluated in signed 32 bits in this version of SjASMPlus (unless explicitly specified as unsigned, like ">>>" operator), so intermediate values have range -2147483648 to +2147483647. True for boolean results is equal to value -1, false is equal to 0 (so result of compare or logical operators can be used as all-bit-mask further).

'$' represents the current program counter. '$$' represents the current page in the current slot in the real device emulation mode, '$$label' evaluates to number of page where the "label" was defined (only regular labels have meaningful value, labels defined under DISP mode may return the page specified in DISP itself, EQU/DEFL/... will produce mostly irrelevant values). '$$$' and '$$$$' can be used inside DISP block to retrieve the "physical" memory address and page (where the displaced machine code is written to). '$$$label' and '$$$$label' can be used to read "physical" address and page of any label (for non-regular labels like EQU, DEFL or SMC-offset label the "physical" address is address where the label was defined (see get_label_physical_memory_operators.asm test for edge cases and details). '{address}' can be used to read WORD from virtual device memory (correct value is read only in last pass of assembling, in early passes the zero value is always returned), '{b address}' reads only BYTE.

It is possible to use parenthesis '(' and ')' to override the precedence of the operators. The following operators may be used in expressions:

    norel  norel L   Label/symbol L will be treated as non-relocatable
    exist  exist L   is label/symbol L defined in source (even when not used)
    sizeof sizeof L  size of label/symbol L, details here (since v1.23.0)
    ..     x..y      string concatenation (both operands must be string literals) (since v1.22.0)
    pair(hi,lo)      binary concatenate two 8 bit values into 16 bit value (since v1.24.0)

    !      !x        logical not
    ~      ~x        complement
    +      +x        does "nothing", can be used to make "+(...)" parse as value (not as memory)
    -      -x        minus
    low    low x     low 8 bits of value or lower part of register pair
    high   high x    high 8 bits of 16 bit value or higher part of register pair
    u16    u16 x     low 16 bits of value (since v1.24.0)
    not    not x     logical not
    abs    abs x     absolute value

    *      x*y       multiplication
    /      x/y       division
    %      x%y       modulo
    mod    x mod y   modulo

    +      x+y       addition
    -      x-y       subtraction

    <<     x<<y      shift left
    >>     x>>y      shift right signed
    >>>    x>>>y     shift right unsigned
    shl    x shl y   shift left
    shr    x shr y   shift right signed

    <?     x<?y      minimum
    >?     x>?y      maximum

    <      x<y       less than
    >      x>y       greater than
    <=     x<=y      equal or less than
    >=     x>=y      equal or greater than

    =      x=y       equal
    ==     x==y      equal
    !=     x!=y      not equal

    &      x&y       bitwise and
    and    x and y   bitwise and

    ^      x^y       bitwise xor
    xor    x xor y   bitwise xor

    |      x|y       bitwise or
    or     x or y    bitwise or

    &&     x&&y      logical and

    ||     x||y      logical or

    $      $         current program counter
    $$     $$        current page at program counter (in virtual device mode)
    $$$    $$$       "physical" program counter (inside DISP block)
    $$$$   $$$$      "physical" memory page (inside DISP block in virtual device mode)
    label  label     value of label (aka symbol), usually memory address
    $$lab  $$lab     page of "lab" label (in virtual device mode)
    $$$lab $$$lab    "physical" address of "lab" label
    $$$$lb $$$$lb    "physical" memory page of "lb" label
    {}     {x}       reads WORD from address x (in virtual device mode, in last pass)
    {b}    {b x}     reads BYTE from address x (in virtual device mode, in last pass)

norel
exist
sizeof
..
pair
!
~
low
high
u16
not
abs
*
/
%
mod
+
-
<<
>>
>>>
shl
shr
<?
>?
<
>
<=
>=
=
==
!=
&
and
^
xor
|
or
&&
||
$
$$
$$$
$$$$
$$label
$$$label
$$$$label
{..}
{b ..}

### Assembly language

This version only accepts Z80 mnemonics. There are some additions to what I think is standard Z80:

- '[' and ']' can be used instead of '(' and ')' for indirection. So `LD A,[HL]` is the same as `LD A,(HL)` (does not apply to IN/OUT ports, those must use '(...)' form)

- `IN F,(C)` and `OUT (C),0` and `SLL/SLI` can be used (warning: on some CPU versions of Z80 the OUT (C),0 is working as OUT (C),255).

- IXL (or LX, XL), IYL (or LY, YL), IXH (or HX, XH) and IYH (or HY, YH) registers are supported.

- Can write code throught colon: ORG 100h:LD A,10:LD B,10:SUB B:RET:IFDEF AA:.....

- JP HL, JP IX and JP IY may be used instead of JP (HL), etc.

- EX AF,AF or EX AF or EXA may be used instead of EX AF,AF'.

- R800's MULUB and MULUW are recognised (but won't work on Z80, of course:)

- Z80N, i8080 and LR35902 (SM83) modes use the identical Z80 sjasmplus syntax (!), for the correct (and incorrect) syntax examples of extended opcodes, please check the test files: Z80N test 1 test 2 and LR35902 (SM83) test 1 test 2 (also for the Z80 syntax examples you can check Z80 tests).

- RLC, RRC, RL, RR, SLA, SRA, SLL (SLI), RES, SET undocumented instructions added.

        SET 4,(IX+4),C ; (aka LD C,SET 4,(IX+4)) is LD C,(IX+4) / SET 4,C / LD (IX+4),C
        RRC (IY),A     ; (aka LD A,RRC (IY+0))   is LD A,(IY)   / RRC A   / LD (IY),A

- PUSH and POP can take register lists:

        PUSH AF,BC  ; push af / push bc
        POP  AF,BC  ; pop  af / pop  bc

- and many other instructions support this "multi-argument" syntax:

        LD A,B,B,D,D,H
       /* this is:
         LD A,B
         LD B,D
         LD D,H
       */
       ;or you can write  LD A,B:LD B,D:LD D,H

       ; it is possible and *recommended* to change the multi-arg delimiter
       ; into ",,", to avoid some ambiguities with certain instructions.
       OPT --syntax=a
       LD A,B,,B,D,,D,H ; same as example above in default syntax
       SUB A,B,,C  ; = SUB B, SUB C (the default syntax does for SUB A,B two SUBs!)

       ; since v1.19.0 DEC|INC|POP|PUSH accepts also single-comma in --syntax=a mode
       POP bc,de,hl,,af ; both single/double comma works for these four instructions

### Fake instructions

Of course the Z80 is only an 8 bit cpu, but sometimes `ld hl,de` would be nice. SjASMPlus now 'fakes' some instructions like that. This improves the readability of the source, but it might not be the fastest way to get the result. Also possibly some 'new' load instructions do affect the flags in ways you wouldn't expect. You can use option `--syntax=f` to get warnings when fake instruction is used, to avoid using them by accident. Here's the list:

      rl bc           ; rl c : rl b
      rl de           ; rl e : rl d
      rl hl           ; rl l : rl h
      rr bc           ; rr b : rr c
      rr de           ; rr d : rr e
      rr hl           ; rr h : rr l
      sla bc          ; sla c : rl b
      sla de          ; sla e : rl d
      sla hl          ; add hl,hl
      sll bc          ; sli c : rl b
      sll de          ; sli e : rl d
      sll hl          ; sli l : rl h
      sli bc          ; sli c : rl b
      sli de          ; sli e : rl d
      sli hl          ; sli l : rl h
      sra bc          ; sra b : rr c
      sra de          ; sra d : rr e
      sra hl          ; sra h : rr l
      srl bc          ; srl b : rr c
      srl de          ; srl d : rr e
      srl hl          ; srl h : rr l

      ld bc,bc        ; ld b,b : ld c,c
      ld bc,de        ; ld b,d : ld c,e
      ld bc,hl        ; ld b,h : ld c,l
      ld bc,ix        ; ld b,xh : ld c,xl
      ld bc,iy        ; ld b,yh : ld c,yl
      ld bc,(hl)      ; ld c,(hl) : inc hl : ld b,(hl) : dec hl
      ld bc,(ix+nn)   ; ld c,(ix+nn) : ld b,(ix+nn+1)
      ld bc,(iy+nn)   ; ld c,(iy+nn) : ld b,(iy+nn+1)

      ld de,bc        ; ld d,b : ld e,c
      ld de,de        ; ld d,d : ld e,e
      ld de,hl        ; ld d,h : ld e,l
      ld de,ix        ; ld d,xh : ld e,xl
      ld de,iy        ; ld d,yh : ld e,yl
      ld de,(hl)      ; ld e,(hl) : inc hl : ld d,(hl) : dec hl
      ld de,(ix+nn)   ; ld e,(ix+nn) : ld d,(ix+nn+1)
      ld de,(iy+nn)   ; ld e,(iy+nn) : ld d,(iy+nn+1)

      ld hl,bc        ; ld h,b : ld l,c
      ld hl,de        ; ld h,d : ld l,e
      ld hl,hl        ; ld h,h : ld l,l
      ld hl,ix        ; push ix : pop hl
      ld hl,iy        ; push iy : pop hl
      ld hl,(ix+nn)   ; ld l,(ix+nn) : ld h,(ix+nn+1)
      ld hl,(iy+nn)   ; ld l,(iy+nn) : ld h,(iy+nn+1)

      ld ix,bc        ; ld xh,b : ld xl,c
      ld ix,de        ; ld xh,d : ld xl,e
      ld ix,hl        ; push hl : pop ix
      ld ix,ix        ; ld xh,xh : ld xl,xl
      ld ix,iy        ; push iy : pop ix

      ld iy,bc        ; ld yh,b : ld yl,c
      ld iy,de        ; ld yh,d : ld yl,e
      ld iy,hl        ; push hl : pop iy
      ld iy,ix        ; push ix : pop iy
      ld iy,iy        ; ld yh,yh : ld yl,yl

      ld (hl),bc      ; ld (hl),c : inc hl : ld (hl),b : dec hl
      ld (hl),de      ; ld (hl),e : inc hl : ld (hl),d : dec hl

      ld (ix+nn),bc   ; ld (ix+nn),c : ld (ix+nn+1),b
      ld (ix+nn),de   ; ld (ix+nn),e : ld (ix+nn+1),d
      ld (ix+nn),hl   ; ld (ix+nn),l : ld (ix+nn+1),h

      ld (iy+nn),bc   ; ld (iy+nn),c : ld (iy+nn+1),b
      ld (iy+nn),de   ; ld (iy+nn),e : ld (iy+nn+1),d
      ld (iy+nn),hl   ; ld (iy+nn),l : ld (iy+nn+1),h

      ldi bc,(hl)     ; ld c,(hl) : inc hl : ld b,(hl) : inc hl
      ldi bc,(ix+nn)  ; ld c,(ix+nn) : inc ix : ld b,(ix+nn) : inc ix
      ldi bc,(iy+nn)  ; ld c,(iy+nn) : inc iy : ld b,(iy+nn) : inc iy

      ldi de,(hl)     ; ld e,(hl) : inc hl : ld d,(hl) : inc hl
      ldi de,(ix+nn)  ; ld e,(ix+nn) : inc ix : ld d,(ix+nn) : inc ix
      ldi de,(iy+nn)  ; ld e,(iy+nn) : inc iy : ld d,(iy+nn) : inc iy

      ldi hl,(ix+nn)  ; ld l,(ix+nn) : inc ix : ld h,(ix+nn) : inc ix
      ldi hl,(iy+nn)  ; ld l,(iy+nn) : inc iy : ld h,(iy+nn) : inc iy

      ldi (hl),bc     ; ld (hl),c : inc hl : ld (hl),b : inc hl
      ldi (hl),de     ; ld (hl),e : inc hl : ld (hl),d : inc hl

      ldi (ix+nn),bc  ; ld (ix+nn),c : inc ix : ld (ix+nn),b : inc ix
      ldi (ix+nn),de  ; ld (ix+nn),e : inc ix : ld (ix+nn),d : inc ix
      ldi (ix+nn),hl  ; ld (ix+nn),l : inc ix : ld (ix+nn),h : inc ix

      ldi (iy+nn),bc  ; ld (iy+nn),c : inc iy : ld (iy+nn),b : inc iy
      ldi (iy+nn),de  ; ld (iy+nn),e : inc iy : ld (iy+nn),d : inc iy
      ldi (iy+nn),hl  ; ld (iy+nn),l : inc iy : ld (iy+nn),h : inc iy

      ldi a,(bc)      ; ld a,(bc) : inc bc
      ldi a,(de)      ; ld a,(de) : inc de
      ldi a,(hl)      ; ld a,(hl) : inc hl
      ldi b,(hl)      ; ld b,(hl) : inc hl
      ldi c,(hl)      ; ld c,(hl) : inc hl
      ldi d,(hl)      ; ld d,(hl) : inc hl
      ldi e,(hl)      ; ld e,(hl) : inc hl
      ldi h,(hl)      ; ld h,(hl) : inc hl
      ldi l,(hl)      ; ld l,(hl) : inc hl
      ldi a,(ix+nn)   ; ld a,(ix+nn) : inc ix
      ldi b,(ix+nn)   ; ld b,(ix+nn) : inc ix
      ldi c,(ix+nn)   ; ld c,(ix+nn) : inc ix
      ldi d,(ix+nn)   ; ld d,(ix+nn) : inc ix
      ldi e,(ix+nn)   ; ld e,(ix+nn) : inc ix
      ldi h,(ix+nn)   ; ld h,(ix+nn) : inc ix
      ldi l,(ix+nn)   ; ld l,(ix+nn) : inc ix
      ldi a,(iy+nn)   ; ld a,(iy+nn) : inc iy
      ldi b,(iy+nn)   ; ld b,(iy+nn) : inc iy
      ldi c,(iy+nn)   ; ld c,(iy+nn) : inc iy
      ldi d,(iy+nn)   ; ld d,(iy+nn) : inc iy
      ldi e,(iy+nn)   ; ld e,(iy+nn) : inc iy
      ldi h,(iy+nn)   ; ld h,(iy+nn) : inc iy
      ldi l,(iy+nn)   ; ld l,(iy+nn) : inc iy

      ldd a,(bc)      ; ld a,(bc) : dec bc
      ldd a,(de)      ; ld a,(de) : dec de
      ldd a,(hl)      ; ld a,(hl) : dec hl
      ldd b,(hl)      ; ld b,(hl) : dec hl
      ldd c,(hl)      ; ld c,(hl) : dec hl
      ldd d,(hl)      ; ld d,(hl) : dec hl
      ldd e,(hl)      ; ld e,(hl) : dec hl
      ldd h,(hl)      ; ld h,(hl) : dec hl
      ldd l,(hl)      ; ld l,(hl) : dec hl
      ldd a,(ix+nn)   ; ld a,(ix+nn) : dec ix
      ldd b,(ix+nn)   ; ld b,(ix+nn) : dec ix
      ldd c,(ix+nn)   ; ld c,(ix+nn) : dec ix
      ldd d,(ix+nn)   ; ld d,(ix+nn) : dec ix
      ldd e,(ix+nn)   ; ld e,(ix+nn) : dec ix
      ldd h,(ix+nn)   ; ld h,(ix+nn) : dec ix
      ldd l,(ix+nn)   ; ld l,(ix+nn) : dec ix
      ldd a,(iy+nn)   ; ld a,(iy+nn) : dec iy
      ldd b,(iy+nn)   ; ld b,(iy+nn) : dec iy
      ldd c,(iy+nn)   ; ld c,(iy+nn) : dec iy
      ldd d,(iy+nn)   ; ld d,(iy+nn) : dec iy
      ldd e,(iy+nn)   ; ld e,(iy+nn) : dec iy
      ldd h,(iy+nn)   ; ld h,(iy+nn) : dec iy
      ldd l,(iy+nn)   ; ld l,(iy+nn) : dec iy

      ldi (bc),a      ; ld (bc),a : inc bc
      ldi (de),a      ; ld (de),a : inc de
      ldi (hl),a      ; ld (hl),a : inc hl
      ldi (hl),b      ; ld (hl),b : inc hl
      ldi (hl),c      ; ld (hl),c : inc hl
      ldi (hl),d      ; ld (hl),d : inc hl
      ldi (hl),e      ; ld (hl),e : inc hl
      ldi (hl),h      ; ld (hl),h : inc hl
      ldi (hl),l      ; ld (hl),l : inc hl
      ldi (ix+nn),a   ; ld (ix+nn),a : inc ix
      ldi (ix+nn),b   ; ld (ix+nn),b : inc ix
      ldi (ix+nn),c   ; ld (ix+nn),c : inc ix
      ldi (ix+nn),d   ; ld (ix+nn),d : inc ix
      ldi (ix+nn),e   ; ld (ix+nn),e : inc ix
      ldi (ix+nn),h   ; ld (ix+nn),h : inc ix
      ldi (ix+nn),l   ; ld (ix+nn),l : inc ix
      ldi (iy+nn),a   ; ld (iy+nn),a : inc iy
      ldi (iy+nn),b   ; ld (iy+nn),b : inc iy
      ldi (iy+nn),c   ; ld (iy+nn),c : inc iy
      ldi (iy+nn),d   ; ld (iy+nn),d : inc iy
      ldi (iy+nn),e   ; ld (iy+nn),e : inc iy
      ldi (iy+nn),h   ; ld (iy+nn),h : inc iy
      ldi (iy+nn),l   ; ld (iy+nn),l : inc iy

      ldd (bc),a      ; ld (bc),a : dec bc
      ldd (de),a      ; ld (de),a : dec de
      ldd (hl),a      ; ld (hl),a : dec hl
      ldd (hl),b      ; ld (hl),b : dec hl
      ldd (hl),c      ; ld (hl),c : dec hl
      ldd (hl),d      ; ld (hl),d : dec hl
      ldd (hl),e      ; ld (hl),e : dec hl
      ldd (hl),h      ; ld (hl),h : dec hl
      ldd (hl),l      ; ld (hl),l : dec hl
      ldd (ix+nn),a   ; ld (ix+nn),a : dec ix
      ldd (ix+nn),b   ; ld (ix+nn),b : dec ix
      ldd (ix+nn),c   ; ld (ix+nn),c : dec ix
      ldd (ix+nn),d   ; ld (ix+nn),d : dec ix
      ldd (ix+nn),e   ; ld (ix+nn),e : dec ix
      ldd (ix+nn),h   ; ld (ix+nn),h : dec ix
      ldd (ix+nn),l   ; ld (ix+nn),l : dec ix
      ldd (iy+nn),a   ; ld (iy+nn),a : dec iy
      ldd (iy+nn),b   ; ld (iy+nn),b : dec iy
      ldd (iy+nn),c   ; ld (iy+nn),c : dec iy
      ldd (iy+nn),d   ; ld (iy+nn),d : dec iy
      ldd (iy+nn),e   ; ld (iy+nn),e : dec iy
      ldd (iy+nn),h   ; ld (iy+nn),h : dec iy
      ldd (iy+nn),l   ; ld (iy+nn),l : dec iy

      ldi (hl),mm     ; ld (hl),mm : inc hl
      ldi (ix+nn),mm  ; ld (ix+nn),mm : inc ix
      ldi (iy+nn),mm  ; ld (iy+nn),mm : inc iy

      ldd (hl),mm     ; ld (hl),mm : dec hl
      ldd (ix+nn),mm  ; ld (ix+nn),mm : dec ix
      ldd (iy+nn),mm  ; ld (iy+nn),mm : dec iy

      adc de,bc       ; ex de,hl : adc hl,bc : ex de,hl
      adc de,de       ; ex de,hl : adc hl,hl : ex de,hl         ;; consider alternative: rl de (-7T)
      adc de,hl       ; ex de,hl : adc hl,de : ex de,hl
      adc de,sp       ; ex de,hl : adc hl,sp : ex de,hl

      add de,bc       ; ex de,hl : add hl,bc : ex de,hl
      add de,de       ; ex de,hl : add hl,hl : ex de,hl         ;; consider alternative: sla de (-3T)
      add de,hl       ; ex de,hl : add hl,de : ex de,hl
      add de,sp       ; ex de,hl : add hl,sp : ex de,hl

      sbc de,bc       ; ex de,hl : sbc hl,bc : ex de,hl
      sbc de,de       ; ex de,hl : sbc hl,hl : ex de,hl
      sbc de,hl       ; ex de,hl : sbc hl,de : ex de,hl
      sbc de,sp       ; ex de,hl : sbc hl,sp : ex de,hl

      sub de,bc       ; or a : ex de,hl : sbc hl,bc : ex de,hl
      sub de,de       ; or a : ex de,hl : sbc hl,hl : ex de,hl  ;; consider alternative: ld de,0 (-17T)
      sub de,hl       ; or a : ex de,hl : sbc hl,de : ex de,hl
      sub de,sp       ; or a : ex de,hl : sbc hl,sp : ex de,hl
      sub hl,bc       ; or a : sbc hl,bc
      sub hl,de       ; or a : sbc hl,de
      sub hl,hl       ; or a : sbc hl,hl                        ;; consider alternative: ld hl,0 (-9T)
      sub hl,sp       ; or a : sbc hl,sp

      ; Z80N only
      mul             ; mul de ; to avoid warning specify registers

`LDI` increases the data pointer after the data access, so `LDI A,(HL)` is the same as `LD A,(HL):INC HL`. Likewise, `LDD A,(DE)` is `LD A,(DE):DEC DE`.

### Real device emulation mode

To enable this mode you must use pseudo-op DEVICE.

In this mode the assembler is assembling program into virtual memory (as MSX's WB-ASS2, ZX-Spectrum's GENS, ZEUS, ALASM etc). You can also use pseudo-ops as MMU, SLOT, PAGE, SAVEBIN, SAVEDEV, SAVETAP, SAVESNA, SAVETRD, SAVENEX, SAVECDT, SAVECPCSNA, SAVECPR, SAVEAMSDOS, SAVE3DOS, SAVEHEX, SAVEHOB, BPLIST, SETBP, CSPECTMAP, LABELSLIST, use special functions in Lua scripts and use operators `{address}, {b address}` to read WORD/BYTE from the virtual memory.

If only single DEVICE is used in whole source batch, the device becomes "global" and will affect also source ahead of the DEVICE line.

docs_examples/s_realdevice.asm

        DEVICE ZXSPECTRUM128
        ; in this device the default slot is SLOT 3 with PAGE 0 paged in.

        ORG 32768
    StartProg:
        JP $

        DEVICE NONE
        ;do something, if you don't want to corrupt virtual
        ;memory with other code, for example, loader of code.
        ;...code...

        ;return to our virtual device:
        DEVICE ZXSPECTRUM128

        SAVESNA "snapshotname.sna", StartProg

Predefined devices:

NONE
NONE

  Disable real device emulation mode. By default.

ZXSPECTRUM48
ZXSPECTRUM48

  Has 4 slots (0-3) with size 4000h, 4 pages (0-3) with size 4000h. Slot 3 (it from 0C000h) enables to current by default.

ZXSPECTRUM128
ZXSPECTRUM128

  Has 8 RAM pages (0-7) with size 4000h. Default slot is 3.

ZXSPECTRUM256
ZXSPECTRUM256

  Same as Russian clone Scorption 256. Has 16 RAM pages (0-15) with size 4000h.

ZXSPECTRUM512
ZXSPECTRUM512

  Same as Russian clones ATM Turbo 512 and Pentagon 512. Has 32 RAM pages (0-31) with size 4000h.

ZXSPECTRUM1024
ZXSPECTRUM1024

  Same as Russian clones ATM Turbo 2 and Pentagon 1024 SL. Has 64 RAM pages (0-63) with size 4000h.

ZXSPECTRUM2048
ZXSPECTRUM2048

  Similar to other spectrum devices, has 128 RAM pages (0-127) with size 4000h.

ZXSPECTRUM4096
ZXSPECTRUM4096

  Similar to other spectrum devices, has 256 RAM pages (0-255) with size 4000h.

ZXSPECTRUM8192
ZXSPECTRUM8192

  Similar to other spectrum devices, has 512 RAM pages (0-511) with size 4000h.

ZXSPECTRUMNEXT
ZXSPECTRUMNEXT

  ZX Spectrum Next, has 8 slots (0-7) of size 0x2000 and 224 RAM pages (0-223) totalling at 1.75MiB of memory. The default mapping is similar to ZX128, paging in: {14, 15, 10, 11, 4, 5, 0, 1} pages. Default slot is 7 (memory range 0xE000..0xFFFF). All memory is zeroed during initialization.

NOSLOT64K
NOSLOT64K

  Has only single slot of size 64kiB (slot 0), covering full Z80 CPU addressing range. There are 32 memory pages available, by default page 0 is mapped. That makes in total 2MiB of memory available (32 * 64 ki = 2 Mi).

  This is currently recommended choice for ZX80/ZX81 development with DeZog debugger.

AMSTRADCPC464
AMSTRADCPC464

  Has 4 slots (0-3) of size 0x4000 and 4 pages (0-3) of 0x4000, totalling 64K. These pages are mapped linearly as { 0, 1, 2, 3 }.

AMSTRADCPC6128
AMSTRADCPC6128

  Has 4 slots (0-3) of size 0x4000 and 8 RAM pages (0-7) of 0x4000 (128K total). The default page mapping is { 0, 1, 2, 3 }.

AMSTRADCPCPLUS
AMSTRADCPCPLUS

  Has 4 slots (0-3) of size 0x4000 and 32 RAM pages (0-31) of 0x4000 (512K total). The default page mapping is { 0, 1, 2, 3 }.

For CPC users this dsk-lua library may be of interest.

If you want to see other devices you must write to us. See Feedback chapter.

### Predefined defines

SjASMPlus has predefined defines.

__SJASMPLUS__ = <24bit number>
__SJASMPLUS__

  Current version split into three 8bit values, ie. 0x010F02 for "1.15.2".

__VERSION__ = "version string"
__VERSION__

  String value (with quotes around it) of current version like `"1.15.2"`.

__ERRORS__ = <number>
__ERRORS__

  Number of errors.

__WARNINGS__ = <number>
__WARNINGS__

  Number of warnings.

__DATE__ = "YYYY-MM-DD"
__DATE__

  Date of assembling (timestamp before first pass).

__TIME__ = "hh:mm:ss"
__TIME__

  Time of assembling (timestamp before first pass, 24h form).

__PASS__ = <number>
__PASS__

  Current assembling pass (1, 2 or 3).

__INCLUDE_LEVEL__ = <number>
__INCLUDE_LEVEL__

  Current include-nesting level.

__BASE_FILE__ = <name of base file>
__BASE_FILE__

  Name of base file (include-level zero).

  This is raw value without quotes, practically unusable by asm source, but LUA provides means to operate with such string value.

__FILE__ = <name of current file>
__FILE__

  Name of current file.

  This is raw value without quotes, practically unusable by asm source, but LUA provides means to operate with such string value.

__LINE__ = <number>
__LINE__

  Current line number.

__COUNTER__ = <incrementing number>
__COUNTER__

  Does increment upon each usage (starting as value 0). Currently difficult to use for labels/symbols, because the starting `_` of the define name prevents mid-word substitution, will make more sense with new substitution rules (if ever the sjasmplus v2.x happens, would be too big change for v1.x). Right now call LUA for the rescue.

  __COUNTER__ usage in LUA script

          DB __COUNTER__   ; DB 0
          LUA ALLPASS
              sj.insert_label("label_" .. sj.get_define("__COUNTER__"), sj.current_address)
                      -- creates "label_1" at "$" (0x0001)
              sj.insert_label("label_" .. sj.get_define("__COUNTER__"), _c("$+10"))
                      -- creates "label_2" at "$+10" (0x000B)
          ENDLUA
      label__COUNTER__: ; does *NOT* substitute in current sjasmplus, sorry
          DB __COUNTER__   ; DB 3

          ; also macro arguments substitution can be used
          MACRO createLabelWithSuffix label?, suffix?
      label?_suffix? ; define global label
          ENDM
          createLabelWithSuffix label, __COUNTER__    ; label_4
          createLabelWithSuffix label, __COUNTER__    ; label_5

Deprecated predefined values from sjasmplus till version 1.15.1:

  The predefined values were renamed and extended to be more like gcc/clang pre-defines, the following ones are deprecated originals.

_SJASMPLUS = 1

  Deprecated, consider using similar __SJASMPLUS__

         IFDEF _SJASMPLUS
           ;code for sjasmplus
         ELSE
           ;code for other compiler
         ENDIF

_VERSION = "version"

  Deprecated, renamed to __VERSION__

         IF _VERSION = "1.07"
           ;code for 1.07
         ELSE
           ;code for other version
         ENDIF

_RELEASE = releasenumber

  Deprecated, consider using similar __SJASMPLUS__

         IF _RELEASE = 1 ; 0 - is stable version
           ;code for Release Candidate 1
         ELSE
           ;code for other version
         ENDIF

_ERRORS = <number>

  Number of errors. Deprecated, renamed to __ERRORS__

_WARNINGS = <number>

  Number of warnings. Deprecated, renamed to __WARNINGS__

## Pseudo-ops (aka Pseudo-instructions, Directives etc)

### Simple example of usage

         .SOMEPSEUDOOP ;or
         SOMEPSEUDOOP  ;or
         somepseudoop

[Almost] complete list of pseudo-ops follows:

### Pseudo-ops

#line <linenum> [<filename>]
#line

  This special directive MUST start at beginning of line same way as label and only the syntax starting with hash is allowed. There could be space between # and `line`.

  Specifies reported source position for subsequent lines, starting from <linenum> line number (must be positive integer) and coming from specified <filename> file (in error/warning reports, listing files and SLD files).

  The directive does NOT work inside `MACRO`, `DUP`, `LUA` and similar blocks, which track their position of definition (even fake one adjusted by `#line` preceding the block) and report that during emit of lines/instructions. The directive will also update content of `__FILE__` and `__LINE__` predefines. The scope of fake source position is current file and it does NOT affect searching directories for `INCLUDE` search, which will still operate based on real filename of the source including.

  The main differences from GCC's `#line` line control is support for only positive integers, hash symbol must be at beginning of line and no macro expansion for arguments (DEFINE substitution is applied to the line, but parsing expects integer literal in decimal form in the result)

  line_example.asm

          ld a,12345  ; warning reported at: line_example.asm(1)
      #   line 101 'some.c'   // quoting the filename is strongly recommended (to avoid `:` as line separator, etc)
          ld a,23456  ; warning reported at: some.c(101)
      #line 500               // rest of the line is ignored
          ld a,34567  ; warning reported at: some.c(500)

.<repeat-count> <single instruction>
.<repeat-count>

  Repeat <single instruction> <repeat-count> many times. Doesn't work in the beginning of line. The <repeat-count> must be either simple integer number or expression fully enclosed in parentheses.

  docs_examples/po_dot_repeat.asm

          .3          INC A    ;will be compiled to INC A:INC A:INC A
      len             EQU 10
          .(12-len)   BYTE 0   ;will be compiled to 2x BYTE 0
          .2 .3       RET      ;will be compiled to 6x RET

  To repeat block of instructions, use the DUP/EDUP pseudo op codes.
ABYTE <offset> <bytes>
ABYTE

  Defines a byte or a string of bytes. The offset is added to each of the following bytes.

          ABYTE 2 4,9    ; Same as BYTE 6,11
          ABYTE 3 "ABC"  ; Same as BYTE "DEF"

ABYTEC <offset> <bytes>
ABYTEC

  Defines a byte or a string of bytes, where the last byte of the string will have bit 7 set. The offset is added to each of the following bytes.

          ABYTEC 0 "KIP"        ; Same as BYTE "KI",'P'|128
          ABYTEC 1 "ABC",0,"DE" ; Same as BYTE "BC",'D'|128,1,'E','F'|128

ABYTEZ <offset> <bytes>
ABYTEZ

  Defines a byte or a string of bytes, followed by a zero. The offset is added to each of the following bytes.

          ABYTEZ 0 "KIP"        ; Same as BYTE "KIP",0

ALIGN [<expression equal to 1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384 or 32768>[, <byte>]]
ALIGN

  Align advances to nearest address where <new address> modulo <expression> (default 4) equals zero (stays at current address if possible).

  If <byte> is specified, memory advanced over is set to it.

          ALIGN         ; => ALIGN 4 - simply align by 4
          ALIGN 2       ; by 2 (preserves value of "device" memory)
          ALIGN 2,0     ; + fills memory with zero

ASSERT <expression>[, <anything>]
ASSERT

  An 'assertion failed' error is issued if the expression evaluates to zero. The "anything" is then also visible in the error message, so you can add description/notes about expression there.

      STACKPOINTER=0D500H
          ASSERT END_OF_PROGRAM < STACKPOINTER, Program code leaks into stack area
      END_OF_PROGRAM

BINARY <filename>[,<offset>[,<length>]]
BINARY INCBIN

  Synonym of INCBIN.

BLOCK <length>[,<init bytes>]
BLOCK

  Defines space. Has to be followed by the number of bytes to reserve, optionally followed by values to init bytes with, the last byte value fills remaining bytes of the block. Default fill value is zero.

          BLOCK 500             ; define a block of 500 bytes of zero
          BLOCK 500, 0          ; define a block of 500 bytes of zero
          BLOCK 400, -1         ; define a block of 400 bytes of 255
          BLOCK 10, "name_1st"Z ; -> 6e 61 6d 65 5f 31 73 74 00 00 (filler zero)
          BLOCK 10, "name_2"    ; -> 6e 61 6d 65 5f 32 32 32 32 32 (filler '2')
          BLOCK 10, "name_3 "   ; -> 6e 61 6d 65 5f 33 20 20 20 20 (filler space)
          BLOCK 10, 1, 'x', 3   ; -> 01 78 03 03 03 03 03 03 03 03 (filler 3)

BPLIST <filename> [unreal|zesarux|mame|fuse]
BPLIST SETBP

  Works only in real device emulation mode. See DEVICE. Breakpoints are added using SETBP directive.

  Opens file to export breakpoints info directly from ASM source, currently four flavours of export are supported: `unreal` (default), `zesarux`, `mame` and `fuse`. For latest Unreal emulators use filename "bpx.ini", you can then load the file from the emulator UI. For ZEsarUX and FUSE the file will contain command-line options or debugger commands, which you can add to the command when launching the emulator. For MAME the file contains debugger script commands in format `bp 0xADDR[,condition]` per breakpoint, with `g` command at the end to start execution without initial break.

  (current version of ZEsarUX will not catch the very first instruction of freshly loaded snapshot/NEX/... file, or it will even disable breakpoints when changing machine parameters and ignore the `--enable-breakpoints` option - will be hopefully improved in the future)

          BPLIST "bpx.ini" unreal  ; open breakpoints list in "Unreal" format
          ; or (only one file per assembling-unit can be specified)
          BPLIST "cmd_line_options.txt" zesarux ; open breakpoints list in "ZEsarUX" format
          ; or for MAME debugger
          BPLIST "breakpoints.txt" mame ; open breakpoints list in "MAME" format
          ; to be used as debug script: mame -debug -debugscript breakpoints.txt <other_options>

BYTE <bytes>
BYTE

  Defines a byte or a string of bytes. Each value should be between -129 and 256.

          BYTE 0x56
          BYTE 1,-78,'@'
          BYTE "Format C:? ",0h

CSPECTMAP [<filename>]
CSPECTMAP

  Useful for ZX-Spectrum Emulator #CSpect by Mike Dailly.

  Works only in real device emulation mode. See DEVICE.

  Saves labels list in format:

      HEXA_16BIT_ADDRESS HEXA_LONG_ADDRESS TYPE LABELNAME

  where TYPE is: 00 = regular label, 01 = EQU or struct defined, 02 = DEFL defined, 03 = ROM page/"none" device, 04 = STRUCT definition.

  If no filename is provided, default is created by appending ".map" to source name.

D24
D24

  Defines three bytes by 24b constant. Values should be between -16777217 and 16777216.

          D24 0x123456   ; define three bytes 0x56, 0x34, 0x12

DB
DB BYTE

  Synonym of BYTE.

DC
DC

  Same as BYTE, but every last character of a string will have bit 7 set. Single character in apostrophes is NOT considered as string.

          DC "kip" ; same as BYTE "ki",'p'|128
          DC 'A'   ; not a string, will produce 0x41 like BYTE 'A'
          DC "A", 'AB', "AB" ; three strings producing: C1 41 C2 41 C2

DD
DD DWORD

  Synonym of DWORD.

DEFARRAY <id> <replacements>
DEFARRAY

  Array of DEFINEs.

  Use `id[#]` to retrieve current size of array.

  docs_examples/po_defarray.asm

          DEFARRAY myarray 10*20,"A",20,</D,40>,50,70
      CNT DEFL 0 ;or CNT=0
          DUP myarray[#]      ; 6
          DISPLAY myarray[CNT]
      CNT DEFL CNT+1 ;or CNT=CNT+1
          EDUP

DEFARRAY+ <id> <additional replacements>
DEFARRAY+

  Appending more DEFINEs to already defined array

          DEFARRAY   myarray 'A', 'B', 'C'
          DEFARRAY+  myarray 'D', 'E'            ; now "myarray" has 5 items
          DUP 3 : DEFARRAY+ myarray '!' : EDUP   ; "DEFARRAYFILL" adding 3x '!'

DEFB
DEFB BYTE

  Synonym of BYTE.

DEFD
DEFD DWORD

  Synonym of DWORD.

DEFDEVICE <deviceid>, <slot_size $100..$10000>, <page_count 1..1024>[, <slot_0_initial_page>[, ...]]
DEFDEVICE DEVICE

  (since v1.20.1) Add custom device definition. The 64ki Z80 address space is divided evenly by `slot_size` to form N slots and the total device memory is `slot_size * page_count` bytes.

  The initial pages are implicitly going from `0` to `page_count-1` (repeating page `page_count-1` for remaining slots when there's not enough pages), but you can specify initial page layout explicitly.

  Use DEVICE to select the newly defined device.

DEFG
DEFG DG

  Synonym of DG.

DEFH
DEFH DH

  Synonym of DH.

DEFINE+ <id> <replacement>
DEFINE+
DEFINE <id> <replacement>
DEFINE

  The identifier <id> will be replaced with the <replacement>. The `DEFINE+` will also redefine the identifier <id> if it already exists. The replacement could be omitted, in such case it is still possible to check if the identifier was defined with IFDEF or IFNDEF.

          DEFINE str_honderd "Honderd"
          BYTE str_honderd,0             ; BYTE "Honderd",0
          DEFINE+ do_stuff set 4,e
          do_stuff                       ; set 4,e
          DEFINE+ do_stuff set 2,e
          do_stuff                       ; set 2,e

<label> DEFL <expression>
DEFL EQU

  Assigns value of <expression> to symbol <label>. New label defined by DEFL is marked internally as "modifiable", allowing to re-assign new values to it with further DEFL statements (you can use also `=` instead of `DEFL`).

      counter DEFL 0
          DUP 4
              DB 0xAA, counter
      counter = counter + 1
          EDUP
      ; machine code produced: AA 00 AA 01 AA 02 AA 03

DEFM
DEFM BYTE

  Synonym of BYTE.

DEFP
DEFP DP

  Synonym of DP.

DEFS
DEFS BLOCK

  Synonym of BLOCK.

DEFW
DEFW WORD

  Synonym of WORD.

DEPHASE
DEPHASE ENT DISP

  Synonym of ENT.

DEVICE <deviceid>[, <RAMTOP>]
DEVICE MMU ZXSPECTRUM128 ZXSPECTRUMNEXT DEFDEVICE

  Enables real device emulation mode by it identifier. If there is only single DEVICE directive in whole source batch, it becomes "global" and the device affects all lines of source, otherwise the DEVICE is applied for lines following it.

  For ZXSPECTRUM-like devices (except ZX Next) you can provide RAMTOP value, which will init the device memory in similar way as "`CLEAR <RAMTOP>`" in BASIC would do, putting top of the fake-stack at the RAMTOP address. The default RAMTOP is since v1.15.0 0x5D5B (it was 0xFF57 before). Use RAMTOP value -1 to skip device memory init completely and get all-zeroed memory. ZXSPECTRUM128 memory state does match the "USR 0" state (ZX48 ROM reset with ZX128 HW).

  Predefined devices' identifiers list:

       NONE ; off real device emulation mode
       ZXSPECTRUM48   ; ZX-Spectrum 48 (4 slots, 4 pages, slot/page size 0x4000, default map: 0, 1, 2, 3)
       ZXSPECTRUM128  ; ZX-Spectrum 128 (like 48 with 8 pages, default map: 7, 5, 2, 0)
       ZXSPECTRUM256  ; e.g. Scorpion 256 (exUSSR clone of ZX-Spectrum 128)
       ZXSPECTRUM512  ; e.g. ATM-Turbo 512 (another clone)
       ZXSPECTRUM1024
       ZXSPECTRUM2048
       ZXSPECTRUM4096
       ZXSPECTRUM8192
       ZXSPECTRUMNEXT ; ZX Spectrum Next (8 slots, 224 pages, slot size 0x2000 = 1.75MiB RAM)
                      ; (default pages map: 14, 15, 10, 11, 4, 5, 0, 1) (default slot: 7 (0xE000..0xFFFF))
       NOSLOT64K      ; Single slot (slot 0) covering full 64kiB address range of Z80, 32 pages
       AMSTRADCPC464  ; Amstrad CPC 464 (4 slots, 4 pages, slot/page size 0x4000, default map: 0, 1, 2, 3)
       AMSTRADCPC6128 ; Amstrad CPC 6128 (like 464 with 8 pages)
       AMSTRADCPCPLUS ; Amstrad CPC Plus (like 464 with 32 pages)

       ;disable:
         DEVICE NONE
       ;enable:
         DEVICE ZXSPECTRUM128

DG <data encoded in bits>
DG

  Data comprises of characters in multiples of eight, each block is converted to a byte value.

  A hyphen '-' (also '.' and '_') represents 0 and any other non-whitespace character represents 1. It ignores spaces, use them for formatting if you like. Warning, "DG 10100001" is value 255, because character '0' is not a dash '-'.

          DG 1-1----1  ; store 161 at the current location
          DG ...# #... .##. .... ; store two bytes: 0x18, 0x60

DH "<data>"[,"<data2>"...]
DH

  The data string comprises pairs of hexadecimal digits, each pair is converted to a byte value. You can add spaces between pairs as you like.

          DH "0123456789ABCDEF"   ; eight bytes #01 #23 ...
          DH "01 23  45 67"       ; four bytes #01 #23 #45 #67

DISP <address>[,<page_number>]
DISP ENT

  Set the address in which the part of code should work. PHASE and TEXTAREA are synonyms of DISP. ENT will restore current address. UNPHASE, DEPHASE and ENDT are synonyms of ENT. DISP blocks can NOT be nested, and to change the displacement address within current DISP block use the ordinary ORG. When in device mode, you can specify fixed value for "fake" page of emitted instructions and regular labels, but to avoid warning, you must also map-in the target page into the target memory slot (MMU). When no fixed page in DISP is specified, the current mapping of memory pages is used.

  docs_examples/po_disp.asm

          DEVICE ZXSPECTRUM48
      SCREEN  EQU $4000
              ORG $8000
              LD HL,BEGIN
              LD DE,SCREEN
              LD BC,ENDOFPROG-BEGIN
              LDIR
              JP SCREEN
      BEGIN   DISP SCREEN ;code will compile for address $4000, but to the current ORG
      MARKA       DEC A
                  HALT
                  JP NZ,MARKA
                  DI
                  HALT
              ENT
      ENDOFPROG

          ASSERT $800E == BEGIN && $8015 == ENDOFPROG && $4000 == MARKA
          ASSERT $76 == {B $800F}     ; HALT instruction lands at $800F (BEGIN+1)

DISPLAY <bytes>
DISPLAY

  This pseudo-op comes from ZX-Spectrum assembler ALASM.

  Out to console a string of bytes. Each value should be between -129 and 256. Keys /D, /B, /C, /H and /A set format of output of numbers:

      /D - out only in Decimal
      /B - out only in Binary (truncated to 8 bit)
      /C - out as character in apostrophes (truncated to 8 bit)
      /H - out only in Hexadecimal
      /A - out both in Hexadecimal and Decimal

  docs_examples/po_display.asm

          ORG 100h
      TESTLABEL:
          ;...some code...
          RET
          DISPLAY "--the some program-- by me"
          DISPLAY "TESTLABEL address is:",/A,TESTLABEL
      /*
      will output to the console strings:
      > --the some program-- by me
      > TESTLABEL address is:0x100, 256
      */

DM
DM BYTE

  Synonym of BYTE.

DP <bytes>
DP

  Defines a Pascal string, ie. size of bytes is preceding the data.

          DP "Ahoy!" ; like: BYTE 5, "Ahoy!"

DS
DS BLOCK

  Synonym of BLOCK.

DUP <count>[, <index_variable>]
DUP

  DUP specifies the number of times to generate the following lines until an EDUP pseudo-op is encountered. DUP can be used in macro's.

  DUP itself is internally implemented almost like anonymous macro emitted count-times, zero count skips the block.

  Optional second argument (since v1.20.2) <index_variable> is label name to have repetition index available inside the block (values 0, 1, 2, 3, ...). This label is mutable and global (don't use same label in nested DUP blocks).

          DUP 3, i1 ; zero count would skip whole block
            NOP
          EDUP

      /*this will expand to (updating also optional index_variable in every loop):
            NOP   ; i1 == 0
            NOP   ; i1 == 1
            NOP   ; i1 == 2
      */

  To repeat just a single instruction you can also use the .<repeat-count> syntax.
DW
DW WORD

  Synonym of WORD.

DWORD
DWORD

  Defines a so called doubleword. Values should be between -2147483649 and 4294967296.

          DWORD 4000h,0d000h
          DWORD 4

DZ
DZ

  Same as BYTE, but an extra zero will be added at the end.

          DZ 1      ; same as BYTE 1,0
          DZ "kip"  ; same as BYTE "kip",0

EMPTYTAP <filenameoftapefile>
EMPTYTAP

  Useful only for ZX-Spectrum users

  Create the new or truncate existing standard tape file for emulators of ZX-Spectrum. See example of SAVETAP.

EMPTYTRD <filenameoftrdimage>[,<disclabel>]
EMPTYTRD

  Useful only for ZX-Spectrum users

  Create the empty TRD image for emulators of ZX-Spectrum. See example of SAVETRD.

ENCODING <encoding>
ENCODING

  Useful only for non English users

  Set the current encoding, i.e. if you set "DOS", SjASMPlus will automatically convert strings from ANSI to DOS-866. Encoding may be "DOS"(DOS-866) or "WIN"(ANSI/Win-1251). Default is "WIN".

          ENCODING "WIN"
          DB "тексттекст" ;will be тексттекст
          ENCODING "DOS"
          DB "тексттекст" ;will be вҐЄбввҐЄбв

END [<startaddress>]
END

  The assembler will stop at this point. The pseudo-op END does NOT work in the beginning of line (even with --dirbol). The optional argument is used by SAVESNA, SAVECPCSNA, SAVETAP and SAVENEX.

ENDLUA
ENDLUA LUA

  See LUA for more information.

ENDMOD
ENDMOD ENDMODULE

  Synonym of ENDMODULE.

ENDMODULE
ENDMODULE MODULE

  To indicate the end of a module (see MODULE).

ENDT
ENDT ENT

  Synonym of ENT.

ENT
ENT DISP

  Restore current address. See DISP for more information.

<label> EQU <expression>[,<pageNumber>]
EQU DEFL

  To give the label a value other than the current program counter. The label should not already exist (you can assign only one value to it). The optional `pageNumber` can enforce explicit page value for the label (for the `$$label` operator). For modifiable labels holding temporary values use DEFL.

      Label EQU 3
      Kip   EQU 0x23*256 + low $

EXPORT <label>
EXPORT

  The named label will be written to the export-file, in the form 'label: EQU value'. This way the export-file can be included in other sources. Order of labels in file follows the order of EXPORT statements.

      DRIE=3
          EXPORT DRIE  ; adds into export file line: "DRIE: EQU 0x00000003"

FPOS <position>
FPOS

  The FPOS directive makes it possible to set the file position to anywhere in the output file. Position value without sign is used as absolute one to be set, position starting with + or - sign will be used as relative position.

  In combination with OUTPUT <filename>,r it is possible to update existing files.

      ; This example will result in a file with a length of one byte:
          BYTE 0
          FPOS 0
          BYTE 1
          END

HEX
HEX DH

  Synonym of DH, usually used without quotes around data.

HEXEND [-1|<start>]
HEXEND HEXOUT

  Finalize currently open HEX file output. If start address is omitted, the one provided by END is used, -1 as start address will explicitly disable the start address record type (03).

HEXOUT <filename>
HEXOUT HEXEND

  With HEXOUT it is possible to create multiple Intel HEX files from one source. All following instructions will be assembled to this file. HEXEND will finalize and close any previously opened HEXOUT.

  You can also use SAVEHEX in DEVICE mode, see it also for more details about Intel HEX format.

INCBIN <filename>[,<offset>[,<length>]]
INCBIN

  To include a binary file into the outputfile. The offset and length are optional. If negative offset or length is provided, it counts relatively from the end of the file.

  (since v1.21.0) The rules of INCLUDE are used for file search.

          INCBIN "gfx.scc",7        ; include gfx.scc, skip first 7 bytes
          INCBIN "rantab.com",3,256 ; include 256 bytes from offset 3
          INCBIN gfx.scc ,,7        ; 7 bytes from offset 0 (unquoted filename must end with space)
          INCBIN "48.rom",-768,-256 ; include (from 16kiB file) 512 bytes 15616..16127

INCHOB <filename>[,<offset>[,<length>]]
INCHOB

  To include a data from a hobeta file into the outputfile. The offset and length are optional.

  (since v1.21.0) The rules of INCLUDE are used for file search.

          INCHOB "gfx.$c",7        ; include gfx.scc, skip first 7 bytes
          INCHOB "sprs.$c",3,256   ; include 256 bytes from offset 3
          INCHOB gfx.$c ,7         ; note the space between the filename and the ',7' here :)

INCLUDE <filename>
INCLUDE

  To include another sourcefile into the current. Sourcefiles can be nested 20 levels deep. If the file cannot be found in the current directory (the current directory is the directory the current asm file comes from!) the file will be searched for in the directories specified at the commandline. When angle brackets are used, the commandline directories are searched before the current directory.

  The directory used to launch the assembling process is automatically added to the list (as if "`-i .`" was added to command line manually). If you want to start with completely empty include-path list, use "`--inc`" option early (order matters) without the "=" to empty the current list, like: `sjasmplus --inc --inc= path1 --inc= path2 file.asm`

  Current implementation expect INCLUDE to be used only outside of macros or conditional/repeatable blocks, so directives like MACRO, DUP, WHILE, IF/IFDEF/../ENDIF should be always nested into single file, not attempting to INCLUDE other pieces of asm sources from within their blocks. While some trivial cases may still work even with current implementation, you are likely to run into weird bugs and inconsistencies if you combine these "contexts of assembling" in other order, not having INCLUDE at top of everything.

          INCLUDE <VDP.I>     ; search for file "VDP.I" in the include directories, then in current
          INCLUDE MORE.I      ; search for "MORE.I" in current directory, then in include directories
          INCLUDE "MORE.I"

INCLUDELUA <filename>
INCLUDELUA

  To include another LUA script in first pass(!). If the file cannot be found in the current directory (the current directory is the directory the current file comes from) the file will be searched for in the directories specified at the commandline. When angle brackets are used, the commandline directories are searched before the current directory.

          INCLUDELUA <mylibrary1.lua>
          INCLUDELUA mylibrary2.lua
          INCLUDELUA "library_for_zx.lua"

INCTRD <filenameoftrdimage>,<filenameintrdimage>[,<offset>[,<length>]]
INCTRD

  To include a file from a TRD image into the outputfile. The offset and length are optional.

  (since v1.21.0) The rules of INCLUDE are used for file search.

          INCTRD "test.trd","mygfx.C" ; include mygfx.C from test.trd
          INCTRD "test.trd","mygfx.C",12 ; include mygfx.C from test.trd, skip first 12 bytes

INSERT <filename>[,<offset>[,<length>]]
INSERT INCBIN

  INSERT is a synonym of INCBIN. See above.

LABELSLIST <filename>[,<virtual labels>]
LABELSLIST

  Useful only for ZX-Spectrum Emulator UNREALSPECCY.

  Works only in real device emulation mode. See DEVICE.

  Save labels list in format:

      NN:ADDRESS LABELNAME

  where NN is number of RAM page and ADDRESS is truncated to 0000..3FFF range

  If `<virtual labels>` is non zero, then the page number NN is not part of output, and ADDRESS is truncated to 0000..FFFF range.

          LABELSLIST "x:/somepath/user.l"

LUA [pass]
LUA

  Using pseudo-ops LUA and ENDLUA you can insert Lua scripts. See more in the chapter "Lua scripting".

  Parameter is optional. It may be:

      PASS1   -  interpret Lua script in first pass only.
      PASS2   -  interpret Lua script in second pass only.
      PASS3   -  interpret Lua script in third pass only. By default.
      ALLPASS -  interpret Lua script in all passes. It is needed if you generate some Z80 code.

          LUA
      -- some comments
              print "Hi, man! This is Lua!"
          ENDLUA
      ; some code now:
          LUA ALLPASS
              _pl("LABEL LD A,10")
              _pc("RET")
          ENDLUA

MEMORYMAP

  Not available yet.

MMU <first slot number or address> [<last slot number or address>|<single slot option>], <page number>[,<address>]
MMU DEVICE SLOT PAGE

  Maps memory page(s) to slot(s), similar to SLOT + PAGE combination, but allows to set up whole range of consecutive slots (with consecutive memory pages). Or when only single slot is specified, extra option can be used to extend particular slot functionality. The slot behaviour will stay set in the current DEVICE until reset by another MMU specifying same slot (even as part of range, that will clear the option to "default").

  The optional third argument is address for ORG functionality.

  You can also use starting address of particular slot instead of its number, ie. for ZX128: `MMU $8000,3` is alias of `MMU 2,3`

  Single slot option (default state is: no error/warning and no wrap = nothing special):

      e = error on writing beyond last byte of slot
      w = warning on writing beyond last byte of slot
      n = wrap address back to start of slot, map next page

  docs_examples/po_mmu.asm

          DEVICE ZXSPECTRUM128 : LABELSLIST "po_mmu.lbl"  ; to check label pages
          MMU 1 3, 5      ; maps slots 1, 2, 3 with pages 5, 6, 7
          ORG 0xBFFF
      label1_p6: scf      ; last byte of page 6 (in slot 2)
      label2_p7: scf      ; first byte of page 7 (in slot 3)

          MMU 3 e, 0      ; page 0 into slot 3, write beyond slot will cause error
          ORG 0xFFFF
          ld  a,1         ; error: Write outside of memory slot: 65536 (65536 = address outside)

          MMU 3 n, 1      ; page 1 into slot 3, make it wrap + map next page automatically
          ORG 0xFFFF      ; ! also the $ address was truncated by MMU from $10001 to $0001 !
      label3_p1: scf      ; last byte of page 1, then wrapping back to 0xC000 with page 2
      label4_p2: scf      ; first byte of page 2 at 0xC000

MODULE <name>
MODULE

  Labels has to be unique only whithin the current module (module is added as prefix to them). Also note the use of '@' operator to suppress all this label-processing. Modules can be nested, and module has to be ended by ENDMODULE.

  docs_examples/po_module.asm

          MODULE xxx
      Kip:                ; label xxx.Kip
          ld  hl,@Kip     ; global Kip
          ld  hl,@Kop     ; global Kop
          ld  hl,Kop      ; xxx.Kop
      Kop:                ; label xxx.Kop
          ld  hl,Kip      ; xxx.Kip
          ld  hl,yyy.Kip  ; yyy.Kip
          ld  hl,nested.Kip   ; xxx.nested.Kip
              MODULE nested
      Kip:        ret     ; label xxx.nested.Kip
              ENDMODULE
          ENDMODULE

          MODULE yyy
      Kip:    ret         ; label yyy.Kip
      @Kop:   ret         ; label Kop (global one, no module prefix)
      @xxx.Kop: nop       ; ERROR: duplicate: label xxx.Kop
          ENDMODULE

      Kip     ret         ; global label Kip

  Since v1.14.0 the module `<name>` can NOT contain dot character. You can use nested modules to get identical identifier as in older versions, or please rename with underscores/etc:

          ; invalid since v1.14.0
              MODULE older.version
      fn1:        ret        ; final label: @older.version.fn1
              ENDMODULE
          ; can be replaced in v1.14.0 with
              MODULE new
                  MODULE version
      fn1:            ret    ; final label: @new.version.fn1
                  ENDMODULE
              ENDMODULE

  Since v1.14.0 the `MODULE` and `ENDMODULE` also resets the current "non-local" label prefix back to "_":

      Kep:    ; "Kep" label (global one), and also works as "non-local" prefix for local labels
          MODULE zzz
      .local: ; in v1.14.0 this will be "zzz._.local" label, previously it was "zzz.Kep.local"
      Kup:    ; this is "zzz.Kup", but also defines "non-local" prefix as "Kup"
      .local  ; this is "zzz.Kup.local"
          ENDMODULE
      .local: ; in v1.14.0 this will be "_.local" label, previously it was "Kup.local"

OPT [push] [reset] [listoff] [liston] [listall] [listact] [listmc] [<command line options>]

OPT

OPT pop

  Allows to reset and modify options affecting syntax and parsing (for lines of source following the OPT). The options allowed for OPT are: `-W[no-]<warning_id>`, `--syntax`, `--zxnext`, `--reversepop`, `--dirbol` and `--cleanonerror`.

  Ahead of options you can use OPT commands: push, pop, reset, listoff, liston, listall, listact, listmc. The "push" command will make OPT to preserve current state of options. The "reset" command will reset OPT-related options to default state. The "listoff" command will suspend the listing for following lines until "liston" command is used (listing availability is part of the push/pop state, so to "nest" listing-off you can use "OPT push listoff : ... code ... : OPT pop" code sequence). The "listall", "listact" and "listmc" commands will select the filter for listing. The "listmc" lists only lines containing machine code bytes, "listact" will remove the "skipped" blocks (like false-blocks of IF/ELSE conditional assembling, or inner definitions of macros and structures), "listall" will switch back to default listing without filtering.

  Then the provided options are applied. The default values are: fake instructions enabled (no warning), multi-argument delimiter is ",", both () and [] brackets can be used to access memory, labels can have any name, ZX Next instructions are OFF, POP with multiple arguments doesn't reverse them and pseudo-ops at beginning of line are OFF (to just reset to these defaults you can use `OPT reset`).

  The "pop" command: the previously preserved state of options is restored (states are preserved in "stack" way, so further OPT with "pop" will restore older states).

  The id-warning system state is NOT saved/restored by OPT push/pop/reset and can be modified only by `-W` option.

  The `--cleanonerror` state is NOT saved/restored by OPT push/pop/reset and can be only enabled (and stay so).

  docs_examples/po_opt.asm

          POP bc, hl   ; pops BC first
          OPT push reset --reversepop --syntax=af
          POP bc,,hl   ; pops HL first
          LD  bc,hl    ; warning about Fake instruction
          LD  bc,hl    ; warning supressed by lowercase "fake" in this comment
          OPT reset --syntax=a
          POP bc,,hl   ; pop BC first (--reversepop was reset)
          OPT pop      ; restoring syntax to original state

ORG <address>[,<page_number>]
ORG DEVICE

  Set the program counter to a specific address. If the second argument is provided, it will change memory page in the current slot, see PAGE and SLOT (it will warn you when <address> is outside of it).

  When used inside DISP block, only the virtual "displaced" program counter is affected, but the machine code will be still sequentially emitted in the original physical location and warning is emitted.

  docs_examples/po_org.asm

          ORG 100h ; or 0x100, or $100, or #100

          ; useful macro that padding code
          MACRO PADORG addr
               ; add padding
               IF $ < addr
               BLOCK addr-$
               ENDIF
               ORG addr
          ENDM

          MACRO PADORG2 addr ; z80asm "FORG" replacement
               ; add padding + display warning
               IF $ > addr
                 ; no padding
                 DISPLAY /L, "Warning! PADORG failed! ", $, " is more than ", addr
               ELSE
                 ; add padding
                 BLOCK addr-$
               ENDIF
               ORG addr
          ENDM

OUTEND
OUTEND OUTPUT SIZE

  Ends generating compiler output to file specified in OUTPUT and resets SIZE value to default "none".

OUTPUT [<filename>[,<mode>]]
OUTPUT SIZE

  With OUTPUT it is possible to create multiple files from one source. All following instructions will be assembled to this file. It will also close (OUTEND) and finalize any previously opened output.

  There are three possible output modes: truncate (overwrite existing files, this is the default), rewind (open and execute FPOS 0) and append (open and leave the file pointer at the end of the file).

      OUTPUT <filename>,t  ; truncate (default)
      OUTPUT <filename>,r  ; rewind
      OUTPUT <filename>,a  ; append

  bigfile.asm

          OUTPUT loader.com
          ORG 100H
          INCLUDE loader.asm
          INCLUDE bios.asm

          OUTPUT bigfile.dat
          ORG 4000H
          INCLUDE main.asm
          ORG 8000H
          INCLUDE data.asm
          OUTEND
          INCLUDE next.asm

  This will create two files: loader.com and bigfile.dat.
PAGE <number>
PAGE DEVICE MMU SLOT

  Works only in real device emulation mode. See DEVICE.

  Set the current memory page to current slot.

          PAGE 7 ;set 7 page
          SAVEBIN "ram7.bin",$C000,$4000 ;- save $4000 begin from $C000 of RAM to file

PHASE
PHASE DISP

  Synonym of DISP.

RELOCATE_END
RELOCATE_END

  Ends the block of code for which relocation data are produced (started by RELOCATE_START). To get the relocation data themselves, use RELOCATE_TABLE and related symbols.

  For more details/examples check the examples/relocation, examples/SymbOS_nslookup and the relocation tests.

RELOCATE_START [HIGH]
RELOCATE_START

  Marks start of block for which relocation data are produced. Any *regular* label (not EQU/DEFL symbols) defined inside the RELOCATE_START .. RELOCATE_END block will be treated as "relocatable" label. Any instruction/directive within the block which needs resulting opcode adjusted when relocated to different address will produce relocation-data for the RELOCATE_TABLE.

  The `RELOCATE_START HIGH` variant checks only high-byte relocation. The produced table points to high byte (MSB) of the machine code. This mode is for OS/loaders which relocate only to 256-byte aligned boundaries (keeping LSB intact).

  The relocation feature of regular label is transitive and EQU value based on already relocatable label will be tagged as relocatable too. Also structures instantiated in relocatable block have their labels tagged as relocatable and their initial values may become part of relocation data if the initial value is based on relocatable value.

  You can prepend operator `norel` to label to strip it of its relocatable property for particular expression.

  The internal heuristic is not bullet-proof, some complex expression may fail to be recognised as fixed/relocatable value and instead warn about unstable result. It is recommended to assemble relocatable blocks in lower regions of address space (like `ORG 0` or `ORG $1000`) and to keep expressions simple enough.

RELOCATE_TABLE [<subtract_offset>]
RELOCATE_TABLE

  Emits relocation data gathered across all relocation blocks. There are also two built-in symbols `relocate_count` and `relocate_size` containing the size of the table (only word-type tables are supported in sjasmplus, so size is always doubled count).

  The table is raw array of WORD (16 bit) values with memory offsets of machine code which should be adjusted (when the code is relocated to different than original base address). To get offsets from particular base address, use ORG ahead of relocation block to land instructions and labels from there.

  The <subtract_offset> is subtracted from every offset recorded in the relocation table (same what WinAPE does with its optional argument "base_address" for RELOCATE_TABLE).

REPT <count>[, <index_variable>]
REPT DUP

  Synonym of DUP. There is also synonym ENDR to end REPT block (EDUP works too).

SAVE3DOS <filename>,<address>,<size>[,<type>[,<w2_line>[,<w3>]]]
SAVE3DOS

  Works only in virtual device emulation mode. See DEVICE.

  Like SAVEBIN, saves the block of device RAM from "address", but with +3DOS 128 byte header prepended.

          DEVICE ZXSPECTRUM48
          ...

          ; +3 BASIC header (8 bytes starting from offset 15 in +3DOS header):
          ; type is first byte (0 = basic, 1, 2 = arrays, 3 = code/screen$)
          ; size is "first word" (bytes 1 and 2)
          ; w2_line argument is "second word" (bytes 3 and 4)
          ; w3 argument is "third word" (bytes 5 and 6) (default value = size)

          ; by default the type=3 (CODE/SCREEN$) is saved
          SAVE3DOS "screen.bin", $4000, $1b00
          ; type 3: w2 argument $C000 is target load-address
          SAVE3DOS "code.bin", $8000, $1234, 3, $C000
          ; type 0: BASIC, default w2 = $8000 (no auto-run)
          SAVE3DOS "prg1.bas", $5c00, 1234, 0
          ; type 0: w2_line is LINE auto-run (30 in example)
          SAVE3DOS "prg2.bas", $5c00, 1234, 0, 30
          ; type 1: Numeric array (refer to +3 manual for these)
          SAVE3DOS "b.dat", $8000, 30, 1
          ; type 2: Character array (or maybe just ignore these)
          SAVE3DOS "c.dat", $8000, 30, 2

SAVEAMSDOS <filename>,<address>,<size>[,<start = 0>[,<type = 2>]]
SAVEAMSDOS

  Works only in virtual device emulation mode. See DEVICE.

  Like SAVEBIN, saves the block of device RAM from "address", but with AMSDOS 128 byte header prepended.

          DEVICE AMSTRADCPC464
          ... some code ...

          ; by default the type=2 (BINARY) is saved
          ; "start" (entry) value is by default 0, overriden here to $1234
          SAVEAMSDOS "code.bin", $1000, $1b00, $1234

SAVEBIN <filename>,<startadress>,<lengthofcode>
SAVEBIN

  Works only in real device emulation mode. See DEVICE.

  Save the block of RAM.

          PAGE 7 ;set 7 page to current slot
          SAVEBIN "ram7.bin",$C000,$4000 ;- save 4000h begin from C000h of RAM to file
          SAVEBIN "ram2.bin",$8000,$3000 ;- save 3000h begin from 8000h of RAM to file

SAVECDT FULL <cdtname>[,<startaddr>[,<screenmode>[,<border>[,<ink0>...<ink15>]]]]

SAVECDT

SAVECDT EMPTY <cdtname>

SAVECDT BASIC <cdtname>,<name>,<start>,<length>

SAVECDT CODE <cdtname>,<name>,<start>,<length>[,<customstartaddress>]

SAVECDT HEADLESS <cdtname>,<start>,<length>[,<sync>[,<format>]]

  Works only in AMSTRADCPC464 / AMSTRADCPC6128 real device emulation mode. See DEVICE.

  Manipulate CDT file (tape/TZX file format used by Amstrad CPC emulators).

          DEVICE AMSTRADCPC6128
          ...
          ; create empty CDT file (truncates already existing file to make it empty)
          SAVECDT EMPTY "output.cdt"

          ; snapshot-like dump with default loader, including loading screen, all banks, ...
          SAVECDT FULL "output.cdt" ; defaults: END start addres, screenmode=1, border=1
                                    ; ink# = 1, 24, 20, 6, 26, 0, 2, 8, 10, 12, 14, 16, 18, 22, 24, 16

          ; BASIC program prepared at address "label"
          SAVECDT BASIC "output.cdt","A",label,length

          ; block of binary data at address "label" (default "entry" = "label")
          SAVECDT CODE "output.cdt","C",label,length,entry

          ; headless block with custom sync and format at address "label", formats: 0 = AMSTRAD, 1 = ZX
          SAVECDT HEADLESS "output.cdt",label,length,sync,format ; default sync = 0x16, format = 0

SAVECPCSNA <filename>[,<startadressofprogram>]
SAVECPCSNA

  Works only in AMSTRADCPC464 / AMSTRADCPC6128 device emulation mode. See DEVICE.

  Save the snapshot for emulators of Amstrad CPC machines. (If start address is omitted, the one provided by END is used)

  The snapshot header content is hard-wired in the sjasmplus binary, with the CRTC, Palette and Gate Array registers set to those of a CPC after a boot into the main ROM. Similarly to the ZX-Spectrum SAVESNA directive, interrupts are disabled and must be re-enabled by your program. A full list of the settings can be found in the source. If you absolutely need different defaults and can't afford init-code modifying the state, you can post-patch the resulting SNA file directly from source, see Issue #139 for example.

          DEVICE AMSTRADCPC464
          ORG $1200
      START  .... ;some code
          RET
          SAVECPCSNA "game.sna",START ;save snapshot to file game.sna. Start address is START ($1200)

SAVECPR <filename>[,<size = 32>]
SAVECPR

  Works only in AMSTRADCPCPLUS device emulation mode. See DEVICE.

  Saves memory per blocks of 16KiB; 1 means 16KiB, 2 means 32KiB, ...

          DEVICE AMSTRADCPCPLUS
          ... some code ...

          ; by default a size of 32 (512KiB) is used
          ; here we save a block of 16KiB
          SAVECPR "code.cpr", 1

SAVEDEV <filename>,<startPage>,<startOffset>,<length>
SAVEDEV

  Works only in real device emulation mode. See DEVICE.

  Like SAVEBIN, saves the block of device RAM.

  But it allows lengths over 64ki, and the offset value goes directly into device virtual memory (where pages are allocated consecutively), ignoring current slot "mapping". I.e. page=2,offset=0 will start saving data from page 2 at its beginning, going through pages 3, 4, 5, ... until the requested length of data is saved.

  The offset is not limited to page size, i.e. arguments page=1,offset=0x500 are equal to arguments page=0,offset=0x4500 for ZXSPECTRUM128 device (has page size 0x4000).

          DEVICE ZXSPECTRUM128 : SAVEDEV "fullram.bin",0,0,0x20000 ; save full 128kiB

SAVEHEX <filename>,<address>,<size>[,<start = -1>]
SAVEHEX

  Works only in virtual device emulation mode. See DEVICE.

  Like SAVEBIN, saves the block of device RAM from "address", but in Intel HEX format. Currently record types 00, 01 and 03 are supported, line endings are always LF, record length is max 16 and there are no 25 NUL chars at beginning of file.

  You can also use command line option `--hex` or HEXOUT.

SAVEHOB <filename>,<filename_in_trdos>,<startadress>,<lengthofcode>
SAVEHOB

  Works only in real device emulation mode. See DEVICE.

  Save the block of RAM in Hobeta format.

          PAGE 7 ;set 7 page to current slot
          SAVEHOB "ram7.$c","myfile1.C",$C000,$4000 ;- save 4000h begin from C000h of RAM to file
          SAVEHOB "ram2.$c","myfile2.C",$8000,$3000 ;- save 3000h begin from 8000h of RAM to file

SAVENEX <command> <command arguments>
SAVENEX

  Commands to build NEX file, for details check SAVENEX chapter.

  Works only in ZXSPECTRUMNEXT device emulation mode. See DEVICE.

SAVESNA <filename>[,<startadressofprogram>]
SAVESNA

  Works only in real device emulation mode. See DEVICE.

  Save the snapshot for emulators of ZX-Spectrum. (If start address is omitted, the one provided by END is used)

  The snapshot header content is hard-wired in sjasmplus binary (disabled interrupts may surprise some users, you can check the source to see all the values). If you absolutely need different defaults and can't afford init-code modifying the state, you can post-patch the resulting SNA file directly from source, see Issue #139 for example.

          DEVICE ZXSPECTRUM128
          ORG $8000
      START  .... ;something code
          RET
          SAVESNA "game.sna",START ;save snapshot to file game.sna. Start address is START ($8000)

SAVETAP <filename>,BASIC,<fileintapeheader>,<start>,<length>[,<autorunline>[,<lengthwithoutvars>]]

SAVETAP

SAVETAP <filename>,CODE,<fileintapeheader>,<start>,<length>[,<customstartaddress>[,<optional3rdparam>]]

SAVETAP <filename>,NUMBERS,<fileintapeheader>,<start>,<length>[,<variableletter(A..Z)>]

SAVETAP <filename>,CHARS,<fileintapeheader>,<start>,<length>[,<variableletter(A..Z)>]

SAVETAP <filename>,HEADLESS,<start>,<length>[,<customblockflag(0..255)>]

  Works only in real device emulation mode. See DEVICE.

  Append the tape header or block of data to the end of the standard tape file for emulators of ZX-Spectrum.

          DEVICE ZXSPECTRUM48
          ...
          EMPTYTAP "output.tap"

          SAVETAP "output.tap",BASIC,"noAutorun",label,100
          SAVETAP "output.tap",BASIC,"w/Autorun",label,100,9999
          SAVETAP "output.tap",BASIC,"withVars",label,123,9999,100
          SAVETAP "output.tap",CODE,"bank17",screen,6912
          SAVETAP "output.tap",CODE,"screen",demo,length,org
          SAVETAP "output.tap",NUMBERS,"dimArray",label,100      ; a()
          SAVETAP "output.tap",NUMBERS,"othernum",label,200,'b'  ; b()
          SAVETAP "output.tap",CHARS,"charArray",label,300       ; a$()
          SAVETAP "output.tap",CHARS,"nextone",label,400,'m'     ; m$()
          SAVETAP "output.tap",HEADLESS,start,length

SAVETAP <filename>[,<startadressofprogram>]

  Works only in real device emulation mode. See DEVICE.

  Save the tape file for emulators of ZX-Spectrum as a "snapshot" of almost-whole memory. Generated tape file supports the ZX-Spectrum clones with extended RAM such as ATM Turbo 512, etc. (If start address is omitted, the one provided by END is used)

  The stored memory starts at $5E00 (with screen data at $4000 auto-detected and optionally stored into the TAP file). The stored memory is automatically trimmed of zero values at start and end of the region (put non-zero byte markers around region you want to store, if you have zero-ed bytes at beginning or end of your code).

          DEVICE ZXSPECTRUM48
          ORG $7FFF
          DB $01      ; non-zero marker to store the following zero-ed data
      DAT DS 1024, 0  ; zero-ed data at $8000 (1024 bytes)
      START  ....     ; some code
          RET
          SAVETAP "game.tap", START ; save tape-snapshot to file game.tap. Start address is $8400

SAVETRD <filename_of_trd_image>,<filename_in_trdos>,<address>,<length>[,<autostart_BASIC_line>[,<length_minus_variables>]]
SAVETRD
SAVETRD <filename_of_trd_image>,|<filename_in_trdos>,<address>,<length>[,<autostart_BASIC_line>]
SAVETRD <filename_of_trd_image>,&<filename_in_trdos>,<address>,<length>

  Works only in real device emulation mode. See DEVICE.

  Save the device memory into TRD disk image. Saving two files with same filename will emit warning, but two files (with same name) will be created in the disk directory.

  Adding pipe character "|" ahead of file name will make sjasmplus to delete old file(s) with the same name before writing the new one => replace-like functionality. If the deleted file did occupy all sectors till the free space position in disc info, sjasmplus will salvage those sectors back and save new file over them (but it will not do full reshuffle/defrag in more complex cases, sjasmplus is just assembler, not full featured TRD images manipulation tool).

  Adding ampersand character "&" ahead of file name will make sjasmplus to look for existing file with the requested name (last of them, any earlier duplicates are deleted). The new content is appended to the file (sector aligned append) and the catalog entry gets only number of sectors patched, up to 255 sectors at most. This is special mode for single-file big-loaders.

  For BASIC programs (.B extension): the optional argument `autostart_BASIC_line` is number of basic line 0..9999, the autostart marker (4 bytes) will be appended after BASIC code. If you want to use custom `length_minus_variables` without autostart marker, use -1 autostart value (since v1.24.0).

  The unofficial three-letter extensions are also supported (official extensions are only: B, C, D and #).

          EMPTYTRD "test.trd" ;create empty TRD image
          PAGE 7 ;set 7 page to current slot
          SAVETRD "test.trd","myfile1.C",$C000,$4000 ;- save 4000h begin from C000h of RAM to file to TRD image
          SAVETRD "test.trd","myfile2.C",$8000,$3000 ;- save 3000h begin from 8000h of RAM to file to TRD image
          SAVETRD "test.trd",|"myfile1.C",$B000,$400 ;- replace "myfile1.C" with new file
          SAVETRD "test.trd",&"myfile1.C",$9000,$734 ;- sector-append new data to "myfile1.C"

SETBP [<expression> | "<condition>"]
SETBP BPLIST
SETBP <expression>, "<condition>"
SETBREAKPOINT ...
SETBREAKPOINT SETBP

  Works only in real device emulation mode. See DEVICE.

  Will add execution-type breakpoint at address <expression> to the BPLIST export file. If no expression is specified, the current program counter will be used.

  Some breakpoint list file types allow for optional extra string like condition of breakpoint, for example `fuse` file type.

          BPLIST "cmd_line.options.txt" zesarux ; open breakpoints list in "ZEsarUX" format
      start:
              nop     ; ZEsarUX will not catch very first instruction of snapshot file loaded
              SETBP   ; so breakpoint ahead of the second instruction (after first "nop")
              xor  a
              SETBP some_routine_label  ; set also some breakpoint to other code by its label
              SETBREAKPOINT $0D6B       ; alias of SETBP, works identically
              ; ... your code of app
              SETBP $1234, "z80:hl=0"   ; with FUSE this will generate `br 0x1234 if z80:hl=0`

SHELLEXEC <filename>[,<parameters>]
SHELLEXEC

  Execute external program <filename> using optional command line <parameters>.

          OUTPUT "mybin.bin"
          ;some code
          IF ((_ERRORS = 0) && (_WARNINGS = 0))
              SHELLEXEC "x:/somepath/bin2tap.exe mybin.bin mytap.tap"
             ; or SHELLEXEC "x:/somepath/bin2tap.exe","mybin.bin mytap.tap"
          ENDIF

SIZE <filesize in bytes>
SIZE OUTPUT

  If the resulting file is less than the given length, as many zero bytes are added as necessary. See OUTPUT for more.

          SIZE 32768       ; make sure file will be 32K

SLDOPT <type> <arguments>
SLDOPT

  Type ``COMMENT`` - add comma delimited list of case sensitive keywords to be detected in end-of-line comments and exported to Source Level Debugging (SLD) data. The particular keywords depend on the debugger you are using to process SLD data.

          DEVICE ZXSPECTRUM128
          SLDOPT COMMENT WPMEM, Log
          ORG $8000
              call init ; Log("calling init") - gets exported to SLD
              ret       ; no keyword here - not exported to SLD
      var1    DB 1      ; WPMEM - exported
      var2    DB 2      ; WpMem - not exported, case sensitive!

  Type ``SWAPON`` and ``SWAPOFF`` - swaps source and definition positions in Source Level Debugging (SLD) data. This may help to debug content of macros in debuggers which work only with source file positions.

          DEVICE ZXSPECTRUM128
          MACRO SWAPON_IN_MACRO
              SLDOPT swapon   ; make `scf` below have SLD data for "source/emit" swapped with "definition" data
              scf             ; "definition" line
              SLDOPT swapoff
          ENDM
          ORG $8000
          SWAPON_IN_MACRO     ; "source/emit" line

SLOT <number_or_address>
SLOT DEVICE MMU PAGE

  Works only in real device emulation mode. See DEVICE.

  Set current slot. Slots are defined by DEVICE pseudo-op. Use pseudo-op PAGE to change page in the current slot.

  You can also use starting address of particular slot instead of its number, ie. for ZX128: `SLOT $8000` is alias of `SLOT 2`

          DEVICE ZXSPECTRUM128
          SLOT 3 ;from 0C000h to 0FFFFh
          PAGE 1 ;set page 1 to slot 3
          ORG 0C000h
          ;your program here
          PAGE 2
          INCBIN "somegfx.bin"
          ;....

TAPEND
TAPEND TAPOUT

  Ends generating compiler output to tape file block specified in TAPOUT.

TAPOUT <filename>[,<flagbyte>]
TAPOUT

  Appends one tape block at the end of specified file. All following code will be assembled to this tape file block.

  Default value of flagbyte is 255.

  bigfile.asm

          EMPTYTAP screen.tap

          TAPOUT screen.tap ,0    ; unquoted filename needs space to end it
          DB 3
          DB 'ScreenName'
          DW 6912
          DW 16384
          DW 32768
          TAPEND

          TAPOUT screen.tap
          INCBIN screen.bin
          TAPEND

  This will create tap file with the screen.
TEXTAREA <address>
TEXTAREA DISP

  Synonym of DISP.

UNDEFINE <id>
UNDEFINE DEFINE

  Removes the identifier defined by DEFINE

          DEFINE Release 1

          IFDEF Release
            DISPLAY "Building release version"
          ENDIF

          UNDEFINE Release

          IFNDEF Release
            DISPLAY "It's works!"
          ENDIF

          IFDEF _SJASMPLUS
            DISPLAY "Yes, it's the sjasmplus!"
          ENDIF

          UNDEFINE *  ; undefine all identifiers

          IFNDEF _SJASMPLUS
            DISPLAY "It's not the sjasmplus??"
          ENDIF

UNPHASE
UNPHASE ENT

  Synonym of ENT.

WHILE <expression>[, <guardian-counter>]
WHILE

  While expression evaluates to non-zero value, generates following lines until an ENDW (EDUP/ENDR alias) pseudo-op is encountered.

  WHILE itself is internally implemented almost like anonymous macro emitted repeatedly.

  The optional guardian-counter value will abort the WHILE if there are more iterations, default guardian-counter is set to 100k.

      ptr = $4000
          WHILE ptr < $4020, 500 ; max 500 loops before error
              DB low ptr
      ptr = ptr + 1
          ENDW    ; EDUP or ENDR works too, but ENDW is advised for WHILE

      /* will emit bytes $00, $01, ..., $1F */

WORD <words>
WORD

  Defines a word. Values should be between -32787 and 65536.

          WORD 4000h,0d000h
          WORD 4,"HA"

### Conditional assembly

It may be useful to assemble a part (or not) based on a certain condition.

IF <expression>
IF

  If <expression> is non-zero the following lines are assembled until an ELSE, ELSEIF or ENDIF.

  Forward reference of label will cause warning - as any machine code emitted inside the IF/IFN block with such expression can lead to unstable results. If you are sure the late definition of label will not affect resulting machine code (the IF block does not emit any machine code and does not define any label), you can suppress the warning.

          IF (aaa == 0) || (2 = aaa && ((bbb % 13) & 0x01))
              ; some code to assemble only if the symbol `aaa` is zero
              ; or (logical OR) when symbol `aaa` is two
              ; and (logical AND) modulo 13 of `bbb` is odd number
          ENDIF

IFN <expression>
IFN

  If <expression> is zero the following lines are assembled until an ELSE, ELSEIF or ENDIF.

IFDEF <id>
IFDEF

  The condition is true if there is an id defined. These are NOT labels.

          IFDEF MSX_LEAN_AND_MEAN
              CALL InitOwnMM
          ELSE
              CALL InitDos2MemMan
          ENDIF

IFNDEF <id>
IFNDEF

  The condition is true if there isn't an id defined. These are NOT labels.

      1   IN A,(0C4H)
          AND 2
          IFNDEF DEBUG
              JR NC,1B
          ENDIF

IFUSED <label>
IFUSED

  The condition is true if there is an label used somewhere in the code. You can create libraries of useful functions using IFUSED pseudo-op

  (similar to tests/misc/ifused_test.asm)

          OUTPUT "TEST.OUT"

          CALL LABEL3 ; LABEL3 - yes
          LD A,(LABEL1) ; LABEL1 - yes

          IFUSED LABEL1
      LABEL1:
          DB 1
          ENDIF

          IFUSED LABEL2
      LABEL2:
          DB 2
          ENDIF

          IFUSED LABEL3
      LABEL3:
          DB 3
          ENDIF

          IFUSED LABEL4
      LABEL4:
          DB 4
          ENDIF

          LD A,LABEL2 ; LABEL2 - yes

          RET

      ; Output will contain bytes from LABEL1 to LABEL3 (1, 2, 3), but not contain from LABEL4, because this label is not used.

      ; Alternative syntax:
      LABEL5:
          IFUSED ; sjasmplus will use name of previous label, i.e. LABEL5

          ENDIF

  Known bug: when code is using label inside module "moduleX", like `call labelY`, only usage of `moduleX.labelY` label is noted. Then if you define "labelY" outside of module and hide it inside `IFUSED labelY` block, the call from module will be unable to find the routine.

  Workaround: you can use the global-label operator @: "`call @labelY`" to trigger usage of the global "labelY", or you can use the alternative IFUSED syntax "`labelY: IFUSED`" which does not only check condition, but also does define the label. Once the label is defined, the "call labelY" line inside module will find the global variant and mark it as "used" correctly.

  Local labels inside the IFUSED block are not visible until the main label is used, if you want to call only local label, you can force the visibility of the routine by "`ASSERT main_label`" - this will trigger usage of the global "main_label", and then the IFUSED block will be assembled, and local labels will become visible too.

IFNUSED <label>
IFNUSED

  The condition is true if there is an label not used somewhere in the code.

ELSE
ELSE IF

  See IF. If the IF condition is false (in all previous IF/ELSEIF blocks), the else-part is assembled.

ELSEIF <expression>
ELSEIF IF

  If the previous IF-block had false condition, the ELSEIF expression is evaluated and the ELSEIF acts as new IF.

ENDIF
ENDIF

  Every IF should be followed by an ENDIF.

Conditional assembly

### Macros

The MACRO pseudo-op defines a macro. It should be followed by the name of the macro, optionally followed by the parameters. The following lines will be stored as the macro-body until an ENDM pseudo-op is encountered. Macro's have to be defined before their use. Macro's name must be unique (even if two macros differ by required arguments).

Macros (also internal ones like DUP/REPT/..) are stored as unparsed text and when used, the stored text is expanded and assembled as newly injected lines of source code. This expansion could cause in edge cases slight difference how the text is parsed and assembled, so using directives further affecting the parsing like LUA or INCLUDE inside macro definition is in current version unsupported. If it works for you in some trivial case, then you can keep using it, it's unlikely the future versions of sjasmplus will do worse than current, but if you hit weird bug for code which works outside of macro, then you are out of luck (unless you want to rewrite sjasmplus internal parsing code).

Macro without parameters (docs_examples/s_macros.asm)

      MACRO ADD_HL_A
        ADD A,L
        JR NC,.hup
        INC H
    .hup
        LD L,A
      ENDM

Labels in a macro starting with a dot are local to each macro expansion.

A macro with parameters (docs_examples/s_macros.asm)

      MACRO WAVEOUT reg, data
        LD A,reg
        OUT (7EH),A
        LD A,data
        OUT (7FH),A
      ENDM
    ; this macro will make
      WAVEOUT 2,17
    ; expand to:
      LD A,2
      OUT (7EH),A
      LD A,17
      OUT (7FH),A

Another example (docs_examples/s_macros.asm)

        MACRO LOOP
          IF $-.lus<127
            DJNZ .lus
          ELSE
            DEC B
            JP NZ,.lus
          ENDIF
        ENDM

    Main
    .lus
        CALL DoALot
        LOOP
    ; This will expand to:
    Main
    .lus                  ; Main.lus
        CALL DoALot
        DJNZ .lus         ; Main.lus

Angle brackets can be used when the arguments contain commas.

Argument in angle brackets (docs_examples/s_macros.asm)

        MACRO UseLess data
          DB data
        ENDM

        UseLess <10,12,13,0>
    ; expands to:
        DB 10,12,13,0

    ; use '!' to include '!' and '>' in those strings.

        UseLess <5, 6 !> 3>
    ; expands to:
        DB 5, 6 > 3

        UseLess <"Kip!!",3>
    ; expands to:
        DB "Kip!",3

As compatibility convenience to make porting from different assemblers somewhat easier, there is alternative syntax, where the macro name is written at beginning of line (as if label, but MODULE part is NOT applied to macro name).

Macro name at beginning of line (docs_examples/s_macros.asm)

    LabelAsMacroName    MACRO  arg1?, arg2?
                            ld  a,arg1?
                            ld  hl,arg2?
                        ENDM

                    LabelAsMacroName 1,$1234
                ; expands to:
                    ld a,1 : ld hl,$1234

If some macro over-shadows regular instruction or directive name, the `@` character in front of instruction/directive name can be used to inhibit macro expansion.

Inhibit macro expansion operator (docs_examples/s_macros.asm)

    djnz    MACRO   arg1?
                dec c
                jr  nz,arg1?
                @djnz arg1? ; avoid self-reference and use real instruction
            ENDM

    1:      djnz    1B      ; macro replacement will be used here
    1:      @djnz   1B      ; original djnz instruction here

Macro arguments can be also passed by name, using the `name=value` syntax. In that case the arguments may be given in any order, but either all of them must be named, or none (mixing positional and named arguments in the same call is not allowed). Spaces around the `=` are permitted.

Named arguments (docs_examples/s_macros.asm)

        MACRO RECT x, y, dx, dy
          DB x, y, dx, dy
        ENDM

        RECT 1, 2, 3, 4                  ; classic positional call
        RECT x=1, y=2, dx=3, dy=4        ; named arguments
        RECT dx=3, dy=4, x=1, y=2        ; named arguments, in any order
        RECT x = 1, y = 2, dx = 3, dy = 4 ; spaces around '=' are allowed

## Structures

### What is it?

Structures can be used to define data structures in memory more easily. The name of the structure is set to the total size of the structure.

### Defining structure

A structure definition starts with: `STRUCT <name>[,<initial offset>]` and ends with `ENDS`. Structure definitions are local to the current module, but, as with labels, '@' can be used to override this.

Lines between STRUCT and ENDS should have the following format:

`membername pseudo-operation operands`

All fields are optional. Lines without label should start with whitespace.

When non zero `offset` is used, it acts as if BLOCK with `length` equal to `offset` was defined as first member of structure.

### STRUCT instructions

Between the STRUCT and ENDS pseudo-instructions the following instructions can be used:

BYTE [<defaultvalue>]
BYTE

  To define a one byte member. The defaultvalue is used when no initialisation value is given when the structure is declared. (DB and DEFB may be used instead of BYTE).

WORD [<defaultvalue>]
WORD

  To define a two byte member. The defaultvalue is used when no initialisation value is given when the structure is declared. (DW and DEFW may be used instead of WORD).

D24 [<defaultvalue>]
D24

  To define a three byte member. The defaultvalue is used when no initialisation value is given when the structure is declared.

DWORD [<defaultvalue>]
DWORD

  To define a four byte member. The defaultvalue is used when no initialisation value is given when the structure is declared. (DD and DEFD may be used instead of DWORD).

TEXT <length>[,{<DB-like operand(s)>}]
TEXT

  To define a member of the specified `length` of bytes. The operands are DB-like formatted, and the last byte explicitly defined will fill the remaining bytes reserved upon definition (zeroed when no value is specified at all).

  The default value can be modified with init values when structure is used, but then only the given bytes overwrite default content, NOT filling remainder of it.

  While the operands are DB-like formatted, they must be enclosed in curly braces. Also make sure the braces are not mistaken with the optional braces on the sub-structure boundaries, when using TEXT member in sub-structure, you may need to use curly braces for the sub-structure itself around of curly braces used for TEXT values (making the sub-structure braces mandatory in such case). The length can be from 1 to 8192 bytes (if you need even longer "text", use two members and split your arguments).

BLOCK <length>[,<fillbyte>]
BLOCK

  To define a member of the specified number of bytes. Arguments are set when defining the current structure, and are not part of init values when the structure is later used. ('#', DS and DEFS may be used instead of BLOCK).

  If `fillbyte` is omitted, the device memory content in the block area is preserved (not zeroed).

ALIGN [<expression>[, <byte>]]
ALIGN

  To align the offset. If the expression is omitted, 4 is assumed. ('##' May be used instead of ALIGN).

  If the byte is omitted, device memory content is preserved (not zeroed).

<structure name> [<init values>]

  It is possible to nest structures, and give new defaults for the BYTE and WORD members.

### Usage of defined structure

`[<label>] <struct_name> [<initial values>]` will emit full structure into machine code, either using default values from structure definition, or overriding them with explicit value from the <initial values> list. In initial values you can use curly brackets {} to group particular initial values for particular sub-structure, any missing values in particular sub-structure init-list are set up by default values of particular field. See "SDOT" example below or tests/struct asm files for more examples.

`<label> <struct_name> = <expression>` will only set up <label>.<struct_field> labels starting from designed address provided by expression, but there will be no machine code emitted (and current address "$" will not advance).

The initial values inside curly braces block can now span over multiple lines with new line ending current value expression (the comma after expression is optional in such case). Please bear in mind this is pushing original sjasmplus architecture beyond its original design (multi-line definitions) and may fail in complex scenarios (for example the "dot-repeater" macro mechanics will repeat only the remainder of current line, ignoring the curly-braces block spanning across following lines). If you have example of code which fails but you believe it's reasonable use case, please open the issue on github with such example, so it is possible to improve the feature in future (the dot-repeater is "won't fix" case, but otherwise the feature should work reliably).

### Examples

docs_examples/c_structures.asm

    	STRUCT SCOLOR
    RED	BYTE 4
    GREEN	BYTE 5
    BLUE	BYTE 6
    	ENDS

This is identical to:

    SCOLOR		EQU 3 ; length
    SCOLOR.RED	EQU 0 ; offset
    SCOLOR.GREEN	EQU 1 ; offset
    SCOLOR.BLUE	EQU 2 ; offset

docs_examples/c_structures.asm

    	STRUCT SDOT
    X	BYTE
    Y	BYTE
    C	SCOLOR 0,0,0 ; use new default values
    	ENDS

This is identical to:

    SDOT		EQU 5 ; length
    SDOT.X		EQU 0 ; offset
    SDOT.Y		EQU 1 ; offset
    SDOT.C		EQU 2 ; offset
    SDOT.C.RED	EQU 2 ; offset
    SDOT.C.GREEN	EQU 3 ; offset
    SDOT.C.BLUE	EQU 4 ; offset

docs_examples/c_structures.asm

    	STRUCT SPOS,4
    X	WORD
    Y	BYTE
    	ALIGN 2
    AD	WORD
    	ENDS

This is identical to:

    SPOS	EQU 10 ; length
    SPOS.X	EQU  4 ; offset
    SPOS.Y	EQU  6 ; offset
    SPOS.AD	EQU  8 ; offset

docs_examples/c_structures.asm

When a structure is defined it is possible to declare labels with it

    COLOR SCOLOR

This is identical to:

    COLOR
    COLOR.RED   BYTE 4
    COLOR.GREEN BYTE 5
    COLOR.BLUE  BYTE 6

Note the default values.

Or without label:

    COLORTABLE
      SCOLOR 0,0,0
      SCOLOR 1,2,3
      SCOLOR ,2
      ; etc.

This is identical to:

    COLORTABLE
      BYTE 0,0,0
      BYTE 1,2,3
      BYTE 4,2,6
      ; etc.

    DOT1 SDOT 0,0, 0,0,0     ; or 0,0,0,0,0 or {0,0,{0,0,0}}

Only BYTE and WORD members can be initialised.

The resulting labels can be used as any other label:

      ld b,(ix+SCOLOR.RED)
      ld a,(COLOR.GREEN)
      ld de,COLOR
      ; etc.

docs_examples/st_usage_example.asm

    	STRUCT BIN_FILE_MAP, 256
    value1	BYTE
    value2	WORD
    	ENDS

            ORG  0x8000
    binData BIN_FILE_MAP = $        ; set up label values only (no bytes)
            INCBIN "some_data.bin"  ; load the bytes from file instead

            ; using the data through struct definition
            ld  a,(binData.value1)
            ld  hl,(binData.value2)

This is identical to:

    BIN_FILE_MAP            EQU 259      ; length
    BIN_FILE_MAP.value1     EQU 256      ; offset
    BIN_FILE_MAP.value2     EQU 257      ; offset
    ; labels to access binary data loaded by INCBIN
    binData                 EQU 0x8000   ; address
    binData.value1          EQU 0x8100   ; address
    binData.value2          EQU 0x8101   ; address

docs_examples/st_text.asm

            STRUCT BLOCK_HEADER
    length      WORD    BLOCK_HEADER
    type        BYTE    $AB
    name        TEXT    10, { "none", 32, '!' } ; will produce "none !!!!!" default data
                        ; because this is definition of the field, here last byte is "filler"
    datastart   WORD
    datalen     WORD
    checksum    BYTE    $CC
            ENDS

            ORG  0x8000
    head1   BLOCK_HEADER {      ; Multi-line initialization requires curly braces.
        , ,                     ; Keeping default length and type by specifying empty values.
        { 'New',                ; The final `name` data will be "New Name!!"
            32,                 ; overwriting only 8 bytes of default data.
            "Name" },           ; The last "e" is NOT "filler" in the non-default value.
            $8000, $1234        ; Explicit datastart and datalen values.
    }                           ; End of initial data block ("checksum" keeps default value).

    ; machine code (struct data):
    ; 12 00 AB 4E 65 77 20 4E 61 6D 65 21 21 00 80 34 12 CC
    ; = { $0012, $AB, "New Name!!", $8000, $1234, $CC }

Do not use the offset labels in indirections like:

    LD A,(SDOT.X)

This will conflict with futher 'improvements' ;-)

If this is absolutely necessary (why?) use something like this:

    LD A,(+SDOT.X)

## Lua scripting

### Why?

Why is scripting engine as Lua embedded to the assembler? Answer is simple: it can be used to add extra features by users. And Lua is small enough, fast and powerful scripting engine.

Sjasmplus currently includes Lua 5.5.0 (since v1.21.1). The upgrade from Lua 5.1 in v1.20.0 was done along replacing tolua++ with LuaBridge2.6 and while there was lot of effort to maintain original Lua bindings to sjasmplus, there were few changes to the API.

If your old source does not work any more after upgrade, please carefully check against the bindings documentation below.

Check if your code does use only official API, and if you were using secret extra arguments, check the test files and examples how those lua scripts were adjusted to build with v1.20.0. The bindings API documentation has been cleaned up, some API extended and has better test-coverage to make it less likely to change in the future.

Also please mind the breaking changes of the Lua itself, and adjust your scripts to Lua 5.5 syntax and library functions, there is example below with common issues already encountered (open github issues with others so we can add them to docs).

### How to use?

You must use LUA and ENDLUA pseudo-ops.

Hello World!

        LUA
            print ("Hello World!")
        ENDLUA

### SjASMPlus functions bindings to Lua

From Lua you can control some variables and use functions of the compiler. It's expected to rely on `_c`, `_pc` and `_pl` bindings to use regular pseudo-operations (directives), the direct bindings are either convenience for specific ones or exist due to historical reasons. Future extensions of bindings will mostly focus on giving access to internal state or functionality which is not easily accessible through regular asm directives.

Complete list:

[integer] _c("expression")
_c(..)

  Calculate expression using calculator of the compiler (32-bit only). Example: `val = _c("SOMELABEL+12")`

[void] _pc("code")
_pc(..)

  Parse string of Z80 assembly. Example: `_pc("ADD A,B")`

[void] _pl("label code")
_pl(..)

  Parse line of Z80 assembly. Example: `_pl("SOMELABEL ADD A,B")`

  This binding makes almost all pseudo-instructions (directives) available from Lua script with all options and features. In the future the bindings API will be mostly frozen, extended only in case where using `_pl(...)` is too cumbersome.

[integer] sj.calc("expression")
sj.calc(..) _c(..)

  See _c

[void] sj.parse_code("code")
sj.parse_code(..) _pc(..)

  See _pc

[void] sj.parse_line("label code")
sj.parse_line(..) _pl(..)

  See _pl

[void] sj.error("message","bad_value" = nil)
sj.error(..)

  Print error message.

[void] sj.warning("message","bad_value" = nil)
sj.warning(..)

  Print warning message.

[boolean] sj.file_exists("filename")
sj.file_exists(..)

  Check for file exists.

[string] sj.get_define("name", "include_macro_args" = false)
sj.get_define(..)

  Get define value, returns nil if define is not found. Can optionally search also macro arguments (with higher priority).

[boolean] sj.insert_define("id", "value" = "")
sj.insert_define(..)

  Add new define. Returns true if the define is new, false when define already existed and value was replaced or when id is invalid.

[integer] sj.get_label("name")
sj.get_label(..)

  Get label address. Returns -1 if name is invalid, 0 when not found or has still undefined value (you can use also `_c("name")` to get symbol value and do some calculations with it).

[boolean] sj.insert_label("name", address)
sj.insert_label(..)

  Add new label. Returns true when label was successfully inserted (or value modified first time in next assembling pass), false when name is invalid or label was already defined. Does apply current namespace and label rules to provided "name".

[integer] sj.current_address
sj.current_address

  Current address, writing new value to it is same as `ORG value`.

[integer] sj.pass
sj.pass

  Read-only variable. Number of current pass (1 to 3). Pass 1 is very first pass (expect forward declared labels being not known to code yet). Pass 2 is pass when all symbols should be already defined and final layout of code and data should be produced, but machine code is not emitted yet (if ever multi-pass feature happens, value 2 will be returned for all intermediate passes until the code layout is stable and ready for final pass). Pass 3 is final pass when not only everything should match pass 2 but also machine code is being emitted and virtual device memory is being updated and could be read back.

[integer] sj.error_count
sj.error_count

  Read-only variable. Count of Errors.

[integer] sj.warning_count
sj.warning_count

  Read-only variable. Count of Warnings.

[void] sj.exit(errorcode = 1)
sj.exit(..)

  Shutdown the compiler.

[void] sj.add_byte(byte)
sj.add_byte(..)

  Add byte to output (or to memory) and increase sj.current_address

[void] sj.add_word(word)
sj.add_word(..)

  Add word to output (or to memory) and twice increase sj.current_address

[integer] sj.get_byte(address)
sj.get_byte(..)

  Get byte from memory. Works only in real device emulation mode and only in last assembling pass.

[integer] sj.get_word(address)
sj.get_word(..)

  Get word from memory. Works only in real device emulation mode and only in last assembling pass.

[string] sj.get_device()
sj.get_device()

  Return current emulating device's identifier. Returns "NONE" if no emulation mode.

[string] sj.get_modules()
sj.get_modules()

  Return current modules namespace. Returns an empty string if not in a module.

[boolean] sj.set_device("id" = "NONE", ramtop = 0)
sj.set_device(..)

  Set current emulating device's identifier. Returns false if requested device id is not found and current device set is lost ("NONE" is set).

[boolean] sj.set_page(number)
sj.set_page(..)

  Set page "number" to the current slot. Works as pseudo-op PAGE.

[boolean] sj.set_slot(number_or_address)
sj.set_slot(..)

  Set current slot to "number". Works as pseudo-op SLOT.

  (since v1.20.0) You can also use starting address of slot instead, ie. for ZX128 `sj.set_slot(0x8000)` is alias of `sj.set_slot(2)`.

[integer] sj.get_page_at(address = sj.current_address)
sj.get_page_at(..)

  Get current page at "address". Returns LABEL_PAGE_OUT_OF_BOUNDS (0x7F80) or LABEL_PAGE_ROM (0x7F00) for invalid address or in no device mode.

[void] sj.shellexec("programname")
sj.shellexec(..)

  See pseudo-op SHELLEXEC.

[boolean] zx.trdimage_create("trd_name", "label" = "")
zx.trdimage_create(..)

  Creates empty TRD image file.

[boolean] zx.trdimage_add_file("trd_name", "somenameC", startaddress, length, autostart = -1, replace = false)
zx.trdimage_add_file(..)

  Save block of memory to TRD image file. Work only in real device emulation mode.

[boolean] zx.save_snapshot_sna("filename.sna", startaddressofprogram)
zx.save_snapshot_sna(..)

  Save snapshot of memory in SNA format. Works only in real device emulation mode and only for ZXSPECTRUM48 and ZXSPECTRUM128..

### Third-party embedded libraries

None at this moment.

Since v1.20.0 the sjasmplus integrates Lua 5.4.4 (now 5.5.0).

The libraries included in older versions of sjasmplus: lpack.c, bit (bitwise operators) and hex (convert to/from hexa numbers) are now all part of the standard Lua 5.4.

Refer to Lua documentation for full list of functions and detailed help.

For small example check the test `tests/lua_examples/trivial/lua_standard_libs.asm`

### Third-party external libraries

The default build method of sjasmplus does link Lua interpreter statically ‐ this is not compatible with dynamically loaded extensions (shared libraries). You may encounter error similar to this:

`error loading module 'lpeg' from file '/usr/lib/x86_64-linux-gnu/lua/5.4/lpeg.so'(0): error: [LUA] undefined symbol: lua_gettop`

You can rebuild the sjasmplus configured to use the OS shared Lua library by reconfiguring the build:

Configure and build sjasmplus to use shared lib for Lua

    # building with make:
    $ make clean && USE_BUNDLED_LUA=0 make
    # test final binary with your extension and install it where you wish

    # building with cmake:
    $ cmake -DSYSTEM_LUA=ON -DCMAKE_BUILD_TYPE=Release -S . -B build
    $ cmake --build build
    $ cmake --install build

This should make Lua interpreter compatible with those extensions and with a bit of luck it will be still compatible with sjasmplus bindings ‐ allowing you to use also the extension API in your code.

### Example

Variables doesn't clear in new passes of the compiler

        LUA PASS1
           v = 1
        ENDLUA

        LUA PASS2
           print (v)
    -- out to console: 1
           v = v + 1
        ENDLUA

        LUA PASS3
           print (v)
    -- out to console: 2
        ENDLUA

To generate some code you need to generate it in all passes

        LUA ALLPASS
            _pl("ClearScreen LD (.savesp+1),SP")
            _pc("LD SP,16384+6144")
            _pc("LD HL,0")
            for i = 32768, 38912, 2 do
                _pc("PUSH HL")
            end
            _pl(".savesp: LD SP,0")
            _pc("RET")
        ENDLUA

        LUA PASS2 ; if you fully understand what you are doing
            sj.add_byte(123) -- and you need emit bytes in other mode
        ENDLUA ; luamc-ok ; you can suppress warning here or at start of block

Declare function and use it

         LUA
             function savetape_mytype(filename, startaddress)
                 local fp
                 fp = assert(io.open(filename, "wb"))
                 for i = 16384, 32767, 4 do
                     assert(fp:write( string.pack("bbbb",
                                    sj.get_byte(i),
                                    sj.get_byte(i+1),
                                    sj.get_byte(i+2),
                                    sj.get_byte(i+3)) ))
                 end
                 assert(fp:flush())
                 assert(fp:close())
             end
         ENDLUA

     ;somewhere in your program
         LUA
             savetape_mytype("tapefiles/myprogram.tape", _c("StartGameLabel"))
         ENDLUA

Simple sample :)

    	LUA
    -- Function reads number from file <fname>, increases it, creates define "BUILD" with the number and saves the number to <fname>.
    -- With this function you can control count of compilations.
    	function increase_build(fname)
    		local fp
    		local build
    		fp = assert(io.open(fname, "rb"))
    		build = tonumber(fp:read("*all"))
    		assert(fp:close())
    		if type(build) == "nil" then
    		    build = 0
    		end
    		build = build + 1;
    		sj.insert_define("BUILD", build)
    		fp = assert(io.open(fname, "wb"))
    		assert(fp:write( build ))
    		assert(fp:flush())
    		assert(fp:close())
    	end

    -- Before using you must create empty file "build.txt"!
    	increase_build("build.txt")

    -- Creates define "TIME" with current time
    	sj.insert_define("TIME", '"' .. os.date("%Y-%m-%d %H:%M:%S") .. '"')
    	ENDLUA

    ; print to console our time and build number
    	DISPLAY "Build time: ", TIME
    	DISPLAY "Build number: ", /D, BUILD

Common issues with upgrade to Lua 5.5

      LUA ALLPASS
        x = 2.3 ^ 4.5             -- old syntax: math.exp(2.3, 4.5)
        _pc("db " .. math.floor(x))
        _pc("db " .. 35//7)       -- old syntax: 35/7
          -- floating point values are now formatted rigorously with ".0" from the Lua
          -- which leads to error in sjasmplus parser: Unexpected: .0
          -- and requires to enforce integer type already at lua side
          -- you can use either `math.floor()` function, or integer
          -- operators like integer divide `//` or left shift `1<<7`

        -- the floating point values (like "12.0") are silently truncated since v1.20.1 (to "12"),
        -- but v1.20.0 errors out on these, unless you patch the lua script to produce integers
      ENDLUA

## SAVENEX guide

### NEX File Format

NEX is binary format for ZX Spectrum Next, aiming to provide simple delivery of software for the platform. For file format details check https://specnext.dev/wiki/NEX_file_format. In short it is header + loading screen + like-snapshot binary and remaining resources appended after.

Sjasmplus currently supports official V1.2 of NEX file format (see wiki for details). The "V1.3" features are currently unofficial extension which does not work with regular NextZXOS distro and require special loader (nexload2).

As such, the SAVENEX commands are available only in ZXSPECTRUMNEXT device emulation mode, see DEVICE.

As the file is designed for self-contained distribution of whole applications/games, its configuration and assembling is a bit too complex for single directive, and the configuration is instead divided into multiple commands, and the assembling goes through multiple stages, so some commands must be used in correct sequence.

While the format technically allows to include multiple screen types data, they are all loaded at the beginning over each other, so it makes sense to provide only single loading screen (sjasmplus enforces that).

### Detailed description of each SAVENEX command

SAVENEX OPEN <filename>[,<startAddress>[,<stackAddress>[,<entryBank16k 0..111>[,<fileVersion 2..3>]]]]

  Opens a NEX file, defines start address, stack address and 16k bank to be mapped at 0xC000 before code is executed (if values are omitted, start address is zero = no start, stack address is 0xFFFE, entryBank is zero, fileVersion is 2).

  Only single NEX file can be open at the same time, and to finalize the header content the command CLOSE has to be used (does auto-close if source ends).

  Entry bank is number of 16k bank (0..111), not native 8k page, default is zero, i.e. the default memory map is identical to ZX 128 (ROM, RAM banks 5, 2 and 0).

  Make sure your new stack has at least tens of bytes available as those will be used already by the NEX loader before executing your entry point (although released back).

  The fileVersion can be 2 (official NEX V1.2) or 3 (unofficial extension V1.3), which will enforce the specified version of file. Otherwise the file is V1.2 by default and will auto-switch to V1.3 when some V1.3 feature is configured/used. When version 2 is enforced, any usage of V1.3 feature will emit error.

SAVENEX CORE <major 0..15>,<minor 0..15>,<subminor 0..255>

  Set minimum required Next core version, can be set any time before CLOSE.

SAVENEX CFG <border 0..7>[,<fileHandle 0/1/$4000+>[,<PreserveNextRegs 0/1>[,<2MbRamReq 0/1>]]]

  Set border colour (during loading), whether the machine should be set to default state (PreserveNextRegs = 0 = default), if the app requires extended RAM (224 8k pages) and how the file handle of the NEX file should be treated: 0 = default = close, 1 = keep open and pass in BC, $4000..$FFFE = keep open, and write into memory at provided address (after entry bank is paged in). This can be set any time before CLOSE.

SAVENEX CFG3 <DoCRC 0/1>[,<PreserveExpansionBus 0/1>[,<CLIbufferAdr>,<CLIbufferSize>]]

  All of these are NEX format V1.3 features and using "CFG3" command will change the version to V1.3 automatically (if not specified by OPEN).

  DoCRC: default = 1, sjasmplus will checksum the file (if you know you will be further patching file afterwards, switch it off, otherwise keep 1).

  PreserveExpansionBus: default = 0, 0 = the NEX loader will disable expansion bus through NextReg $80 / 1 = the NEX loader will not do anything.

  CLIbufferAdr, CLIbufferSize: default = [0, 0], address and size of buffer for NEX loader to copy the arguments line into. The buffer is copied after the "entryBank" is mapped, so you can allocate buffer in your entryBank. The loader/format has hard limit 2048 bytes, the argument line will be truncated if longer. If your reserved buffer is shorter than 2048 bytes, the copy will be also truncated to your buffer size only. If not truncated, the argument line can end with any of these: Enter (13), colon or zero byte (truncated has no terminator character, just fills the full buffer).

SAVENEX BAR <loadBar 0/1>,<barColour 0..255>[,<startDelay 0..255>[,<bankDelay 0..255>[,<posY 0..255>]]]

  Loading-bar related setup ("colour" usage depends on screen mode), can be set any time before CLOSE.

  The posY argument will apply only to V1.3 Layer2 screens in 320x256 and 640x256 resolution (then defaults to 254 = bottom of screen, 2px tall bar).

SAVENEX PALETTE NONE

SAVENEX PALETTE DEFAULT

SAVENEX PALETTE MEM <palPage8kNum 0..223>,<palOffset>

SAVENEX PALETTE BMP <filename>

  This is optional command to set palette in alternative way ahead of SCREEN command, with higher priority (if the PALETTE command is used, following SCREEN commands will ignore the palette arguments and keep the palette defined by this command). You can be use it between OPEN and SCREEN command. But appropriate SCREEN type supporting palette must be defined by SCREEN command too.

  Palette consists of 512 bytes (256 palette items from index 0), in 9b colour format: first byte is %RRRGGGBB, second byte is %P000000B (P is priority flag for Layer 2 colours).

  The DEFAULT palette will generate colour values from the colour index, the same way how default Layer 2 palette is initialized on the ZX Spectrum Next.

  The NONE palette will force the "no palette" flag even if SCREEN command later does specify some palette (it will be still ignored).

SAVENEX SCREEN L2 [<Page8kNum 0..223>,<offset>[,<palPage8kNum 0..223>,<palOffset>]]

  Layer 2 loading screen, can be used between OPEN and first AUTO/BANK command.

  Palette consists of 512 bytes (256 palette items from index 0), in 9b colour format: first byte is %RRRGGGBB, second byte is %P000000B (P is priority flag for Layer 2 colours).

  Image data are 48kiB block of memory, the loader will use always banks 9..11 to display it (8k pages 18..23), but if you will prepare the data there, it will be also re-saved by AUTO command, so either use other banks, and overwrite them with valid data/code after using the SCREEN command, or reset pages 18..23 to zero after SCREEN.

  If no memory address is specified, the pages 18..23 are stored in file, and if no palette address is specified, no-palette flag is set in NEX file.

SAVENEX SCREEN L2_320 [<Page8kNum 0..223>,<offset>[,<palPage8kNum 0..223>,<palOffset>]]

SAVENEX SCREEN L2_640 [<Page8kNum 0..223>,<offset>[,<palPage8kNum 0..223>,<palOffset>]]

  Works same way as "L2" variant, but will set up screen for new resolutions 320x256x8 and 640x256x4. The difference is that the required image data are 80kiB (five 16kiB banks; equals ten 8kiB pages), the banks used to show the screen in loader are 9..13 (8k pages 18..27 - that's also the default address of data if not specified) - avoid these in regular banks stored in the file.

  These are NEX format V1.3 features and using them will change the version to V1.3 automatically (if not specified by OPEN). This command doesn't allow to set specific palette offset value (too lazy to add it, use BMP variant if you really need it).

  The data has to be already in correct format and organized as if displayed (the "transposed" bitmap where +1 address goes to pixel below, and +256 goes to pixel on the right (or pair of pixels in case of 640x256x4bpp mode), this command will just dump them into file "as is".

  The loading bar colour byte will be also used "as is", which in 4bpp mode means the byte does define pair of 4 bit pixels, i.e. if you want solid-colour "3" loading bar in 4bpp mode, define it as value $33. Keep also in mind the default loading bar position is Y=254, which on most of the TV/LCD displays is outside of visible range, the reasonably "safe" (visible on almost all of the screens) resolution is about 288x224 (+16px around PAPER area, +24px is visible on many displays too), you may want to organize your screen in a way to show all important information within this area, and make the rest "unimportant" so it can hide beyond the edge of screen.

SAVENEX SCREEN LR [<Page8kNum 0..223>,<offset>[,<palPage8kNum 0..223>,<palOffset>]]

  LoRes (128x96) loading screen, can be used between OPEN and first AUTO/BANK command.

  Palette is similar to Layer 2 mode, just LoRes mode doesn't have priority bit.

  Image data are 12288 bytes memory block - either consecutive block if specific address is provided, or without address the actual bank 5 memory is stored (taking 6144 bytes from address 0x4000 and 6144 bytes from address 0x6000).

SAVENEX SCREEN BMP <filename>[,<savePalette 0/1>[,<paletteOffset 0..15>]]

  Only small subset of BMP files can be used: 256x192 (Layer 2) or 128x96 (LoRes), indexed (8bit image data with palette) and palette data will be truncated to 3:3:3 color space directly (no smart colour quantization or dithering is applied). And the file must be uncompressed.

  For V1.3 NEX files you can include also 320x256 and 640x256 files (Layer 2), the 640x256 should be also 8bit indexed, but only 4 bits of pixel data will be used (256 colour palette is legitimate and will be stored "as is" in NEX file). These two new modes can also include paletteOffset argument 0..15 (does not apply to V1.2 BMP files above), the offset is added to top four bits of pixel value.

  The BMP will be included as loading screen, can be used between OPEN and first AUTO/BANK command.

  By default the palette from BMP file is used, but you can override that by savePalette = 0.

SAVENEX SCREEN (SCR|SHC|SHR) [<hiResColour 0..7>]

  ULA/Timex modes loading screen, can be used between OPEN and first AUTO/BANK command.

  The actual bank 5 memory (pages 10..11) is stored as if the image is displayed, in these modes the palette can't be specified.

  SCR is classic ZX 6912 bytes long screen from address 0x4000 (page 10 is used, even if the slot 1 is modified to other page, so you must prepare the image "in place").

  SHC and SHR are Timex HiColor (8x1 attribute) and HiRes (512x192 bitmap) modes, prepare data "in place", i.e. 6144 bytes into page 10 and 6144 bytes into page 11 (0x4000 and 0x6000 addresses in default memory setup). For HiRes mode you should specify ink colour (the paper is complement of ink).

SAVENEX SCREEN TILE <NextReg $6B>,<NextReg $6C>,<NextReg $6E>,<NextReg $6F>[,<AlsoStoreBank5 0/1 = 1>]

  NEX V1.3 tilemap loading screen, can be used between OPEN and first AUTO/BANK command.

  To define palette use the PALETTE command.

  The image data are stored as regular Bank 5 of the NEX file (which is the first bank to be loaded by loader), depending on AlsoStoreBank5 value (default 1), this SCREEN command will also execute BANK 5 command to store the image data.

  The NextRegisters $6B, $6C, $6E and $6F should be enough to specify any variant of tilemap mode, so the precise sub-type and image data layout is under control of user, the sjasmplus doesn't enforce any particular configuration.

  If you want to use also COPPER command, use it either ahead of the SCREEN TILE command, or use "AlsoStoreBank5 = 0" to delay the storage of Bank 5 data. In such case you must then later explicitly store the Bank 5 as regular bank, either with BANK or AUTO command.

SAVENEX COPPER <Page8kNum 0..223>,<offset>

  The NEX format V1.3 feature. Can be used after OPEN and before first AUTO or BANK command (the copper data are stored between screen and bank data).

  Exactly 2048 bytes are stored (full Copper memory), and the loader will start the copper code in mode %01 (reset CPC to 0, then starts the copper), so the copper code will wrap around infinitely after the 1024th instruction executed. The copper is started after the screen block is loaded and displayed.

SAVENEX BANK <bank16k 0..111>[,...]

  Can be used after OPEN or SCREEN and before CLOSE, but the 16ki banks must be saved in correct order: 5, 2, 0, 1, 3, 4, 6, 7, 8, 9, 10, ..., 111

SAVENEX AUTO [<fromBank16k 0..111>[,<toBank16k 0..111>]]

  Can be used after OPEN or SCREEN and before CLOSE. The sjasmplus will save every 16k bank containing at least one non-zero byte; detected in the correct order (automatically will save every non-zero 16k bank detected in the correct order (automatically starting from first possible bank after previous BANK/AUTO commands, or from provided "fromBank").

  For "fromBank" value use the specified order above in BANK command, i.e. 5, 2, 0, ...

SAVENEX CLOSE [<fileToAppend>, ...]

  Can be used after OPEN. The currently open NEX file will be finalized (header adjusted), and optional extra files appended to the end of NEX file.

### Examples

docs_examples/s_savenex_examples.asm

Creating NEX file which will have Layer2 loading screen (stripes), progress bar, and will enter infinite loop with calling stack (used by IM 1 interrupt handler) visible on the Layer 2 screen.

        DEVICE ZXSPECTRUMNEXT
        ORG $7E00
    start:  ei : jr $           ; app code entry point, BC = NEX file handle
        ; Layer2 screen (top 1/3 defined, bottom of it will be used also as "visible" stack)
        ORG $C000 : DUP 64*32 : DB $90,$91,$92,$93,$94,$95,$96,$97 : EDUP

        ; write everything into NEX file
        SAVENEX OPEN "example.nex", start, $FFFE, 9  ; stack will go into Layer2
        SAVENEX CORE 2, 0, 0        ; Next core 2.0.0 required as minimum
        SAVENEX CFG 4, 1, 0, 1      ; green border, file handle in BC, reset NextRegs, 2MB required
        SAVENEX BAR 1, $E0, 50, 25  ; do load bar, red colour, start/load delays 50/25 frames
        SAVENEX SCREEN L2 0, 0      ; store the data from C000 (page 0, offset 0), no palette
        SAVENEX BANK 5, 100, 101    ; store the 16ki banks 5 (contains the code at 0x7E00), 100, 101
        SAVENEX CLOSE               ; (banks 100 and 101 are added just as example)

## Source Level Debugging (SLD) data

### What is it?

SLD data are extra "tracing" data produced during assembling for debuggers and IDEs, similar to "map" files already supported by sjasmplus ( LABELSLIST and CSPECTMAP).

The debugger can read these data, and with non-tricky source producing machine code with correct device memory mapping, the debugger can trace the origins of every instruction back to the original source code line, and display the source instead/along the disassembly view (the "map" files mentioned above provide only list of labels which is usually already super helpful, but can't track the source origins of each instruction).

The original impulse and working patch for this feature came from Chris Kirby, adding the single-instruction-step feature to his development tools: Next Development System (currently in 2019 under heavy development).

### Usage

On the sjasmplus side all you need is to add another command line option when starting the assembler.

    prompt$ sjasmplus --sld=project.sld.txt file1.asm file2.asm

This will produce along regular files also file `project.sld.txt` containing the tracing data. The file format is text-like, so the content can be viewed in any text editor, but it's supposed to be processed by the debugger.

The SLD data are being exported only for machine code produced within one of the virtual devices (see DEVICE). And the accuracy of the data directly depends on the state of the virtual device at the moment when the particular instruction is assembled, see next section for further advice how to get best tracing data.

If the option `--sld` without explicit filename is used, the first input source filename will be copied and its extension changed to `.sld.txt`, which should work well for single-main file type of projects.

### How to write "non tricky" source

For best results (tracing data covering all your instructions and making source-level debugging available all the time):

Generate machine code only with instructions, i.e. while `db 0` in source will produce `nop` instruction, the tracing data for such `nop` will be missing. Only source line containing the "nop" will emit both the zero byte into machine code and tracing data.

Keep the memory map of the virtual device as it will be set at run-time when writing particular code. While using only regular ORG to place the code in desired memory area, keeping only the modified area with memory pages mapped-in is enough to get correct SLD data - but when using DISP directive to generate code in displaced way, you need to map-in correct pages in both memory areas (where the code is currently assembled, and where it is supposed to be operational at run-time - currently impossible if the two areas share the same SLOT area).

To map-in correct pages you can use directives: SLOT, PAGE, MMU and ORG

Only labels defined in regular way get also the memory-page data in SLD file, EQU values have page information only when explicitly given upon definition:

    regularLabel:   nop             ; current page exported to SLD data
    equLabel        EQU   $8000     ; only value without page is exported
    equPagedLabel   EQU   $E123, 4  ; value with page 4 is exported
    varSymbol       =     14        ; =/DEFL labels are omitted completely

Re-using the same memory area (page + address) for two different pieces of code (for example by having multiple routines targeting the same address with DISP, and run-time code uploading the correct variant dynamically at the destination) will highly likely confuse the debugger, producing two source-origins data for the identical address. You may want to avoid this, especially with early version of tools not being able to resolve such ambiguity in reasonable way (the sjasmplus will generate tracing data ok even in this case, but they are tricky to interpret).

If you are using the colons to put multiple instructions on single line, verify the debugger can cope with the tracing data containing full line + column begin/end information, the simple/early tools will likely highlight only whole line of source. For a start keeping single instruction per line may keep things simple.

The various code generators written in macros or Lua scripts will produce accurate tracing data in case of macros, and condensed (pointing at "ENDLUA" line) data for Lua generators, but it still depends on the debugger if it can display both top level source code triggering the generator and source lines containing definitions of particular instructions.

### SLD File Format definition (version "1")

The file format is still under development. If you are working on support in debugger, stay in touch with us, so we can discuss any modifications to current format with you.

The SLD data is text-file (sjasmplus is using UNIX-like newlines 0x0A, but parsers should rather cope with any of common EOL scheme). The general format is CSV-like using pipe character as delimiter between fields.

If the first field is empty, the line is one of the special control lines - the second field selects type of control line. If also the second field is empty, the remaining part of line should be ignored (comment line). Currently only one type of "control line" exists, the "SLD.data.version" (third field is integer number):

    |SLD.data.version|1
    ||anything ... comment-like line

The file format version line should be always first line of the SLD data file.

The regular tracing data lines have fixed amount of fields: eight. The seventh field contains single uppercase letter defining type of the line, which affects sub-variants of format for eighth field, but first six fields share the same formatting and meaning across all types of regular lines:

    <source file>|<src line>|<definition file>|<def line>|<page>|<value>|<type>|<data>

`<source file>` - file name of top-level source emitting the instruction/label/device.

`<src line>` - line and characters in top-level source file, the precise format is "`<line>[:<column begin>[:<column end>]]`" where first number is line number (starting at 1). The two following numbers delimited by colon are optional, representing the column where the segment starts/ends at the line. The column values are starting from 1 too, the "end" column is pointing beyond the current segment. The columns are in "bytes", i.e. tabulator does increase the column value by +1 only, and the "end" may actually point well beyond "strlen(line)".

`<definition file>` - file name of the source defining the particular instruction (for example where the instruction inside MACRO was originally defined). If empty, the definition-file name is identical to `<source file>` (common case for single-source projects).

`<def line>` - zero when there is no extra "definition" of instruction involved. When non-zero, the format is identical to `<src line>`, but with regard to the file specified in `<definition file>` field.

`<page>` - number of memory page where the `<value>` address points to, or -1 when value is not memory address.

`<value>` - any 32 bit integer value (EQU), but when representing memory address, it is full 16 bit value from Z80 address space (the top bits beyond the memory page size contains the number of "slot" where the instruction/label resides).

`<type>` - single uppercase letter representing type of line, current types: T (instruction Trace), L (Label), F [deprecated, parse L], D [deprecated, parse L], Z (device memory model), K (keyword comment, see `SLDOPT COMMENT`).

`<data>` - extra data specific for particular line type. Empty for "T" lines. For "L" a list of items separated by comma, where first three items represent sub-parts of symbol name: module name, main label, local label (sub-part may be empty), optional remaining items are usage-traits giving away various internal info about usage of symbol (currently exported traits: +local, +equ, +macro, +reloc, +reloc_high, +used, +module, +endmod, +struct_def, +struct_data, +sizeof). For type "K" the comment containing one of they defined keywords is present.

For type "Z" the data field contains string describing the device memory model which is being selected in source and should be applied for following SLD data lines. The memory model string format is:

    pages.size:<page size>,pages.count:<page count>,slots.count:<slots count>[,slots.adr:<slot0 adr>,...,<slotLast adr>]
    // unsigned <page size> is also any-slot size in current version.
    // unsigned <page count> and <slots count> define how many pages/slots there are
    // uint16_t <slotX adr> is starting address of slot memory region in Z80 16b addressing

Example of SLD file

    |SLD.data.version|1
    || ZX Spectrum Next device description:
    toplevel.asm|59||0|-1|-1|Z|pages.size:8192,pages.count:224,slots.count:8,slots.adr:0,8192,16384,24576,32768,40960,49152,57344
    || label "main" points to 32768, with page 14 mapped-in (defined in toplevel.asm:62)
    || the label "main" is used by other code (IFUSED-like), and has relocation data
    toplevel.asm|62||0|14|32768|L|,main,,+reloc,+used
    || instruction opcode at 32768 (page 14) was created by toplevel.asm:64
    toplevel.asm|64||0|14|32768|T|
    || instruction opcode at 32769 (p 14) was created by toplevel.asm:67
    || (but it is a line using macro, the instruction was defined by toplevel.asm:52)
    toplevel.asm|67||52|14|32769|T|
    || instruction opcode at 32770 (p 18) was created by toplevel.asm:68:12-24
    toplevel.asm|68:12:24||0|18|32770|T|
    || instruction opcode at 32771 (p 18) was created by toplevel.asm:68:24 (till EOL)
    toplevel.asm|68:24||0|18|32771|T|
    || "PORT_NUMBER EQU 254" defined at toplevel.asm:69
    toplevel.asm|69||0|-1|254|L|,PORT_NUMBER,,+equ
    || label+instruction emitted from toplevel.asm:70, but defined in include.asm:3
    || with macro label "0>macro_defined_in_include_asm" (source ".macro_defined_in_include_asm:")
    toplevel.asm|70|include.asm|3|37|40976|L|,0,macro_defined_in_include_asm,+macro,+used
    toplevel.asm|70|include.asm|3|37|40976|T|