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)
: 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.
- 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).
- Merlin high-bit text encodings (
ASC/DCI/INV/FLS) need an
encoding-mode layer.
- 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.
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 is018-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)
string encodings, macro grammar →
as65/ca65/merlin.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.cppalready dispatches on aCommandLineOptions::Subcommandenum (
As65,Run,Help,Version), andAs65already has its own dedicatedflag parser (
ParseAs65Flags, ~line 755) — todayas65is just the implicitfallback. Extend the existing pattern:
as65/ca65/merlin(matchrun; not--as65).Parse<Dialect>Flags+ scoped--help(per-mode usage already exists).as65to an explicit subcommand and drop the "unrecognized → as65" heuristic.as65gets--cpu 6502|65c02(as65 has no in-source CPU directive);
ca65/merlindon't offer it — they usein-source
.setcpu/.pc02/XC.Internal architecture
AssemblerOptions.dialectso all entry points (CLI, GUIin-app assembler, unit tests) select it uniformly; subcommand parsers just
populate it.
DialectProfileseam theParserconsults: comment char(s),label-sigil rules, directive-name→canonical map, string-encoding modes, macro
grammar. The two-pass engine, expression evaluator, and
OpcodeTablestayshared — three front doors, one room.
.setcpu/XC, the assembler must hold/look up bothinstruction 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 andlabel:· 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, theASC/DCI/INV/FLShigh-bit-text family,STR,]variables,LUP/--^(loop),
DUM/DEND,PUT/USE, MerlinMAC/EOM/<<<+]1macro params,:labellocal-label prefix, strict column rules.ca65 delta — ~L (partly architectural)
Surface (
.setcpu,.res,.macro/.endmacro,.repeat) is ~M, but depth isreal:
.proc/.scope/::scoping,@cheap-locals + bare:/:+/:-unnamed labels,
.segment "NAME", and especially.import/.export/.global.Cross-cutting barriers (the actual cost)
:sigil collides three ways — Casso uses:as a label terminator;Merlin uses
:nameas a local-label prefix; ca65 uses bare:as anunnamed label. Needs dialect-scoped, context-sensitive parsing.
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).
ASC/DCI/INV/FLS) need anencoding-mode layer.
mandatory (the subcommand above).
Decisions / non-goals
lenient-superset default — each dialect profile can be strict and authentic
(real Merlin columns, real ca65
:/@locals).as65becomes explicit; thefallback heuristic goes away. Internal callers just name a dialect in
AssemblerOptions.dialect(mechanical migration).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-supportbranch andneeds none of this. This spec is the broader multi-dialect ambition.