Files
Shawn Quick 6a163bdb9c Initial Merlin 16+ compatible cross-assembler.
Host-side 6502/65C02/65816 assembler using Merlin 16+ as the primary
dialect, with Merlin 8/16 compatibility and native high-ASCII source
support ($A0 column tabs, $8D line endings).
2026-08-31 08:31:18 -07:00

5.9 KiB
Raw Permalink Blame History

Merlin 16+ dialect (with 8/16 compatibility)

Primary spec: Merlin 16+ (Glen Bredon, Roger Wagner Publishing, 1988–89). Merlin 8/16 is the subset we remain compatible with. This file is the working language contract for this assembler, distilled from:

  • doc/manuals/Merlin_816_Macro_Assembler_Manual.pdf
  • doc/manuals/Merlin_16_Supplementary_Manual.pdf

Host-only extensions, if any, will be called out. None yet.

Line format

LABEL    OPCODE    OPERAND    ; comment
  • A label, if present, starts in column 0 (no leading whitespace).
  • Fields are separated by spaces or tabs.
  • * or ; as the first non-blank character makes a comment line.
  • Opcode mnemonics are case-insensitive. Labels, macros, and equates follow CAS SE / CAS IN (default: insensitive, matching typical 16+ FARMS).

Native Merlin source files (disk / IIgs editor)

The on-disk format from Merlin 8/16/16+ is not plain ASCII:

  • Letters, digits, and punctuation normally have bit 7 set.
  • Column tabs (label / opcode / operand / comment) are a single $A0 (space with the high bit set). $09 and $89 (TAB) are accepted too.
  • Literal spaces in quoted operands and comments are $20 (high bit clear). That is why $A0 is a tab and $20 is not: the assembler can split columns without tracking quotes.
  • Lines end with $8D (high-bit CR), sometimes $0D / $0A.
  • Extra $A0 after the third separator is treated as an ordinary space (David Empson / CiderPress notes).

Host-side .s files (spaces, TABs, LF/CRLF, high bit clear) are also accepted. High-bit letters with $20 separators (partially converted files) still assemble: bit 7 is stripped on each field.

Processors

XC CPU
off 6502 only
once 65C02
twice, or 16/16+ default 65802 / 65816

16+: XC OFF restores 6502-only. Startup:

  • --dialect 16+ / 16: XC on (65816), MX %11 (emulation, 8-bit A/XY)
  • --dialect 8: XC off, MX %11

Sources should still begin with explicit MX / XC so they assemble the same way regardless of FARMS / CLI defaults.

SEP / REP update assembler MX from the immediate operand when it is constant. MX %00–%11 (or 0–3) sets it without emitting code.

M=0 / X=0 means 16-bit; 1 means 8-bit.

Addressing

Merlin does not write ASL A. Accumulator/implied is a bare mnemonic.

Immediate data starts with #. Addresses do not.

Syntax Mode
LDA #$00 immediate (8 or 16 from M)
LDA $10 direct page if value < $100 and not forced
LDA $C000 absolute
LDAL $E12000 / LDA >$E12000 long
LDA: $10 / LDA\ $10 force absolute
LDA <$1234 force direct page (low byte)
LDA ($10),Y DP indirect indexed
LDA [$10],Y DP indirect long indexed
LDA $10,S stack relative
LDA ($10,S),Y stack relative indirect indexed
BEQ LABEL relative
MVN src,dst block move (object: dest bank, src bank)

Byte selectors after #: < low, > high, ^ (or #A) bank.

Alternate mnemonics: BLT=BCC, BGE=BCS, TAS=TCS, TSA=TSC, TAD=TCD, TDA=TDC, SWA=XBA. Long ALU forms LDAL, STAL, …

Undefined labels are 16-bit (absolute) until equated. Zero-page labels must be defined before use (Merlin rule; otherwise DP/abs size would be unstable).

Expressions

Numbers: decimal, $hex, %binary (_ allowed in binary). ASCII 'A' / "A". * is the program counter at term position, multiply at operator position.

Default evaluation is left-to-right, no precedence (1+2*3 = 9). Enclose in { } for algebraic precedence (16+). --algebraic does that for all expressions (FARMS bit; can break old LAB1-LAB2/2 sources).

Operators, lowest to highest inside braces:

<  =  >  #          comparison (true=1, false=0); # is not-equal
+  -
*  /
&  .  !             AND, OR, XOR
-                   unary minus

( ) are addressing, not grouping. Use { }.

Labels

  • Global: start with a letter or extra-alphabetic (_, @, … not a digit).
  • ]NAME as a label is a backward-only local (nearest previous).
  • :NAME is a local whose scope is the enclosing global labels.
  • ]NAME = expr is a variable (redefinable). Forward refs to variables are illegal.
  • Locals cannot be ENT/EXT and cannot be the first label in a file.

Compatibility: keep names ≤ 26 characters for real Merlin 16+.

Data / directives (16+ set, 8/16 subset)

Assembler: ORG, OBJ, EQU/=, PUT, USE, VAR, SAV, DSK, END, DUM, DEND, TYP, REL, LNK, ENT, EXT, MX, XC, CAS, ERR, CHK, DATE, STRL (16+), LST, EXP, TR, PAU, PAG, AST, SKP.

Data: HEX, DFB/DB, DA/DW, DDB, ADR, ADRL, DS, ASC, DCI, INV, FLS, REV, STR, STRL.

16+ ASC accepts hex bytes mixed into the string, empty strings, and ASC #expr / #' / #" / #>.

Conditionals: DO / ELSE / FIN, IF / ELSE / FIN. IF MX/2 tests M; IF "=]1" / IF #,]1 tests the first character of a variable (macros). Nest ≤ 8 on real 16+.

Macros: MAC … EOM / <<<. Call by name, or PMC / >>>. Parameters ]1–]9 separated by ;. Nest ≤ 15 on real 16+.

Loops: LUP n … --^. @ in a label becomes Z,Y,X,… per iteration (max 26).

Output

Absolute object (BIN) is produced from ORG + DSK/SAV/-o. REL / Linker.GS / OMF v2.1 is planned; not in the MVP.

TYP accepts hex ($B3) or ASCII names (SYS, S16, CDA, EXE, BIN).

What 16+ added (vs 8/16) that we treat as first-class

  • XC OFF
  • CAS SE / CAS IN
  • {algebraic} expressions and < = > # comparisons
  • STRL
  • mixed hex in ASC/STR/… and ASC #
  • TYP ASCII filetypes; TYP in REL files
  • SAV compatible with REL
  • LST FILE,name
  • pathname ../ (. + / backup) in assembler/linker files
  • DATE operands 1–8
  • FLO (SANE extended) — not yet implemented
  • DUP equate-same-value option — not yet a CLI flag