← News

FantASM 2.0.0 is available

September 4, 2026FantASM

FantASM 2.0.0 is available to download. The editor now answers with what the build answers. Libraries get a real interface, tests get spans and checksums, and 59 new diagnostic codes say what is wrong rather than sharing one number between five faults.

This is a major version and the breaking changes are worth reading before you upgrade — most of them refuse source that used to assemble, quietly, into something you did not mean.

Breaking

  • The machine is a device, not a target. -d/--device, !device, [assembler] device and {_device} replace the target spellings. Each old one still works, says so once (W1103), and goes in 2.1. [[target]] is unchanged.
  • The unused-label sweep is --find-unused, not --enable-warnings. -u, --no-find-unused and find_unused replace the warnings spellings, which never turned any other warning on or off.
  • INCLUDE no longer searches the working directory first. A basename finds the file beside the including source, so a build is the same wherever fantasm is run from. Name the directory, or add it with -I.
  • A bin whose code is not contiguous is refused (E1085). It used to write a file where a label pointed at the wrong byte. Assemble each region separately and combine them.
  • A label must start at column 0 (E1093). An indented name that is no instruction, directive or macro is refused. Locals, constants and names inside a macro body are unaffected.
  • A macro’s name at column 0 defines a label rather than expanding (W1094). Indent the call.
  • rep is a directive, so it may not name a macro (E1060). Rename the macro; labels and STRUCT members still take the word.
  • assert and debug are directives, so neither may name a macro (E1060). Same fix, same exemptions.
  • An index displacement outside -128 to 127 is refused (E1123). ld a,(ix+200) used to assemble as ld a,(ix-56) in silence.
  • A !test command outside a !test block is refused (E1122). They were accepted anywhere and discarded, so an assertion past its !endtest passed without running.
  • An undefined name in an ENUM member’s value is refused (E1003). The member used to take the next counter value silently.
  • Two targets may not share an artifact path. A project-wide [artifacts] with one labels key left a file holding only the last target’s symbols. Move the key into each [target.artifacts].
  • !nex screen is refused for anything but 0 (E1117). No screen was ever written, so the flag sent a loader into the program.
  • A .nex is tagged V1.2 unless it needs V1.3. It was always V1.3, which a core older than 3.x refuses to load.
  • init --force has no short form. -f is --format everywhere else. init -f still works and says so.
  • -vvv is refused. There are two levels of -v and it silently meant -vv.

13 more breaking changes to the Rust API are recorded in the project’s own history, which the published changelog leaves out. The crate is not on crates.io and makes no semantic-version promise; only a program embedding it is affected.

Added

  • A language server worth using — rename across the project, call hierarchy, quick fixes from a diagnostic’s hint, a nested outline, folding, inlay hints, clickable INCLUDEs. It reads your fantasm.toml, and a file a [[target]] includes is analysed as part of that target.
  • Libraries!library makes a file a namespace with its own include guard, INTERFACE says what is public, and --gc-libraries discards what nothing calls without touching a placement MODULE.
  • REP <count>, <instruction>, and a REP/ENDR block, for unrolling without ldir’s loop overhead.
  • Tests that check memoryassert_mem and init_mem take a run, a fill, a file to compare against or a physical address; assert_checksum folds a run into a CRC-32C; init_cycle_limit and assert_cycles budget the time.
  • Debugger annotations!assert and !debug record a claim into the --sld file, emitting nothing.
  • A [lint] table in fantasm.toml sets what each warning is filed as, by code [w1049 = "deny"].
  • Nested structs, --reloc for relocatable images, --report-discards, --color, and Rhai scripts as include "x.rhai" with rhai.file.fn() calls.
  • These notes ship with the buildRELEASE-NOTES.md, beside the licence in every package.

46 additions in all.

Fixed

113 fixes, and the worst of each group:

  • 2 crashesds -1 and db (1<<63)/-1 aborted the assembler outright.
  • 24 wrong output, silently — a bank’s code written twice into a .nex, add hl,ix assembling as add hl,hl, db 1<2<3 giving 8, seven block-graphics characters emitting the wrong code.
  • 24 sources that should have assembled, or should not have — an IF inside a branch that was not being assembled having its condition evaluated anyway, a !test body inside a MODULE unable to use that scope’s own names, a macro parameter inside brackets never substituted.
  • 37 in output and tooling — colour written into a redirected log, only the first error of a build reported however many it found, no two --sld files matching between builds.
  • 19 diagnostics that named the wrong thing, or put the caret at the end of the statement rather than on the name.
  • 5 in the language server, and 2 in the documentation.

26 of those 113 are the editor’s, spread across the groups rather than gathered in one: it disagreed with the build on any file holding a MODULE, opening a second file of one program filled the screen with faults in correct source, and an accented character in a comment took the server down.

59 new diagnostic codes, 84 to 140, and three withdrawn — E1034, I1051 and E1062 documented rules the assembler did not have. Test coverage expanded from 742 to 1,432 tests, and every combination of the nex, sna, lsp and update-check features builds and passes.

Every change is in the full changelog under [2.0.0], and the documentation has been rewritten to match — including a new page on libraries.

Get it from the Downloads page.