Skip to content

Broaden assembler dialect support: Merlin + ca65 (proposed spec 019) #92

Description

@relmer

Goal

Broaden Casso's assembler beyond its current single as65-flavored dialect to
also accept Merlin and ca65 source — the two dominant Apple II
assemblers (classic + modern). Captured for a future spec; proposed spec
number 019
(highest reserved today is 018-mockingboard-support).

To be picked up on its own branch/session — this issue is the seed, not the spec.

Two orthogonal axes (do not conflate)

  • Dialect = syntax: comment char, label sigils, directive spellings,
    string encodings, macro grammar → as65 / ca65 / merlin.
  • CPU target = which opcodes are legal (the instruction table) →
    6502 / 65C02.

They're independent: 65C02 code can be written in Merlin or ca65. So the
dialect selector must not imply a CPU, and CPU selection must work under
any dialect.

Proposed CLI shape — subcommands (the CLI is already built for this)

CassoCli/CommandLine.cpp already dispatches on a CommandLineOptions::Subcommand
enum (As65, Run, Help, Version), and As65 already has its own dedicated
flag parser (ParseAs65Flags, ~line 755) — today as65 is just the implicit
fallback. Extend the existing pattern:

  • Add bare-word subcommands as65 / ca65 / merlin (match run; not --as65).
  • Each gets its own Parse<Dialect>Flags + scoped --help (per-mode usage already exists).
  • Free wins: mutual exclusion, dialect-only flags that can't leak, scoped help.
  • Promote as65 to an explicit subcommand and drop the "unrecognized → as65" heuristic.
  • CPU knob is per-dialect (validates the split): as65 gets --cpu 6502|65c02
    (as65 has no in-source CPU directive); ca65/merlin don't offer it — they use
    in-source .setcpu/.pc02 / XC.

Internal architecture

  • Dialect lives in AssemblerOptions.dialect so all entry points (CLI, GUI
    in-app assembler, unit tests) select it uniformly; subcommand parsers just
    populate it.
  • Introduce a DialectProfile seam the Parser consults: comment char(s),
    label-sigil rules, directive-name→canonical map, string-encoding modes, macro
    grammar. The two-pass engine, expression evaluator, and OpcodeTable stay
    shared — three front doors, one room.
  • To honor in-source .setcpu/XC, the assembler must hold/look up both
    instruction tables (a small instruction-set provider), not the single fixed
    Microcode[256] it takes today.

Head start (already overlaps both dialects)

Comments ; · labels col-0 and label: · constants =/EQU/SET ·
literals $/%/0x/0b/decimal/char · </>/LO/HI · *=PC · directives
(bare and dotted) ORG, DB/BYT/BYTE/FCB/FCC→.BYTE, DW/WORD/FDB→.WORD,
DS/RMB, END, ALIGN, INCLUDE, STRUCT, IFDEF/IFNDEF, IF/ELSE,
LIST/…, CODE/DATA/BSS · macros (name MACRO … ENDM) · two-pass · absolute image.

Merlin delta — ~M (bounded)

XC (CPU toggle), *-in-col-1 comment lines, DFB/DA/DDB, HEX, the
ASC/DCI/INV/FLS high-bit-text family, STR, ]variables, LUP/--^
(loop), DUM/DEND, PUT/USE, Merlin MAC/EOM/<<< + ]1 macro params,
:label local-label prefix, strict column rules.

ca65 delta — ~L (partly architectural)

Surface (.setcpu, .res, .macro/.endmacro, .repeat) is ~M, but depth is
real: .proc/.scope/:: scoping, @ cheap-locals + bare :/:+/:-
unnamed labels, .segment "NAME", and especially .import/.export/.global.

Cross-cutting barriers (the actual cost)

  1. : sigil collides three ways — Casso uses : as a label terminator;
    Merlin uses :name as a local-label prefix; ca65 uses bare : as an
    unnamed label. Needs dialect-scoped, context-sensitive parsing.
  2. ca65 is relocating; Casso is absolute. Real ca65 source assumes ld65
    (named segments via a linker config, object files, imports/exports); Casso
    emits one ORG-anchored image. True compat needs a linker/relocation model
    (big) — scope ca65 to the absolute subset instead (most published ca65
    projects then won't assemble unmodified; acceptable given goals).
  3. Merlin high-bit text encodings (ASC/DCI/INV/FLS) need an
    encoding-mode layer.
  4. Both dialects can't share one grammar unambiguously → dialect mode is
    mandatory (the subcommand above).

Decisions / non-goals

  • Back-compat is NOT a priority (extremely limited adoption). So: drop any
    lenient-superset default — each dialect profile can be strict and authentic
    (real Merlin columns, real ca65 :/@ locals). as65 becomes explicit; the
    fallback heuristic goes away. Internal callers just name a dialect in
    AssemblerOptions.dialect (mechanical migration).
  • Full ca65 (with linker) is out of scope; target the absolute subset.

Relation to spec 016

Independent and smaller: making as65 emit 65C02 (BBR/BBS zero-page-relative
parsing — spec-016 T040) is being handled on the 016-apple2c-support branch and
needs none of this. This spec is the broader multi-dialect ambition.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions