asm8 syntax notes (for codegen)
April 24, 2026 · View on GitHub
Source: https://github.com/begoon/asm8 (README + target/monitor.asm at main as of 2026-04-17).
Our codegen must emit valid asm8 input. asm8 is a two-pass 8080 assembler in TypeScript; all documented Intel 8080 mnemonics are accepted.
Lexical
- Case-insensitive for mnemonics, registers, and symbols. We'll emit lowercase mnemonics, symbols untouched.
- Comments:
;to end of line. No block comments. - Statement terminator: newline. No
;between statements. - Multi-statement lines:
/(spaces on both sides), up to 10 per line. We won't use this — one statement per line is easier to diff against listings.
Numbers and literals
- Decimal:
255. - Hex: must start with a digit,
hsuffix —0FFh,8000h. If we compute a hex literal starting withA–F, prefix with0. - Character:
'A'— usable anywhere a byte value fits. - Strings (in
dbonly):db "hello"ordb 'hello'. - Expressions: C precedence, operators
+ - * / % | & ^ ~ << >>and parentheses.LOW(expr)/HIGH(expr)extract low/high byte of a 16-bit value — maps 1:1 to PL/MLOW/HIGHbuiltins.
Directives
Each directive may optionally be written with a leading dot (.org, .db, ...) but we'll stick to the undotted form.
org <addr>— start a new section at<addr>.section <name>— name the current section.equ <expr>— constant; used aslabel equ <expr>.db <b1>, <b2>, ...— emit bytes (expressions or strings).dw <w1>, <w2>, ...— emit 16-bit words, little-endian (8080 convention).ds N— reserve N bytes of zeros.ds N (F)— reserve N bytes filled with byteF.end— end of source.
Labels
name: at column 0 before an instruction or directive. Names are case-insensitive and can include digits after the first char.
Output model
- Every
orgopens a new section. Default output is a single.binfile covering first section start → last section end, zero-padded between sections (and in front of the first section if itsorgisn't 0). Overlapping sections are an error. - With
--split, each section becomes its own<base>-<sectionname>.bin.
Implications for plm-80 codegen
- One big module, one
orgfor v0. Defaultorg 0100h(CP/M-style) unless the user overrides. - Code section first, then data section via a second
org— avoid interleaving so the.binstays compact. - Global variables → labeled
dsentries in the data section. ABYTE xbecomesx: ds 1; aWORD wbecomesw: ds 2; aBYTE a(10)becomesa: ds 10. INITIALconstants →db/dwwith the provided values, still at a labeled location in the data section (or a rodata section if we later split code/data/rodata).- Constants /
LITERALLY→equdirectives emitted at the top. - Numeric literals we emit: decimal for small values,
0NNhfor anything derived from addresses or bit masks. Never rely on implicit leading-zero-less hex. - Symbol names: PL/M allows
$as a visual separator inside identifiers (must be stripped before emitting); asm8 symbol rules don't include$, soCOUNT$LOin PL/M source becomesCOUNTLOin asm. - Built-ins that map cleanly:
LOW(x)→LOW(x),HIGH(x)→HIGH(x).SHR/SHL→>>/<<at compile-time for constants, orana/rar/ral/rlc/rrcsequences at runtime (TBD). - Strings: PL/M string literals become
db "…"— but note PL/M doubles''for a single quote; convert to"…"form to avoid the escape dance. - Multi-statement
/— avoid. Keeps golden-file diffs readable.
Emitter conventions (proposal)
- 4-space indent for instructions; labels at column 0.
- Single blank line between procedures.
- Header comment with source file + compiler version.
- Symbols: uppercase mnemonics feel more authentic to the 8080 era, but asm8 examples are lowercase — go lowercase to match the reference corpus (
monitor.asm).
Open questions to resolve before codegen lands
- How do we want to surface
CALLto anEXTERNALprocedure when there's no linker? (Probably: require a user-suppliedequfor its address, or a separateexternals.asmstub.) - 16-bit arithmetic helpers (
__mulhi,__divhi,__shlhi) — ship as hand-written asm inruntime/and concatenate onto compiler output, or emit inline? - Entry point convention: do we emit a
jmp startatorgand let the user definestart, or assume the PL/M program's top-level block isstart?