Files
SplitBit-Emulator/Programs/CosmOS/Assembler/vectors.asm
T
AnachronautandClaude Opus 5 c5e4ec3455 M2: the native assembler builds applications
> load Asm.sbx
    > run Say.asm
    wrote Say.sbx: program 46, data 93, labels 7
    > load Say.sbx
    > run built by the machine itself
    it says: built by the machine itself

The machine assembles an application and then runs what it built. Say,
greet and Files all come out byte for byte identical to the C assembler's,
and Tests/native.sh checks all three on every run alongside the boot image
M1 already covered.

WHAT IT TOOK, and it was more than #Include and #Base:

  #Include   The reader is a stack of readers. The current file's whole
             state goes aside - buffer and all, 292 bytes - the new one
             opens, and the end of it pops the old one back. A file goes in
             once; including it twice does nothing, which is what lets two
             libraries depend on a third. The list is forgotten between the
             passes, because the second has to walk the same tree.
  #Base      Cursors start there, so labels hold the addresses the program
             will really have. A program that says where it goes gets the
             SBEX header and a .sbx name; one that says nothing gets SPBT
             and .bin. A program that bases one segment and leaves the
             other unbased with content in it is refused.
  #Reserve   Runs of zeroes, moved over in the first pass and written in
  #Align     the second. How many an #Align comes to depends on where the
             cursor has reached, which is why both passes keep a cursor.
  #Vectors   Names are read and numbered, pinned where the source pins
             them, so SWI osPrintString resolves. Every application needs
             this - a program that calls a service names a vector declared
             in a file it includes.

THE TWO PASSES ARE NOW ONE LOOP, walked twice, with Emitting the only
difference. They have to agree about the length of every token, and the way
they stop agreeing is by being two pieces of code that drifted apart -
which is the exact shape of the bug this assembler found in the C one.
Sharing the body means there is nothing to drift. What is left is checked
anyway: the second pass compares its own totals against the first's and
refuses to write the file if they differ.

THE BUG WORTH RECORDING. The tokenizer holds one character of lookahead,
and at an #Include that character belongs to the file being put aside. It
was carried across and handed back on the way out, which is wrong: a file
runs out in the middle of whatever the tokenizer happens to be doing, so
the character arrives in the middle of a word. `start:` came back as `s`
and then `tart:` - and the result assembled into a perfectly plausible
file. The fix is to undo the read instead, so the character is simply still
there when the file is opened again and the question of when to hand it
back never arises.

Three smaller ones, all old friends: three places took the CONTENTS of a
buffer where they wanted its ADDRESS; vecTakeAuto returned its answer in A,
which a RET puts back; and pass two re-declared every vector because only
the label table was being skipped on the second walk.

sameText moved down into numbers.asm from the label table - four parts want
it now, and a reader test that includes neither labels nor tokens has to
build on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-20 22:50:39 -04:00

215 lines
3.8 KiB
NASM

; The names in the Vector Segment, and what numbers they have.
;
; A vector name is not a label and the two are kept deliberately apart, so a program may
; call a routine `announce` and name a vector `announce` without either shadowing the
; other. They are looked up in different places because they mean different things: a label
; is an address and a vector is a number.
;
; FIXED FIELDS HERE, unlike the label table's arena. There are at most a couple of hundred
; of these against several hundred labels, and the names are short, so packing them would
; cost more code than it saved. Twenty four bytes an entry: a name of up to twenty two with
; its zero, and the number.
;
; Numbers come from two places. A pinned one is written down in the source, and that is how
; anything two separately assembled programs must agree about is fixed - the system's
; services are all pinned. Everything else is numbered automatically from 64 up, out of a
; range nothing outside one program can name, so what number it gets cannot matter.
;
; Written by Anachronaut
#Program
vecReset:
SETD.0 VecCount
CALL numZero
INIA 0d64
SETD.0 VecNextAuto
STA.0
RET
; Declares the name at DP0 with the number in A. Q is zero if it went in.
vecDeclare:
SETD.2 VecPutNumber
STA.2
SETD.2 VecSubject
STD.0.2
CALL vecFind
BNQ vecDeclareFresh
SETD.0 VecTwice
CALL clsComplain
BRI vecDeclareNo
vecDeclareFresh:
SETD.0 VecCount
SETD.2 VecLimit
CALL numCompare
BNC vecDeclareFull
SETD.0 VecWhich
SETD.2 VecCount
CALL numSet
CALL vecSlotAt
SETD.1 VecSubject
LDD.0.1
SETD.1 VecSlot
LDD.1.1
CALL srcKeepName
SETD.1 VecSlot
LDD.0.1
INIA 0d23
SETD.0 VecSlot
CALL numAddByte
SETD.1 VecSlot
LDD.0.1
SETD.2 VecPutNumber
LDA.2
STA.0
SETD.0 VecCount
CALL numStep
RSTA
RSTB
CCF
ADD
RET
vecDeclareFull:
SETD.0 VecFull
CALL clsComplain
vecDeclareNo:
RSTA
INIB 0d1
CCF
ADD
RET
; The next number nothing has taken, into VecPutNumber. These start at 64, above everything
; that may be pinned, so a name a program made up for itself can never land on a system
; service.
;
; Into memory rather than into A, because a CALL puts A back as it found it.
vecTakeAuto:
SETD.0 VecNextAuto
LDA.0
SETD.0 VecPutNumber
STA.0
SETD.0 VecNextAuto
LDA.0
INCA
STA.0
RET
; Looks up the name at DP0. Q is zero if it is there, and then VecNumber is its number.
vecFind:
SETD.2 VecSought
STD.0.2
SETD.0 VecWhich
CALL numZero
vecFindLoop:
SETD.0 VecWhich
SETD.2 VecCount
CALL numCompare
BNC vecFindMissing
CALL vecSlotAt
SETD.1 VecSlot
LDD.0.1
SETD.1 VecSought
LDD.1.1
CALL sameText
BRQ vecFindGot
SETD.0 VecWhich
CALL numStep
BRI vecFindLoop
vecFindGot:
SETD.1 VecSlot
LDD.0.1
INIA 0d23
SETD.0 VecSlot
CALL numAddByte
SETD.1 VecSlot
LDD.0.1
LDA.0
SETD.0 VecNumber
STA.0
RSTA
RSTB
CCF
ADD
RET
vecFindMissing:
RSTA
INIB 0d1
CCF
ADD
RET
; Where entry number VecWhich sits, into VecSlot. Twenty four bytes an entry.
vecSlotAt:
SETD.0 VecNames
SETD.1 VecSlot
STD.0.1
SETD.0 VecSlotLeft
SETD.2 VecWhich
CALL numSet
vecSlotLoop:
SETD.0 VecSlotLeft
LDA.0
INCD.0
LDB.0
OR
BRQ vecSlotDone
INIA 0d24
SETD.0 VecSlot
CALL numAddByte
SETD.0 VecSlotLeft
SETD.2 VecOne
CALL numTake
BRI vecSlotLoop
vecSlotDone:
RET
#Data
VecCount:
0x00 0x00
VecWhich:
0x00 0x00
VecSlot:
0x00 0x00
VecSlotLeft:
0x00 0x00
VecSought:
0x00 0x00
VecSubject:
0x00 0x00
VecNumber:
0x00
VecPutNumber:
0x00
VecNextAuto:
0x00
VecOne:
0x00 0x01
; Sixty four names, which is every number a program may name for itself.
VecLimit:
0x00 0x40
VecTwice:
"that vector name is declared twice"
VecFull:
"too many vector names"
VecName:
#Reserve 0d23
VecNames:
#Reserve 0d1536