Files
SplitBit-Emulator/Programs/CosmOS/Assembler/numbers.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

141 lines
2.9 KiB
NASM

; The small things every other part of the assembler needs: sixteen bit arithmetic, for
; something that counts in addresses, and one string comparison.
;
; sbfs.asm has routines like these and the assembler cannot use them: it does not include
; the filesystem, because it reaches the disk through the system's services instead. That
; is the no-linker tax, paid in about a hundred and fifty bytes, and it is cheaper than the
; two and a half kilobytes including sbfs.asm would cost.
;
; Everything here works on numbers in memory rather than in registers, because a CALL puts
; A, B and Data Pointers 0 to 2 back as it found them. Only memory survives a return.
;
; Numbers are stored most significant byte first, the way every number on this machine is.
;
; Written by Anachronaut
#Program
; The two byte number at DP0 becomes the one at DP2. Destination first, so a call reads
; the way an assignment does.
numSet:
LDA.2
STA.0
INCD.2
INCD.0
LDA.2
STA.0
RET
; The two byte number at DP0 becomes itself plus the one at DP2.
numAdd:
DPUP.0 0d01
DPUP.2 0d01
LDA.0
LDB.2
CCF
ADD
MVQA
STA.0
DPDN.0 0d01
DPDN.2 0d01
LDA.0
LDB.2
ADD ; Carries in from the low half. Nothing between touches it.
MVQA
STA.0
RET
; The two byte number at DP0 becomes itself less the one at DP2.
numTake:
DPUP.0 0d01
DPUP.2 0d01
LDA.0
LDB.2
CCF
SUB
MVQA
STA.0
DPDN.0 0d01
DPDN.2 0d01
LDA.0
LDB.2
SUB ; Borrows in from the low half.
MVQA
STA.0
RET
; Adds the byte in A to the two byte number at DP0.
numAddByte:
DPUP.0 0d01
LDB.0
CCF
ADD
MVQA
STA.0
BNC numAddByteDone
DPDN.0 0d01
LDA.0
INCA
STA.0
numAddByteDone:
RET
; Adds one to the two byte number at DP0.
numStep:
DPUP.0 0d01
LDA.0
INCA
STA.0
BNC numStepDone ; It did not wrap, so the high byte is untouched.
DPDN.0 0d01
LDA.0
INCA
STA.0
numStepDone:
RET
; Compares the two byte number at DP0 with the one at DP2. Q is zero if they are equal,
; and the Carry Flag is set if the one at DP0 is the smaller. Both come back, because
; neither Q nor the Status register is put back by a return.
numCompare:
LDA.0
LDB.2
CCF
SUB ; The high bytes settle it unless they are the same.
BNQ numCompareDone
INCD.0
INCD.2
LDA.0
LDB.2
CCF
SUB
numCompareDone:
RET
; Q is zero if the strings at DP0 and DP1 are the same, both ending in a zero byte.
;
; Down here rather than with the label table, where it started, because four separate
; parts want it: labels, vector names, which file has already been included, and which
; directive a keyword is.
sameText:
LDA.0
LDB.1
CCF
SUB
BNQ sameTextDone
LDA.0
BRA sameTextDone ; They ended together, so they matched all the way.
INCD.0
INCD.1
BRI sameText
sameTextDone:
RET
; The two byte number at DP0 becomes zero.
numZero:
RSTA
STA.0
INCD.0
STA.0
RET