SplitBit assembles SplitBit: M1, a single file with no includes

Programs/CosmOS/Assembler/ is an assembler written in SplitBit assembly. It
runs under CosmOS, reads source off a SplitBit disk, and writes a binary back
to it with no host involved anywhere:

    > run Asm.sbx hello.asm
    wrote hello.bin: program 17, data 14, labels 2

THE ACCEPTANCE TEST IS THE BYTES. Tests/native.sh assembles Programs/hello.asm
both ways and compares the two files byte for byte, then runs the one the
machine built. "It ran" and "the sizes look right" both pass for a binary with
a label one byte out, which is a program that jumps into the middle of an
instruction - so the only honest test is the one SplitDisk and sbfs.asm
already work under: two implementations of one written specification, each
checking the other. The files are identical and the result prints Hello,
World! in 70 cycles.

hello.asm is the target because it is the oldest program in the repository.
The first thing this machine ever ran is now the first thing it assembles for
itself.

TWO PASSES OVER STREAMED SOURCE. The C assembler reads every token of every
file into one array; that cannot port, because cosmos.asm alone is 56,047
bytes against 64K of Data Memory. The native one streams through a 256 byte
window, twice, and keeps only the label table between the passes. Two passes
suffice because every length is known without resolving anything - an
instruction's from its shape, a value's is one, a string's is its characters
and a zero - so the first pass fixes every address and the second never needs
a fixup list. A forward reference stops being a special case and becomes the
reason there are two passes at all.

The parts, each checked before anything was built on it:
  source.asm    characters out of a file of any size, with a line number
  token.asm     tokens out of characters, one character of lookahead
  classify.asm  what a token is, in the C assembler's order, which IS the
                language: keyword, instruction, value, string, label
  labels.asm    names packed in an arena, four bytes of index each
  numbers.asm   sixteen bit arithmetic, since sbfs.asm's cannot be reached
  table.asm     the instruction set, generated by the same script the
                monitor's copy is, and now BOTH are checked by docs.sh

readTest.asm and tokenTest.asm check the reader and the tokenizer on their
own, recorded as cosmosSource and cosmosTokens. A wrong classification does
not produce a wrong byte somewhere obvious; it produces a right looking
program of the wrong length, so it is worth catching where it happens.

WHAT IT REFUSES: #Include, #Base, #Align, #Reserve and #Vectors are refused
by name rather than ignored. Skipping a directive would produce a file that
looked right and was the wrong length, which is the worst thing an assembler
can do.

Two traps worth recording, both already known to this project and both hit
again: CALL restores A, B and DP0-DP2, so three routines returning an answer
in A had it undone by their own return; and numStep works on DP0, so three
sites that set DP1 left a pointer that never advanced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-20 22:13:15 -04:00
co-authored by Claude Opus 5
parent a131a90c67
commit dcb331c151
21 changed files with 3185 additions and 7 deletions
+178
View File
@@ -0,0 +1,178 @@
; The assembler's front end on its own, one line per token.
;
; Prints the line each token started on, what the token turned out to be, how many bytes
; it will come to, and the token itself between brackets so that whitespace at either end
; would show if any ever leaked in.
;
; WHAT A TOKEN IS is the part worth checking here rather than at the far end. A wrong
; classification does not produce a wrong byte in an obvious place - it produces a right
; looking program of the wrong length, with everything after it shifted, and by then the
; only symptom is that a label points at the middle of an instruction.
;
; Written by Anachronaut
#Include services.asm
#Program
#Base 0x2000
start:
SETD.0 Wanted
INIB 0d23
SWI osArgument
SETD.0 Wanted
LDA.0
BRA nothingAsked
SETD.0 Wanted
CALL srcOpen
BNQ noFile
tokenLoop:
CALL tokNext
BNQ tokensDone
SETD.0 TokLine
LDA.0
INCD.0
LDB.0
SWI osPrintNumber
CALL clsToken
BNQ badToken
; Which of the six it turned out to be. The names are four characters and a space, so
; the columns line up without any counting.
SETD.0 TypeNames
SETD.2 ClsType
LDA.2
CALL nameOfType
SETD.1 NamePointer
LDD.0.1
SWI osPrintString
SETD.0 ClsLength
LDA.0
RSTB
PSHA
POPB
RSTA
SWI osPrintNumber
SETD.0 OpenMark
SWI osPrintString
SETD.0 TokText
SWI osPrintString
SETD.0 CloseMark
SWI osPrintString
BRI tokenLoop
badToken:
SETD.0 StoppedText
SWI osPrintString
SWI osExit
; The name of type A, out of a table of fixed width entries so that no pointer arithmetic
; is needed beyond a multiply by the width.
nameOfType:
SETD.0 TypeWidth
LDB.0
RSTA
SETD.0 NameLeft
STA.0
SETD.2 ClsType
LDA.2
SETD.0 NameOffset
CALL numZero
nameLoop:
SETD.2 ClsType
LDA.2
SETD.0 NameLeft
LDB.0
CCF
SUB
BRQ nameFound
SETD.0 TypeWidth
LDA.0
SETD.0 NameOffset
CALL numAddByte
SETD.0 NameLeft
LDA.0
INCA
STA.0
BRI nameLoop
nameFound:
; The answer is left in NamePointer rather than in DP0, because a RET puts Data Pointers
; 0 to 2 back as they were: a routine cannot hand back a pointer, only write one down.
SETD.0 TypeNames
SETD.1 NamePointer
STD.0.1
SETD.0 NamePointer
SETD.2 NameOffset
CALL numAdd
RET
tokensDone:
SETD.0 DoneText
SWI osPrintString
SWI osExit
nothingAsked:
SETD.0 AskText
SWI osPrintString
SWI osExit
noFile:
SETD.0 NoFileText
SWI osPrintString
SWI osExit
#Data
#Base 0x1000
Wanted:
#Reserve 0d23
OpenMark:
" ["
CloseMark:
"]
"
; Six names of eleven characters each, counting the zero the assembler puts on the end of
; every string. Written one to a line so that adding a type is adding a line.
TypeNames:
" keyword "
" instr "
" value "
" string "
" label: "
" label "
TypeWidth:
0d11
NameLeft:
0x00
NameOffset:
0x00 0x00
NamePointer:
0x00 0x00
StoppedText:
"---- stopped: the assembler does not understand that
"
DoneText:
"---- no more tokens
"
AskText:
"say which file
"
NoFileText:
"no such file
"
#Include numbers.asm
#Include source.asm
#Include token.asm
#Include classify.asm
#Include table.asm