Compare commits
12
Commits
b36d438132
...
fb335681d2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb335681d2 | ||
|
|
20989c3439 | ||
|
|
04424a8917 | ||
|
|
c5e4ec3455 | ||
|
|
affe9d09ea | ||
|
|
dcb331c151 | ||
|
|
a131a90c67 | ||
|
|
ca243895ca | ||
|
|
3d2ab34229 | ||
|
|
3b800a69e8 | ||
|
|
c23adb2836 | ||
|
|
5fd995aa62 |
@@ -0,0 +1,62 @@
|
|||||||
|
; Stopping a program to look at it.
|
||||||
|
;
|
||||||
|
; SWI osBreak shows every register as this program had them, waits for a key, and carries
|
||||||
|
; on. It is two bytes and it fires every time it is reached.
|
||||||
|
;
|
||||||
|
; WHY IT IS AN INSTRUCTION RATHER THAN SOMETHING SET FROM OUTSIDE. A breakpoint that was
|
||||||
|
; poked into a running program would have to overwrite an instruction, and then putting that
|
||||||
|
; instruction back in order to continue is the same act as disarming the breakpoint. Firing
|
||||||
|
; a second time would mean stepping over the restored instruction and putting the breakpoint
|
||||||
|
; back behind it, and this machine cannot step one instruction. Nothing is overwritten here,
|
||||||
|
; so there is nothing to restore and nothing to re-arm.
|
||||||
|
;
|
||||||
|
; The price is that it is part of the program. A build with breakpoints in it has different
|
||||||
|
; addresses from a build without, which is the same bargain every machine makes that has a
|
||||||
|
; break instruction.
|
||||||
|
;
|
||||||
|
; Correct output is two stops. A and B differ between them, the addresses differ, and the
|
||||||
|
; Stack Pointer differs too, because the second one is inside a subroutine and a call has
|
||||||
|
; put ten bytes down by then.
|
||||||
|
|
||||||
|
#Include services.asm
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
#Base 0x2000
|
||||||
|
|
||||||
|
start:
|
||||||
|
SETD.0 Banner
|
||||||
|
SWI osPrintString
|
||||||
|
|
||||||
|
INIA 0d17
|
||||||
|
INIB 0d34
|
||||||
|
SETD.0 Marker
|
||||||
|
SWI osBreak
|
||||||
|
|
||||||
|
; The second stop is inside a subroutine, so that the Stack Pointer is visibly not where
|
||||||
|
; it was: a call puts ten bytes down before this one gets there.
|
||||||
|
CALL deeper
|
||||||
|
|
||||||
|
SETD.0 DoneText
|
||||||
|
SWI osPrintString
|
||||||
|
SWI osExit
|
||||||
|
|
||||||
|
deeper:
|
||||||
|
INIA 0d68
|
||||||
|
INIB 0d85
|
||||||
|
SETD.0 Banner
|
||||||
|
SWI osBreak
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
#Base 0x1000
|
||||||
|
|
||||||
|
Banner:
|
||||||
|
"two stops, and what the registers were at each
|
||||||
|
"
|
||||||
|
Marker:
|
||||||
|
"marker"
|
||||||
|
DoneText:
|
||||||
|
"carried on to the end
|
||||||
|
"
|
||||||
@@ -0,0 +1,491 @@
|
|||||||
|
; Reading a file the machine cannot hold.
|
||||||
|
;
|
||||||
|
; Every other program here asks for a file and is handed the whole of it, which settles the
|
||||||
|
; question for anything under 64K and settles nothing above. CosmOS's own source is above:
|
||||||
|
; the sources together are a hundred kilobytes, and Data Memory is sixty four. A machine
|
||||||
|
; that is one day going to assemble itself has to be able to read a file bigger than its
|
||||||
|
; memory, and this is the program that proves it can.
|
||||||
|
;
|
||||||
|
; It uses osFileInfo and osFileBlock, and nothing else knows how a filesystem works. There
|
||||||
|
; is no open and no close - every call names the file and says which block it wants, so a
|
||||||
|
; program that stops halfway leaves nothing behind for anybody to clean up.
|
||||||
|
;
|
||||||
|
; ---- What it checks, and why each one is here ----
|
||||||
|
;
|
||||||
|
; 1. A file of four hundred odd blocks is read from end to end, a block at a time, into a
|
||||||
|
; buffer of one block. That is the feature.
|
||||||
|
; 2. A small file is read BOTH WAYS - whole with osFileRead, and streamed - and the two
|
||||||
|
; have to agree. This is the real proof: it compares streaming against the path that
|
||||||
|
; was already known to work, so a fault in the block count or the order of the blocks
|
||||||
|
; shows up as a difference rather than as a plausible wrong answer.
|
||||||
|
; 3. Two files are read alternately. The system remembers where the last file it was
|
||||||
|
; asked about lives, and this is the case that catches a memory that does not notice
|
||||||
|
; the name has changed.
|
||||||
|
; 4. A rename in the middle. Same reason, from the other side: the file the system
|
||||||
|
; remembers has moved out from under the name it remembered it by.
|
||||||
|
; 5. The three ways of being told no, each with its own number.
|
||||||
|
;
|
||||||
|
; THE CHECKSUM IS FLETCHER'S, not a sum. A plain total is the same whatever order the bytes
|
||||||
|
; arrived in, and the order is exactly what streaming has to get right; carrying a second
|
||||||
|
; accumulator that adds the first one in each time makes a block delivered out of turn
|
||||||
|
; change the answer.
|
||||||
|
;
|
||||||
|
; Written by Anachronaut
|
||||||
|
|
||||||
|
#Include services.asm
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
#Base 0x2000
|
||||||
|
|
||||||
|
start:
|
||||||
|
; ---- 1. How big is something that will not fit ----
|
||||||
|
;
|
||||||
|
; In blocks, not bytes, and that is forced rather than chosen: a file on a sixteen
|
||||||
|
; megabyte disk can be twenty four bits long and a pointer holds sixteen.
|
||||||
|
SETD.0 BigName
|
||||||
|
SWI osFileInfo
|
||||||
|
BNQ noBig
|
||||||
|
SETD.0 BigIs
|
||||||
|
SWI osPrintString
|
||||||
|
PSHD.3
|
||||||
|
POPB
|
||||||
|
POPA
|
||||||
|
SWI osPrintNumber
|
||||||
|
SETD.0 BlocksText
|
||||||
|
SWI osPrintString
|
||||||
|
|
||||||
|
; ---- 2. Read the whole of it through a hole one block wide ----
|
||||||
|
CALL clearChecksum
|
||||||
|
CALL clearIndex
|
||||||
|
|
||||||
|
bigLoop:
|
||||||
|
SETD.0 BigName
|
||||||
|
SETD.1 Block
|
||||||
|
SETD.2 Index
|
||||||
|
LDA.2
|
||||||
|
INCD.2
|
||||||
|
LDB.2 ; Which block, most significant first.
|
||||||
|
SWI osFileBlock
|
||||||
|
BNQ bigDone
|
||||||
|
|
||||||
|
CALL takeCount
|
||||||
|
SETD.1 Block
|
||||||
|
CALL checksum
|
||||||
|
CALL stepIndex
|
||||||
|
BRI bigLoop
|
||||||
|
|
||||||
|
bigDone:
|
||||||
|
; The loop ends because a block past the end was asked for, which is answer three. Any
|
||||||
|
; other answer stopped it early and would otherwise look exactly like success, so what
|
||||||
|
; ended it is printed rather than assumed.
|
||||||
|
CALL keepWhy
|
||||||
|
SETD.0 ReadText
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.2 Index
|
||||||
|
LDA.2
|
||||||
|
INCD.2
|
||||||
|
LDB.2
|
||||||
|
SWI osPrintNumber
|
||||||
|
SETD.0 BlocksSumText
|
||||||
|
SWI osPrintString
|
||||||
|
CALL printChecksum
|
||||||
|
SETD.0 StoppedText
|
||||||
|
SWI osPrintString
|
||||||
|
CALL printWhy
|
||||||
|
|
||||||
|
; ---- 3. The same file both ways ----
|
||||||
|
;
|
||||||
|
; osFileRead is the path that already worked, so it is what streaming is measured
|
||||||
|
; against. If the two checksums agree, every byte arrived and they arrived in order.
|
||||||
|
SETD.0 SmallName
|
||||||
|
SETD.1 Whole
|
||||||
|
SWI osFileRead
|
||||||
|
BNQ noSmall
|
||||||
|
CALL takeCount
|
||||||
|
CALL clearChecksum
|
||||||
|
SETD.1 Whole
|
||||||
|
CALL checksum
|
||||||
|
CALL keepChecksum
|
||||||
|
|
||||||
|
CALL clearChecksum
|
||||||
|
CALL clearIndex
|
||||||
|
smallLoop:
|
||||||
|
SETD.0 SmallName
|
||||||
|
SETD.1 Block
|
||||||
|
SETD.2 Index
|
||||||
|
LDA.2
|
||||||
|
INCD.2
|
||||||
|
LDB.2
|
||||||
|
SWI osFileBlock
|
||||||
|
BNQ smallDone
|
||||||
|
CALL takeCount
|
||||||
|
SETD.1 Block
|
||||||
|
CALL checksum
|
||||||
|
CALL stepIndex
|
||||||
|
BRI smallLoop
|
||||||
|
|
||||||
|
smallDone:
|
||||||
|
SETD.0 BothText
|
||||||
|
SWI osPrintString
|
||||||
|
CALL printChecksum
|
||||||
|
SETD.0 AgainstText
|
||||||
|
SWI osPrintString
|
||||||
|
CALL printKept
|
||||||
|
SETD.0 NewLine
|
||||||
|
SWI osPrintString
|
||||||
|
CALL sameAsKept
|
||||||
|
BNQ differ
|
||||||
|
SETD.0 SameText
|
||||||
|
SWI osPrintString
|
||||||
|
BRI interleave
|
||||||
|
differ:
|
||||||
|
SETD.0 DifferText
|
||||||
|
SWI osPrintString
|
||||||
|
|
||||||
|
; ---- 4. Two files, alternately ----
|
||||||
|
;
|
||||||
|
; Block zero of the big file, then a block of the small one, then block zero of the big
|
||||||
|
; file again. The two readings of the same block have to match. A system that remembered
|
||||||
|
; the first file and did not notice the name had changed would hand back a block of the
|
||||||
|
; wrong file in the middle, and then the right one again, so only the middle call would
|
||||||
|
; be wrong - which is why this asks for the same block twice rather than once.
|
||||||
|
interleave:
|
||||||
|
CALL clearChecksum
|
||||||
|
CALL readFirstBig
|
||||||
|
CALL keepChecksum
|
||||||
|
|
||||||
|
SETD.0 SmallName
|
||||||
|
SETD.1 Block
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
SWI osFileBlock
|
||||||
|
|
||||||
|
CALL clearChecksum
|
||||||
|
CALL readFirstBig
|
||||||
|
CALL sameAsKept
|
||||||
|
BNQ mixedUp
|
||||||
|
SETD.0 InterleaveOk
|
||||||
|
SWI osPrintString
|
||||||
|
BRI moved
|
||||||
|
mixedUp:
|
||||||
|
SETD.0 InterleaveBad
|
||||||
|
SWI osPrintString
|
||||||
|
|
||||||
|
; ---- 5. A file that moves out from under the name ----
|
||||||
|
;
|
||||||
|
; The system has just been asked about the small file, so it is the one being remembered.
|
||||||
|
; Renaming it has to throw that away: the blocks are still there and still hold the same
|
||||||
|
; bytes, so a stale answer would work perfectly and be wrong.
|
||||||
|
moved:
|
||||||
|
SETD.0 SmallName
|
||||||
|
SETD.1 OtherName
|
||||||
|
SWI osFileRename
|
||||||
|
BNQ noRename
|
||||||
|
SETD.0 MovedText
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 SmallName
|
||||||
|
SWI osFileInfo
|
||||||
|
CALL keepWhy
|
||||||
|
SETD.0 OldNameText
|
||||||
|
SWI osPrintString
|
||||||
|
CALL printWhy
|
||||||
|
SETD.0 NewNameText
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 OtherName
|
||||||
|
SWI osFileInfo
|
||||||
|
CALL keepWhy
|
||||||
|
CALL printWhy
|
||||||
|
|
||||||
|
; ---- 6. The three ways of being told no ----
|
||||||
|
missing:
|
||||||
|
SETD.0 MissingName
|
||||||
|
SWI osFileInfo
|
||||||
|
CALL keepWhy
|
||||||
|
SETD.0 MissingText
|
||||||
|
SWI osPrintString
|
||||||
|
CALL printWhy
|
||||||
|
|
||||||
|
SETD.0 OtherName
|
||||||
|
SETD.1 Block
|
||||||
|
INIA 0xFF
|
||||||
|
INIB 0xFF
|
||||||
|
SWI osFileBlock
|
||||||
|
CALL keepWhy
|
||||||
|
SETD.0 PastText
|
||||||
|
SWI osPrintString
|
||||||
|
CALL printWhy
|
||||||
|
|
||||||
|
SWI osExit
|
||||||
|
|
||||||
|
noBig:
|
||||||
|
CALL keepWhy
|
||||||
|
SETD.0 NoBigText
|
||||||
|
SWI osPrintString
|
||||||
|
CALL printWhy
|
||||||
|
SWI osExit
|
||||||
|
noSmall:
|
||||||
|
SETD.0 NoSmallText
|
||||||
|
SWI osPrintString
|
||||||
|
SWI osExit
|
||||||
|
noRename:
|
||||||
|
SETD.0 NoRenameText
|
||||||
|
SWI osPrintString
|
||||||
|
SWI osExit
|
||||||
|
|
||||||
|
; ---- Routines ----
|
||||||
|
|
||||||
|
; Block zero of the big file, into the running checksum.
|
||||||
|
readFirstBig:
|
||||||
|
SETD.0 BigName
|
||||||
|
SETD.1 Block
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
SWI osFileBlock
|
||||||
|
BNQ readFirstDone
|
||||||
|
CALL takeCount
|
||||||
|
SETD.1 Block
|
||||||
|
CALL checksum
|
||||||
|
readFirstDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; What the service just answered in DP3 becomes Left, which is what the checksum counts
|
||||||
|
; down. Kept in memory rather than in a pointer because a CALL does not preserve one.
|
||||||
|
takeCount:
|
||||||
|
PSHD.3
|
||||||
|
POPB
|
||||||
|
POPA
|
||||||
|
SETD.2 Left
|
||||||
|
STA.2
|
||||||
|
INCD.2
|
||||||
|
STB.2
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Adds the bytes at DP1 into the running checksum, as many of them as Left says.
|
||||||
|
;
|
||||||
|
; Two accumulators, each a byte wide, each throwing away what carries off the top. The
|
||||||
|
; first is the sum of the bytes and the second is the sum of the first, so a byte that
|
||||||
|
; arrives late counts for less than one that arrived early - which is what makes this
|
||||||
|
; notice a block delivered out of turn.
|
||||||
|
checksum:
|
||||||
|
checksumLoop:
|
||||||
|
LDA.1
|
||||||
|
SETD.2 Fletch1
|
||||||
|
LDB.2
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
MVQA
|
||||||
|
STA.2
|
||||||
|
SETD.2 Fletch2
|
||||||
|
LDB.2
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
MVQA
|
||||||
|
STA.2
|
||||||
|
INCD.1
|
||||||
|
|
||||||
|
; Left goes down by one, sixteen bits of it: a whole block is 256 bytes and a whole file
|
||||||
|
; is more than one block, so a byte counter would not reach.
|
||||||
|
SETD.2 Left
|
||||||
|
INCD.2
|
||||||
|
LDA.2
|
||||||
|
BNA checksumLow
|
||||||
|
DPDN.2 0d01
|
||||||
|
LDA.2
|
||||||
|
DECA
|
||||||
|
STA.2 ; Borrow out of the high byte.
|
||||||
|
DPUP.2 0d01
|
||||||
|
INIA 0xFF
|
||||||
|
STA.2
|
||||||
|
BRI checksumTest
|
||||||
|
checksumLow:
|
||||||
|
DECA
|
||||||
|
STA.2
|
||||||
|
checksumTest:
|
||||||
|
SETD.2 Left
|
||||||
|
LDA.2
|
||||||
|
INCD.2
|
||||||
|
LDB.2
|
||||||
|
OR ; Zero only when both halves are.
|
||||||
|
BNQ checksumLoop
|
||||||
|
RET
|
||||||
|
|
||||||
|
clearChecksum:
|
||||||
|
RSTA
|
||||||
|
SETD.2 Fletch1
|
||||||
|
STA.2
|
||||||
|
SETD.2 Fletch2
|
||||||
|
STA.2
|
||||||
|
RET
|
||||||
|
|
||||||
|
clearIndex:
|
||||||
|
RSTA
|
||||||
|
SETD.2 Index
|
||||||
|
STA.2
|
||||||
|
INCD.2
|
||||||
|
STA.2
|
||||||
|
RET
|
||||||
|
|
||||||
|
stepIndex:
|
||||||
|
SETD.2 Index
|
||||||
|
INCD.2
|
||||||
|
LDA.2
|
||||||
|
INCA
|
||||||
|
STA.2
|
||||||
|
BNC stepIndexDone
|
||||||
|
DPDN.2 0d01
|
||||||
|
LDA.2
|
||||||
|
INCA
|
||||||
|
STA.2
|
||||||
|
stepIndexDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Puts the checksum aside so that a second one can be compared with it.
|
||||||
|
keepChecksum:
|
||||||
|
SETD.2 Fletch1
|
||||||
|
LDA.2
|
||||||
|
SETD.2 Kept1
|
||||||
|
STA.2
|
||||||
|
SETD.2 Fletch2
|
||||||
|
LDA.2
|
||||||
|
SETD.2 Kept2
|
||||||
|
STA.2
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Q is zero if the running checksum is the one that was put aside.
|
||||||
|
sameAsKept:
|
||||||
|
SETD.2 Fletch1
|
||||||
|
LDA.2
|
||||||
|
SETD.2 Kept1
|
||||||
|
LDB.2
|
||||||
|
XOR
|
||||||
|
BNQ sameAsKeptDone
|
||||||
|
SETD.2 Fletch2
|
||||||
|
LDA.2
|
||||||
|
SETD.2 Kept2
|
||||||
|
LDB.2
|
||||||
|
XOR
|
||||||
|
sameAsKeptDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
printChecksum:
|
||||||
|
SETD.2 Fletch1
|
||||||
|
LDA.2
|
||||||
|
SETD.2 Fletch2
|
||||||
|
LDB.2
|
||||||
|
SWI osPrintNumber
|
||||||
|
RET
|
||||||
|
|
||||||
|
printKept:
|
||||||
|
SETD.2 Kept1
|
||||||
|
LDA.2
|
||||||
|
SETD.2 Kept2
|
||||||
|
LDB.2
|
||||||
|
SWI osPrintNumber
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Why the last service said no. Q survives a CALL, which is the only reason this can be a
|
||||||
|
; routine at all, but it does not survive the next SWI - so it is written down here and
|
||||||
|
; printed later, with whatever has to happen in between happening in between.
|
||||||
|
keepWhy:
|
||||||
|
MVQA
|
||||||
|
SETD.2 Why
|
||||||
|
STA.2
|
||||||
|
RET
|
||||||
|
|
||||||
|
printWhy:
|
||||||
|
RSTA
|
||||||
|
SETD.2 Why
|
||||||
|
LDB.2
|
||||||
|
SWI osPrintNumber
|
||||||
|
SETD.0 NewLine
|
||||||
|
SWI osPrintString
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
#Base 0x1000
|
||||||
|
|
||||||
|
BigName:
|
||||||
|
"big.txt"
|
||||||
|
SmallName:
|
||||||
|
"small.txt"
|
||||||
|
OtherName:
|
||||||
|
"moved.txt"
|
||||||
|
MissingName:
|
||||||
|
"nothing.txt"
|
||||||
|
|
||||||
|
BigIs:
|
||||||
|
"big.txt is "
|
||||||
|
BlocksText:
|
||||||
|
" blocks
|
||||||
|
"
|
||||||
|
ReadText:
|
||||||
|
"read "
|
||||||
|
BlocksSumText:
|
||||||
|
" blocks, checksum "
|
||||||
|
StoppedText:
|
||||||
|
", stopped with "
|
||||||
|
BothText:
|
||||||
|
"small.txt streamed is "
|
||||||
|
AgainstText:
|
||||||
|
", read whole is "
|
||||||
|
SameText:
|
||||||
|
"the same
|
||||||
|
"
|
||||||
|
DifferText:
|
||||||
|
"DIFFERENT
|
||||||
|
"
|
||||||
|
InterleaveOk:
|
||||||
|
"the same block twice with another file between: the same
|
||||||
|
"
|
||||||
|
InterleaveBad:
|
||||||
|
"the same block twice with another file between: DIFFERENT
|
||||||
|
"
|
||||||
|
MovedText:
|
||||||
|
"renamed small.txt
|
||||||
|
"
|
||||||
|
OldNameText:
|
||||||
|
"the old name now answers "
|
||||||
|
NewNameText:
|
||||||
|
"the new name answers "
|
||||||
|
MissingText:
|
||||||
|
"a name that was never there answers "
|
||||||
|
PastText:
|
||||||
|
"a block past the end answers "
|
||||||
|
|
||||||
|
NoBigText:
|
||||||
|
"big.txt would not open, answer "
|
||||||
|
NoSmallText:
|
||||||
|
"small.txt would not read
|
||||||
|
"
|
||||||
|
NoRenameText:
|
||||||
|
"it would not rename
|
||||||
|
"
|
||||||
|
NewLine:
|
||||||
|
"
|
||||||
|
"
|
||||||
|
|
||||||
|
Index:
|
||||||
|
0x00 0x00
|
||||||
|
Left:
|
||||||
|
0x00 0x00
|
||||||
|
Fletch1:
|
||||||
|
0x00
|
||||||
|
Fletch2:
|
||||||
|
0x00
|
||||||
|
Kept1:
|
||||||
|
0x00
|
||||||
|
Kept2:
|
||||||
|
0x00
|
||||||
|
Why:
|
||||||
|
0x00
|
||||||
|
|
||||||
|
; One block, which is the whole point: the big file is four hundred times this.
|
||||||
|
Block:
|
||||||
|
#Reserve 0d256
|
||||||
|
|
||||||
|
; And room for the small one all at once, so that the two ways of reading it can be
|
||||||
|
; compared against each other.
|
||||||
|
Whole:
|
||||||
|
#Reserve 0d1024
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,882 @@
|
|||||||
|
; What a token is.
|
||||||
|
;
|
||||||
|
; THE ORDER OF THESE TESTS IS THE LANGUAGE, and it is copied deliberately from the C
|
||||||
|
; assembler rather than reinvented, because the two have to produce the same bytes from
|
||||||
|
; the same source. A token is a keyword, then an instruction, then a literal value, then a
|
||||||
|
; string, then a label - and what a thing means depends on which of those it reaches first.
|
||||||
|
;
|
||||||
|
; ClsType 0 keyword 1 instruction 2 value 3 string
|
||||||
|
; 4 label definition 5 label reference
|
||||||
|
;
|
||||||
|
; A STRING IS NEVER ANYTHING ELSE. The quotes are gone by the time a token is looked at, so
|
||||||
|
; without that guard a string whose text reads "ADD" assembles as an instruction and one
|
||||||
|
; that begins with a zero is rejected as a malformed literal. Both have happened; the C
|
||||||
|
; assembler carries the same guard in two places and this carries it in four, because the
|
||||||
|
; keyword test needs it too and over there it does not have it.
|
||||||
|
;
|
||||||
|
; Written by Anachronaut
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
; Works out what TokText is. Q is zero if it is something the assembler understands.
|
||||||
|
clsToken:
|
||||||
|
SETD.0 ClsLength
|
||||||
|
CALL numZero
|
||||||
|
|
||||||
|
SETD.0 TokString
|
||||||
|
LDA.0
|
||||||
|
BNA clsIsString
|
||||||
|
|
||||||
|
; ---- A keyword ----
|
||||||
|
SETD.0 TokText
|
||||||
|
LDA.0
|
||||||
|
INIB 0x23 ; '#'
|
||||||
|
XOR
|
||||||
|
BNQ clsTryInstruction
|
||||||
|
INIA 0d0
|
||||||
|
SETD.0 ClsType
|
||||||
|
STA.0
|
||||||
|
BRI clsYes
|
||||||
|
|
||||||
|
clsTryInstruction:
|
||||||
|
CALL clsInstruction
|
||||||
|
BNQ clsTryValue
|
||||||
|
INIA 0d1
|
||||||
|
SETD.0 ClsType
|
||||||
|
STA.0
|
||||||
|
BRI clsYes
|
||||||
|
|
||||||
|
clsTryValue:
|
||||||
|
; A leading zero means a literal was meant, so anything malformed after it is an error
|
||||||
|
; rather than a label. Falling through to the label test would quietly emit two bytes
|
||||||
|
; where one was wanted and shift everything after it.
|
||||||
|
SETD.0 TokText
|
||||||
|
LDA.0
|
||||||
|
INIB 0x30 ; '0'
|
||||||
|
XOR
|
||||||
|
BNQ clsTryLabel
|
||||||
|
CALL clsValue
|
||||||
|
BNQ clsNo
|
||||||
|
INIA 0d2
|
||||||
|
SETD.0 ClsType
|
||||||
|
STA.0
|
||||||
|
INIA 0d1
|
||||||
|
CALL clsSetLength
|
||||||
|
BRI clsYes
|
||||||
|
|
||||||
|
clsIsString:
|
||||||
|
INIA 0d3
|
||||||
|
SETD.0 ClsType
|
||||||
|
STA.0
|
||||||
|
; A string is its characters and the zero byte after them, which is why two strings
|
||||||
|
; written in a row are two strings rather than one long one.
|
||||||
|
;
|
||||||
|
; SIXTEEN BITS, and this is the token that needs them: a string may be 255 characters,
|
||||||
|
; which with its zero is 256, and 256 does not fit in a byte. Everything else here is 0,
|
||||||
|
; 1, 2 or 3.
|
||||||
|
SETD.0 ClsLength
|
||||||
|
CALL numZero
|
||||||
|
SETD.2 TokLength
|
||||||
|
LDA.2
|
||||||
|
SETD.0 ClsLength
|
||||||
|
CALL numAddByte
|
||||||
|
SETD.0 ClsLength
|
||||||
|
CALL numStep
|
||||||
|
BRI clsYes
|
||||||
|
|
||||||
|
clsTryLabel:
|
||||||
|
; A colon on the end makes it a definition. Everything else is a use of a name, which
|
||||||
|
; is two bytes of address wherever it appears.
|
||||||
|
CALL clsLastCharacter
|
||||||
|
SETD.0 ClsByte
|
||||||
|
LDA.0
|
||||||
|
INIB 0x3A ; ':'
|
||||||
|
XOR
|
||||||
|
BNQ clsUse
|
||||||
|
INIA 0d4
|
||||||
|
SETD.0 ClsType
|
||||||
|
STA.0
|
||||||
|
BRI clsYes
|
||||||
|
|
||||||
|
clsUse:
|
||||||
|
INIA 0d5
|
||||||
|
SETD.0 ClsType
|
||||||
|
STA.0
|
||||||
|
INIA 0d2
|
||||||
|
CALL clsSetLength
|
||||||
|
|
||||||
|
clsYes:
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
clsNo:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; The last character of the token, into ClsByte. Zero if the token is empty.
|
||||||
|
;
|
||||||
|
; INTO MEMORY, not into A, and that is not a style choice: a CALL saves and restores A, B
|
||||||
|
; and Data Pointers 0 to 2, so a routine that leaves its answer in one of those has the
|
||||||
|
; answer undone by its own return. Only Q, DP3 and memory survive.
|
||||||
|
clsLastCharacter:
|
||||||
|
SETD.0 TokLength
|
||||||
|
LDA.0
|
||||||
|
BRA clsLastNone
|
||||||
|
SETD.0 TokText
|
||||||
|
SETD.1 ClsWalk
|
||||||
|
STD.0.1
|
||||||
|
SETD.0 TokLength
|
||||||
|
LDA.0
|
||||||
|
DECA
|
||||||
|
SETD.0 ClsWalk
|
||||||
|
CALL numAddByte
|
||||||
|
SETD.1 ClsWalk
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
SETD.0 ClsByte
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
clsLastNone:
|
||||||
|
RSTA
|
||||||
|
SETD.0 ClsByte
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ---- Instructions ----
|
||||||
|
|
||||||
|
; Is TokText an instruction? Q is zero if it is, and then ClsOpcode, ClsShape, ClsLength
|
||||||
|
; and ClsSelectorValue describe it.
|
||||||
|
;
|
||||||
|
; The name is folded to upper case and the selectors are split off before anything is
|
||||||
|
; looked up, because SETD.2 is the instruction SETD naming Data Pointer 2 rather than a
|
||||||
|
; name of its own.
|
||||||
|
clsInstruction:
|
||||||
|
CALL clsSplitName
|
||||||
|
BNQ clsInstructionNo ; Longer than any mnemonic, so it is not one.
|
||||||
|
CALL clsFindName
|
||||||
|
BNQ clsInstructionNo
|
||||||
|
|
||||||
|
; How many selectors this shape wants. They are emitted whether or not they were
|
||||||
|
; written, so the length is fixed by the instruction and leaving one off means zero.
|
||||||
|
SETD.0 ClsShape
|
||||||
|
LDA.0
|
||||||
|
SETD.0 AsmShapeSelectors
|
||||||
|
CALL clsIndexByte
|
||||||
|
SETD.0 ClsByte
|
||||||
|
LDA.0
|
||||||
|
SETD.0 ClsWanted
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
; More selectors than the instruction has pointers to name is a mistake worth catching:
|
||||||
|
; it means the programmer thinks it does something it does not.
|
||||||
|
SETD.0 ClsGiven
|
||||||
|
LDA.0
|
||||||
|
SETD.2 ClsWanted
|
||||||
|
LDB.2
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRQ clsSelectorsFit
|
||||||
|
BRC clsSelectorsFit ; Fewer than wanted is allowed and means zero.
|
||||||
|
SETD.0 TooManySelectors
|
||||||
|
CALL clsComplain
|
||||||
|
BRI clsInstructionNo
|
||||||
|
|
||||||
|
clsSelectorsFit:
|
||||||
|
SETD.0 ClsWanted
|
||||||
|
LDA.0
|
||||||
|
INCA
|
||||||
|
CALL clsSetLength ; The opcode and its selectors. The operand is its own token.
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
clsInstructionNo:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Splits TokText into an upper case mnemonic in ClsName, padded to four with spaces, and
|
||||||
|
; up to two selector digits in ClsSelectorValue. Q is zero if the name could be a mnemonic
|
||||||
|
; at all, which means four characters or fewer.
|
||||||
|
clsSplitName:
|
||||||
|
INIA 0x20
|
||||||
|
SETD.0 ClsName
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
RSTA
|
||||||
|
STA.0 ; Four spaces and a zero, so a short name still compares.
|
||||||
|
|
||||||
|
RSTA
|
||||||
|
SETD.0 ClsGiven
|
||||||
|
STA.0
|
||||||
|
SETD.0 ClsSelectorValue
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
SETD.0 ClsNameLength
|
||||||
|
RSTA
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
SETD.0 TokText
|
||||||
|
SETD.1 ClsWalk
|
||||||
|
STD.0.1
|
||||||
|
|
||||||
|
clsNameLoop:
|
||||||
|
SETD.1 ClsWalk
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
BRA clsSplitDone
|
||||||
|
INIB 0x2E ; '.'
|
||||||
|
XOR
|
||||||
|
BRQ clsSelectorPart
|
||||||
|
|
||||||
|
SETD.0 ClsNameLength
|
||||||
|
LDA.0
|
||||||
|
INIB 0d4
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNC clsSplitTooLong ; A fifth character, so this is not a mnemonic.
|
||||||
|
|
||||||
|
SETD.1 ClsWalk
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
CALL clsUpper
|
||||||
|
SETD.0 ClsByte
|
||||||
|
LDA.0
|
||||||
|
SETD.0 ClsName
|
||||||
|
SETD.2 ClsNameLength
|
||||||
|
CALL clsPutIndexed
|
||||||
|
SETD.0 ClsNameLength
|
||||||
|
LDA.0
|
||||||
|
INCA
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
clsNameStep:
|
||||||
|
SETD.0 ClsWalk
|
||||||
|
CALL numStep
|
||||||
|
BRI clsNameLoop
|
||||||
|
|
||||||
|
clsSelectorPart:
|
||||||
|
; The character after the dot is which Data Pointer, in decimal.
|
||||||
|
SETD.0 ClsWalk
|
||||||
|
CALL numStep
|
||||||
|
SETD.1 ClsWalk
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
BRA clsSplitDone
|
||||||
|
INIB 0x30
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
MVQA ; The digit as a number.
|
||||||
|
SETD.0 ClsDigitHold
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
; There are four Data Pointers, so anything above three does not name one.
|
||||||
|
INIB 0d4
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNC clsSelectorRange
|
||||||
|
|
||||||
|
SETD.0 ClsGiven
|
||||||
|
LDA.0
|
||||||
|
INIB 0d2
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNC clsSelectorSpare ; Already two, so anything more is counted and discarded;
|
||||||
|
; the count is what the caller complains about.
|
||||||
|
SETD.0 ClsDigitHold
|
||||||
|
LDA.0
|
||||||
|
SETD.0 ClsSelectorValue
|
||||||
|
SETD.2 ClsGiven
|
||||||
|
CALL clsPutIndexed
|
||||||
|
clsSelectorSpare:
|
||||||
|
SETD.0 ClsGiven
|
||||||
|
LDA.0
|
||||||
|
INCA
|
||||||
|
STA.0
|
||||||
|
BRI clsNameStep
|
||||||
|
|
||||||
|
clsSplitDone:
|
||||||
|
SETD.0 ClsNameLength
|
||||||
|
LDA.0
|
||||||
|
BRA clsSplitTooLong ; Nothing before the dot is not a mnemonic either.
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
clsSelectorRange:
|
||||||
|
SETD.0 BadSelector
|
||||||
|
CALL clsComplain
|
||||||
|
clsSplitTooLong:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Looks ClsName up in the instruction table. Q is zero if it is there, and then ClsOpcode
|
||||||
|
; and ClsShape say what it is.
|
||||||
|
clsFindName:
|
||||||
|
SETD.0 AsmInstructions
|
||||||
|
SETD.1 ClsEntry
|
||||||
|
STD.0.1
|
||||||
|
SETD.0 AsmInstructionCount
|
||||||
|
LDA.0
|
||||||
|
SETD.0 ClsLeft
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
clsFindLoop:
|
||||||
|
SETD.1 ClsEntry
|
||||||
|
LDD.0.1
|
||||||
|
INCD.0
|
||||||
|
INCD.0 ; Past the opcode and the shape, to the name.
|
||||||
|
SETD.1 ClsName
|
||||||
|
CALL clsSameName
|
||||||
|
BRQ clsFindGot
|
||||||
|
|
||||||
|
; Seven bytes to an entry: an opcode, a shape, and four characters with a zero.
|
||||||
|
INIA 0d7
|
||||||
|
SETD.0 ClsEntry
|
||||||
|
CALL numAddByte
|
||||||
|
SETD.0 ClsLeft
|
||||||
|
LDA.0
|
||||||
|
DECA
|
||||||
|
STA.0
|
||||||
|
BNA clsFindLoop
|
||||||
|
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
clsFindGot:
|
||||||
|
SETD.1 ClsEntry
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
SETD.1 ClsOpcode
|
||||||
|
STA.1
|
||||||
|
SETD.1 ClsEntry
|
||||||
|
LDD.0.1
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
SETD.1 ClsShape
|
||||||
|
STA.1
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Four characters at DP0 against four at DP1. Q is zero if they are the same.
|
||||||
|
clsSameName:
|
||||||
|
INIA 0d4
|
||||||
|
SETD.2 ClsLeft2
|
||||||
|
STA.2
|
||||||
|
clsSameLoop:
|
||||||
|
LDA.0
|
||||||
|
LDB.1
|
||||||
|
XOR
|
||||||
|
BNQ clsSameDone
|
||||||
|
INCD.0
|
||||||
|
INCD.1
|
||||||
|
SETD.2 ClsLeft2
|
||||||
|
LDA.2
|
||||||
|
DECA
|
||||||
|
STA.2
|
||||||
|
BNA clsSameLoop
|
||||||
|
clsSameDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ---- Literal values ----
|
||||||
|
|
||||||
|
; Is TokText a well formed literal? Q is zero if it is, and ClsValue is what it comes to.
|
||||||
|
; Anything beginning with a zero has to be one, so a failure here is an error rather than
|
||||||
|
; an invitation to try the next test.
|
||||||
|
;
|
||||||
|
; A LITERAL IS ONE BYTE WHEREVER IT GOES, so this is the byte-wide door onto clsWord below.
|
||||||
|
; The directives are the wide one: #Base takes an address and #Reserve a count, and neither
|
||||||
|
; would fit through here.
|
||||||
|
clsValue:
|
||||||
|
CALL clsWord
|
||||||
|
BNQ clsValueNo
|
||||||
|
SETD.0 ClsWord
|
||||||
|
LDA.0
|
||||||
|
BNA clsValueTooBig ; Something in the high byte, so it will not fit in one.
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
SETD.0 ClsValue
|
||||||
|
STA.0
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
clsValueTooBig:
|
||||||
|
SETD.0 TooBig
|
||||||
|
CALL clsComplain
|
||||||
|
clsValueNo:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Reads TokText as a sixteen bit number, into ClsWord. Q is zero if it is a well formed one.
|
||||||
|
;
|
||||||
|
; Both bases are here rather than in two routines because the only difference is which
|
||||||
|
; digits count and what to multiply by, and a number is written the same way wherever it
|
||||||
|
; appears - an address after #Base, a count after #Reserve, a byte in a segment.
|
||||||
|
clsWord:
|
||||||
|
SETD.0 TokText
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
INIB 0x78 ; 'x'
|
||||||
|
XOR
|
||||||
|
BRQ clsWordHex
|
||||||
|
SETD.0 TokText
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
INIB 0x64 ; 'd'
|
||||||
|
XOR
|
||||||
|
BRQ clsWordDecimal
|
||||||
|
|
||||||
|
SETD.0 BadPrefix
|
||||||
|
CALL clsComplain
|
||||||
|
BRI clsWordNo
|
||||||
|
|
||||||
|
clsWordHex:
|
||||||
|
INIA 0d16
|
||||||
|
SETD.0 ClsBase
|
||||||
|
STA.0
|
||||||
|
BRI clsWordDigits
|
||||||
|
|
||||||
|
clsWordDecimal:
|
||||||
|
INIA 0d10
|
||||||
|
SETD.0 ClsBase
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
clsWordDigits:
|
||||||
|
SETD.0 TokLength
|
||||||
|
LDA.0
|
||||||
|
INIB 0d3
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRC clsWordEmpty ; Only the prefix, so there are no digits at all.
|
||||||
|
|
||||||
|
SETD.0 ClsWord
|
||||||
|
CALL numZero
|
||||||
|
SETD.0 TokText
|
||||||
|
INCD.0
|
||||||
|
INCD.0
|
||||||
|
SETD.1 ClsWalk
|
||||||
|
STD.0.1
|
||||||
|
|
||||||
|
clsWordLoop:
|
||||||
|
SETD.1 ClsWalk
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
BRA clsWordGood
|
||||||
|
CALL clsDigit
|
||||||
|
BNQ clsWordBadDigit
|
||||||
|
|
||||||
|
CALL clsWordTimesBase
|
||||||
|
BNQ clsWordTooBig
|
||||||
|
|
||||||
|
; And the digit on the end. A sum that comes out smaller than what went into it is a sum
|
||||||
|
; that went past sixteen bits, which is the only test needed and costs one comparison.
|
||||||
|
RSTA
|
||||||
|
SETD.0 ClsDigitWord
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
SETD.2 ClsDigitValue
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
SETD.0 ClsWord
|
||||||
|
SETD.2 ClsDigitWord
|
||||||
|
CALL numAdd
|
||||||
|
SETD.0 ClsWord
|
||||||
|
SETD.2 ClsDigitWord
|
||||||
|
CALL numCompare
|
||||||
|
BRC clsWordTooBig
|
||||||
|
|
||||||
|
SETD.0 ClsWalk
|
||||||
|
CALL numStep
|
||||||
|
BRI clsWordLoop
|
||||||
|
|
||||||
|
clsWordGood:
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
clsWordEmpty:
|
||||||
|
SETD.0 NoDigits
|
||||||
|
CALL clsComplain
|
||||||
|
BRI clsWordNo
|
||||||
|
clsWordBadDigit:
|
||||||
|
SETD.0 BadDigit
|
||||||
|
CALL clsComplain
|
||||||
|
BRI clsWordNo
|
||||||
|
clsWordTooBig:
|
||||||
|
SETD.0 TooBigWord
|
||||||
|
CALL clsComplain
|
||||||
|
clsWordNo:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ClsWord becomes itself times ClsBase. Q is not zero if that went past sixteen bits.
|
||||||
|
;
|
||||||
|
; By repeated addition, because this machine has no multiply. The base is ten or sixteen,
|
||||||
|
; so it is at most sixteen additions per digit, and a number in a source file has four or
|
||||||
|
; five digits.
|
||||||
|
clsWordTimesBase:
|
||||||
|
SETD.0 ClsAccum
|
||||||
|
CALL numZero
|
||||||
|
SETD.0 ClsMulLeft
|
||||||
|
SETD.2 ClsBase
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
clsWordMulLoop:
|
||||||
|
SETD.0 ClsMulLeft
|
||||||
|
LDA.0
|
||||||
|
BRA clsWordMulDone
|
||||||
|
DECA
|
||||||
|
STA.0
|
||||||
|
SETD.0 ClsAccum
|
||||||
|
SETD.2 ClsWord
|
||||||
|
CALL numAdd
|
||||||
|
SETD.0 ClsAccum
|
||||||
|
SETD.2 ClsWord
|
||||||
|
CALL numCompare
|
||||||
|
BRC clsWordMulOver ; It came out smaller than what was added, so it wrapped.
|
||||||
|
BRI clsWordMulLoop
|
||||||
|
|
||||||
|
clsWordMulDone:
|
||||||
|
SETD.0 ClsWord
|
||||||
|
SETD.2 ClsAccum
|
||||||
|
CALL numSet
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
clsWordMulOver:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; The character in A as a digit in ClsBase, into ClsDigitValue. Q is zero if it is one.
|
||||||
|
clsDigit:
|
||||||
|
SETD.0 ClsHold
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
; 0 to 9
|
||||||
|
INIB 0x30
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRC clsDigitNo
|
||||||
|
SETD.0 ClsHold
|
||||||
|
LDA.0
|
||||||
|
INIB 0x3A
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRC clsDigitDecimal
|
||||||
|
|
||||||
|
; A to F, either case, and only when the base has room for them.
|
||||||
|
SETD.0 ClsBase
|
||||||
|
LDA.0
|
||||||
|
INIB 0d16
|
||||||
|
XOR
|
||||||
|
BNQ clsDigitNo
|
||||||
|
|
||||||
|
SETD.0 ClsHold
|
||||||
|
LDA.0
|
||||||
|
CALL clsUpper
|
||||||
|
SETD.0 ClsByte
|
||||||
|
LDA.0
|
||||||
|
SETD.0 ClsHold
|
||||||
|
STA.0
|
||||||
|
INIB 0x41
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRC clsDigitNo
|
||||||
|
SETD.0 ClsHold
|
||||||
|
LDA.0
|
||||||
|
INIB 0x47
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNC clsDigitNo
|
||||||
|
|
||||||
|
SETD.0 ClsHold
|
||||||
|
LDA.0
|
||||||
|
INIB 0x37 ; 'A' is ten, so the offset is 0x41 less 10.
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
MVQA
|
||||||
|
SETD.0 ClsDigitValue
|
||||||
|
STA.0
|
||||||
|
BRI clsDigitYes
|
||||||
|
|
||||||
|
clsDigitDecimal:
|
||||||
|
SETD.0 ClsHold
|
||||||
|
LDA.0
|
||||||
|
INIB 0x30
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
MVQA
|
||||||
|
SETD.0 ClsDigitValue
|
||||||
|
STA.0
|
||||||
|
; A decimal digit is a hexadecimal one too, so this needs no test of the base.
|
||||||
|
|
||||||
|
clsDigitYes:
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
clsDigitNo:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ---- Odds and ends ----
|
||||||
|
|
||||||
|
; Q is zero if the strings at DP0 and DP1 are the same, ignoring case.
|
||||||
|
;
|
||||||
|
; For the words that are part of the LANGUAGE rather than names somebody chose: Device, and
|
||||||
|
; the vectors the machine already uses. Mnemonics are matched the same way, for the same
|
||||||
|
; reason - nobody should have to remember how the manual capitalised something.
|
||||||
|
sameFolded:
|
||||||
|
LDA.0
|
||||||
|
CALL clsUpper
|
||||||
|
SETD.2 ClsByte
|
||||||
|
LDA.2
|
||||||
|
SETD.2 ClsFoldHold
|
||||||
|
STA.2
|
||||||
|
LDA.1
|
||||||
|
CALL clsUpper
|
||||||
|
SETD.2 ClsByte
|
||||||
|
LDA.2
|
||||||
|
SETD.2 ClsFoldHold
|
||||||
|
LDB.2
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNQ sameFoldedDone
|
||||||
|
LDA.0
|
||||||
|
BRA sameFoldedDone ; They ended together, so they matched all the way.
|
||||||
|
INCD.0
|
||||||
|
INCD.1
|
||||||
|
BRI sameFolded
|
||||||
|
sameFoldedDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ClsLength becomes the byte in A. Everything but a string is a small number, and this is
|
||||||
|
; how a small number is written into a sixteen bit field.
|
||||||
|
clsSetLength:
|
||||||
|
SETD.0 ClsLength
|
||||||
|
RSTB
|
||||||
|
STB.0
|
||||||
|
INCD.0
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; The character in A, folded to upper case, into ClsByte.
|
||||||
|
clsUpper:
|
||||||
|
SETD.0 ClsHold
|
||||||
|
STA.0
|
||||||
|
INIB 0x61 ; 'a'
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRC clsUpperDone
|
||||||
|
SETD.0 ClsHold
|
||||||
|
LDA.0
|
||||||
|
INIB 0x7B ; One past 'z'.
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNC clsUpperDone
|
||||||
|
SETD.0 ClsHold
|
||||||
|
LDA.0
|
||||||
|
INIB 0d32
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
MVQA
|
||||||
|
SETD.0 ClsByte
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
clsUpperDone:
|
||||||
|
SETD.0 ClsHold
|
||||||
|
LDA.0
|
||||||
|
SETD.0 ClsByte
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; The byte at DP0, offset by A, into ClsByte.
|
||||||
|
clsIndexByte:
|
||||||
|
PSHA
|
||||||
|
PSHD.0
|
||||||
|
POPB
|
||||||
|
POPA ; The low byte is on top, the way a pointer is pushed.
|
||||||
|
SETD.0 ClsWalk
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STB.0
|
||||||
|
POPA
|
||||||
|
SETD.0 ClsWalk
|
||||||
|
CALL numAddByte
|
||||||
|
SETD.1 ClsWalk
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
SETD.0 ClsByte
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Puts A at DP0 offset by the byte at DP2.
|
||||||
|
clsPutIndexed:
|
||||||
|
PSHA
|
||||||
|
PSHD.0
|
||||||
|
POPB
|
||||||
|
POPA
|
||||||
|
SETD.0 ClsPut
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STB.0
|
||||||
|
LDA.2
|
||||||
|
SETD.0 ClsPut
|
||||||
|
CALL numAddByte
|
||||||
|
SETD.1 ClsPut
|
||||||
|
LDD.0.1
|
||||||
|
POPA
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Says what is wrong, with the file and the line, the way an error ought to.
|
||||||
|
clsComplain:
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 InFileText
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 SrcName
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 AtLineText
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 TokLine
|
||||||
|
LDA.0
|
||||||
|
INCD.0
|
||||||
|
LDB.0
|
||||||
|
SWI osPrintNumber
|
||||||
|
SETD.0 SaidText
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 TokText
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 SaidEnd
|
||||||
|
SWI osPrintString
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
ClsType:
|
||||||
|
0x00
|
||||||
|
ClsLength:
|
||||||
|
0x00 0x00
|
||||||
|
ClsOpcode:
|
||||||
|
0x00
|
||||||
|
ClsShape:
|
||||||
|
0x00
|
||||||
|
ClsSelectorValue:
|
||||||
|
0x00 0x00
|
||||||
|
ClsGiven:
|
||||||
|
0x00
|
||||||
|
ClsWanted:
|
||||||
|
0x00
|
||||||
|
ClsNameLength:
|
||||||
|
0x00
|
||||||
|
ClsName:
|
||||||
|
#Reserve 0d5
|
||||||
|
ClsValue:
|
||||||
|
0x00
|
||||||
|
ClsWord:
|
||||||
|
0x00 0x00
|
||||||
|
ClsAccum:
|
||||||
|
0x00 0x00
|
||||||
|
ClsDigitWord:
|
||||||
|
0x00 0x00
|
||||||
|
ClsBase:
|
||||||
|
0x00
|
||||||
|
ClsDigitValue:
|
||||||
|
0x00
|
||||||
|
ClsMulLeft:
|
||||||
|
0x00
|
||||||
|
ClsHold:
|
||||||
|
0x00
|
||||||
|
ClsDigitHold:
|
||||||
|
0x00
|
||||||
|
ClsByte:
|
||||||
|
0x00
|
||||||
|
ClsFoldHold:
|
||||||
|
0x00
|
||||||
|
ClsLeft:
|
||||||
|
0x00
|
||||||
|
ClsLeft2:
|
||||||
|
0x00
|
||||||
|
ClsWalk:
|
||||||
|
0x00 0x00
|
||||||
|
ClsEntry:
|
||||||
|
0x00 0x00
|
||||||
|
ClsPut:
|
||||||
|
0x00 0x00
|
||||||
|
|
||||||
|
InFileText:
|
||||||
|
" in "
|
||||||
|
AtLineText:
|
||||||
|
" at line "
|
||||||
|
SaidText:
|
||||||
|
"
|
||||||
|
it said: "
|
||||||
|
SaidEnd:
|
||||||
|
"
|
||||||
|
"
|
||||||
|
BadPrefix:
|
||||||
|
"a literal needs 0x for hexadecimal or 0d for decimal"
|
||||||
|
NoDigits:
|
||||||
|
"a literal with no digits after its prefix"
|
||||||
|
BadDigit:
|
||||||
|
"that is not a digit in the base the prefix asked for"
|
||||||
|
TooBig:
|
||||||
|
"a literal too large to fit in one byte"
|
||||||
|
TooBigWord:
|
||||||
|
"a number too large to fit in sixteen bits"
|
||||||
|
TooManySelectors:
|
||||||
|
"more Data Pointer selectors than that instruction has pointers to name"
|
||||||
|
BadSelector:
|
||||||
|
"that does not name a Data Pointer, which run from 0 to 3"
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
; The label table: the only thing that survives between the two passes.
|
||||||
|
;
|
||||||
|
; Names are packed end to end in an arena and each index entry holds a pointer into it,
|
||||||
|
; rather than every entry carrying a field wide enough for the longest name. MEASURED on
|
||||||
|
; CosmOS, which is the biggest thing this will ever be asked to assemble: 453 labels
|
||||||
|
; averaging 11.3 characters. Packed they come to about 7,400 bytes; in 32 byte fields they
|
||||||
|
; would come to 15,400. The arena is worth the handful of extra instructions.
|
||||||
|
;
|
||||||
|
; Four bytes an index entry: two saying where the name is, two saying what it resolves to.
|
||||||
|
;
|
||||||
|
; A name is stored WITHOUT its colon, so that a definition and a use of it compare equal
|
||||||
|
; without either side having to know which it was looking at.
|
||||||
|
;
|
||||||
|
; The first pass fills this and the second only reads it. That is what makes a forward
|
||||||
|
; reference ordinary rather than special: by the time anything is emitted, every name in
|
||||||
|
; the program already has an address.
|
||||||
|
;
|
||||||
|
; Written by Anachronaut
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
; Empties the table.
|
||||||
|
labReset:
|
||||||
|
SETD.0 LabCount
|
||||||
|
CALL numZero
|
||||||
|
SETD.0 LabUsed
|
||||||
|
CALL numZero
|
||||||
|
SETD.0 LabNext
|
||||||
|
SETD.2 ScratchLabArena
|
||||||
|
CALL numSet
|
||||||
|
SETD.0 LabBase
|
||||||
|
SETD.2 ScratchLabIndex
|
||||||
|
CALL numSet
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Adds the name at DP0, meaning the address in A and B. Q is zero if it went in.
|
||||||
|
;
|
||||||
|
; A name already in the table is refused rather than replaced: one name may mean one place,
|
||||||
|
; and quietly taking the second would move everything that referred to the first.
|
||||||
|
labAdd:
|
||||||
|
SETD.2 LabPutAddress
|
||||||
|
STA.2
|
||||||
|
INCD.2
|
||||||
|
STB.2
|
||||||
|
SETD.2 LabSubject
|
||||||
|
STD.0.2
|
||||||
|
|
||||||
|
CALL labFind
|
||||||
|
BNQ labAddFresh
|
||||||
|
SETD.0 LabTwice
|
||||||
|
CALL labComplain
|
||||||
|
BRI labAddNo
|
||||||
|
|
||||||
|
labAddFresh:
|
||||||
|
SETD.0 LabCount
|
||||||
|
SETD.2 LabLimit
|
||||||
|
CALL numCompare
|
||||||
|
BNC labAddFull ; The index is as full as it goes.
|
||||||
|
|
||||||
|
; And the arena, counting the zero that ends the name.
|
||||||
|
SETD.1 LabSubject
|
||||||
|
LDD.0.1
|
||||||
|
CALL labLength
|
||||||
|
SETD.0 LabEnd
|
||||||
|
SETD.2 LabUsed
|
||||||
|
CALL numSet
|
||||||
|
SETD.0 LabEnd
|
||||||
|
SETD.2 LabLength
|
||||||
|
CALL numAdd
|
||||||
|
SETD.0 LabRoom
|
||||||
|
SETD.2 LabEnd
|
||||||
|
CALL numCompare
|
||||||
|
BRC labAddCrowded ; The arena is smaller than where this name would end.
|
||||||
|
|
||||||
|
; The index entry: where the name is about to go, and what it means.
|
||||||
|
SETD.0 LabWhich
|
||||||
|
SETD.2 LabCount
|
||||||
|
CALL numSet
|
||||||
|
CALL labEntryAt
|
||||||
|
|
||||||
|
SETD.1 LabEntry
|
||||||
|
LDD.0.1
|
||||||
|
SETD.1 LabNext
|
||||||
|
LDD.1.1
|
||||||
|
PSHD.1
|
||||||
|
POPB
|
||||||
|
POPA ; The low byte is on top, the way a pointer is pushed.
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STB.0
|
||||||
|
INCD.0
|
||||||
|
SETD.2 LabPutAddress
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
INCD.2
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
; And the name itself, into the arena.
|
||||||
|
SETD.1 LabNext
|
||||||
|
LDD.1.1
|
||||||
|
SETD.2 LabSubject
|
||||||
|
LDD.0.2
|
||||||
|
labAddLoop:
|
||||||
|
LDA.0
|
||||||
|
STA.1
|
||||||
|
BRA labAddCopied
|
||||||
|
INCD.0
|
||||||
|
INCD.1
|
||||||
|
BRI labAddLoop
|
||||||
|
labAddCopied:
|
||||||
|
INCD.1 ; Past the zero, which was copied with the rest.
|
||||||
|
SETD.0 LabNext
|
||||||
|
STD.1.0
|
||||||
|
|
||||||
|
SETD.0 LabUsed
|
||||||
|
SETD.2 LabLength
|
||||||
|
CALL numAdd
|
||||||
|
SETD.0 LabCount
|
||||||
|
CALL numStep
|
||||||
|
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
labAddFull:
|
||||||
|
SETD.0 LabFull
|
||||||
|
CALL labComplain
|
||||||
|
BRI labAddNo
|
||||||
|
labAddCrowded:
|
||||||
|
SETD.0 LabNoRoom
|
||||||
|
CALL labComplain
|
||||||
|
labAddNo:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Looks up the name at DP0. Q is zero if it is there, and then LabAddress is what it means.
|
||||||
|
;
|
||||||
|
; A straight walk from the front. With 453 labels and a few thousand uses of them that is
|
||||||
|
; the slowest thing the assembler does, and it is deliberately the simple version: sorting
|
||||||
|
; the table or bucketing it on the first character are both easy later, and neither is
|
||||||
|
; worth writing before anything has been measured.
|
||||||
|
labFind:
|
||||||
|
SETD.2 LabSought
|
||||||
|
STD.0.2
|
||||||
|
SETD.0 LabWhich
|
||||||
|
CALL numZero
|
||||||
|
|
||||||
|
labFindLoop:
|
||||||
|
SETD.0 LabWhich
|
||||||
|
SETD.2 LabCount
|
||||||
|
CALL numCompare
|
||||||
|
BNC labFindMissing ; Walked the whole table without a match.
|
||||||
|
|
||||||
|
CALL labEntryAt
|
||||||
|
SETD.1 LabEntry
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
INCD.0
|
||||||
|
LDB.0
|
||||||
|
SETD.0 LabNamePointer
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STB.0
|
||||||
|
|
||||||
|
SETD.1 LabNamePointer
|
||||||
|
LDD.0.1
|
||||||
|
SETD.1 LabSought
|
||||||
|
LDD.1.1
|
||||||
|
CALL sameText
|
||||||
|
BRQ labFindGot
|
||||||
|
|
||||||
|
SETD.0 LabWhich
|
||||||
|
CALL numStep
|
||||||
|
BRI labFindLoop
|
||||||
|
|
||||||
|
labFindGot:
|
||||||
|
SETD.1 LabEntry
|
||||||
|
LDD.0.1
|
||||||
|
INCD.0
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
INCD.0
|
||||||
|
LDB.0
|
||||||
|
SETD.0 LabAddress
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STB.0
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
labFindMissing:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Where entry number LabWhich is, into LabEntry. Four bytes an entry, so the offset is the
|
||||||
|
; number doubled twice - there being no multiply on this machine, and none needed.
|
||||||
|
labEntryAt:
|
||||||
|
SETD.0 LabOffset
|
||||||
|
SETD.2 LabWhich
|
||||||
|
CALL numSet
|
||||||
|
SETD.0 LabOffset
|
||||||
|
SETD.2 LabOffset
|
||||||
|
CALL numAdd
|
||||||
|
SETD.0 LabOffset
|
||||||
|
SETD.2 LabOffset
|
||||||
|
CALL numAdd
|
||||||
|
SETD.0 LabEntry
|
||||||
|
SETD.2 LabBase
|
||||||
|
CALL numSet
|
||||||
|
SETD.0 LabEntry
|
||||||
|
SETD.2 LabOffset
|
||||||
|
CALL numAdd
|
||||||
|
RET
|
||||||
|
|
||||||
|
; How long the string at DP0 is, counting the zero on the end, into LabLength.
|
||||||
|
labLength:
|
||||||
|
SETD.1 LabLenWalk
|
||||||
|
STD.0.1
|
||||||
|
SETD.0 LabLength
|
||||||
|
CALL numZero
|
||||||
|
labLengthLoop:
|
||||||
|
SETD.0 LabLength
|
||||||
|
CALL numStep
|
||||||
|
SETD.1 LabLenWalk
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
BRA labLengthDone
|
||||||
|
SETD.0 LabLenWalk
|
||||||
|
CALL numStep
|
||||||
|
BRI labLengthLoop
|
||||||
|
labLengthDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
labComplain:
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 LabNamed
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 TokText
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 LabAtLine
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 TokLine
|
||||||
|
LDA.0
|
||||||
|
INCD.0
|
||||||
|
LDB.0
|
||||||
|
SWI osPrintNumber
|
||||||
|
SETD.0 LabNewLine
|
||||||
|
SWI osPrintString
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
LabCount:
|
||||||
|
0x00 0x00
|
||||||
|
LabUsed:
|
||||||
|
0x00 0x00
|
||||||
|
LabNext:
|
||||||
|
0x00 0x00
|
||||||
|
LabBase:
|
||||||
|
0x00 0x00
|
||||||
|
LabAddress:
|
||||||
|
0x00 0x00
|
||||||
|
LabPutAddress:
|
||||||
|
0x00 0x00
|
||||||
|
LabNamePointer:
|
||||||
|
0x00 0x00
|
||||||
|
LabSought:
|
||||||
|
0x00 0x00
|
||||||
|
LabSubject:
|
||||||
|
0x00 0x00
|
||||||
|
LabEntry:
|
||||||
|
0x00 0x00
|
||||||
|
LabOffset:
|
||||||
|
0x00 0x00
|
||||||
|
LabWhich:
|
||||||
|
0x00 0x00
|
||||||
|
LabLength:
|
||||||
|
0x00 0x00
|
||||||
|
LabLenWalk:
|
||||||
|
0x00 0x00
|
||||||
|
LabEnd:
|
||||||
|
0x00 0x00
|
||||||
|
|
||||||
|
; How many labels there may be, and how many bytes of name between them.
|
||||||
|
;
|
||||||
|
; SIZED FOR THE ASSEMBLER ITSELF, which turns out to be the largest thing it is asked to
|
||||||
|
; build: 555 labels and about 6,800 bytes of name, against CosmOS's 475 and 5,881. Running into
|
||||||
|
; either limit says so rather than writing past the end of the table. The buffers live
|
||||||
|
; above the program rather than inside it - see the scratch map in Asm.asm.
|
||||||
|
LabLimit:
|
||||||
|
0x03 0x00
|
||||||
|
LabRoom:
|
||||||
|
0x20 0x00
|
||||||
|
|
||||||
|
LabNamed:
|
||||||
|
": "
|
||||||
|
LabAtLine:
|
||||||
|
", at line "
|
||||||
|
LabNewLine:
|
||||||
|
"
|
||||||
|
"
|
||||||
|
LabTwice:
|
||||||
|
"that label is defined twice"
|
||||||
|
LabFull:
|
||||||
|
"too many labels"
|
||||||
|
LabNoRoom:
|
||||||
|
"no room left for label names"
|
||||||
|
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
; 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
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
; The source reader on its own, before anything is built on top of it.
|
||||||
|
;
|
||||||
|
; Everything else in the assembler reads its source through srcNext, so a fault in here
|
||||||
|
; would turn up later as a mysterious wrong byte in an output file. It is worth checking by
|
||||||
|
; itself, against a file whose contents are already known.
|
||||||
|
;
|
||||||
|
; It reads whatever it was told to, prints every character back, and then says how many
|
||||||
|
; lines went past. The file it is given is deliberately bigger than one block, so the seam
|
||||||
|
; between one block and the next is crossed rather than assumed.
|
||||||
|
;
|
||||||
|
; 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
|
||||||
|
|
||||||
|
readLoop:
|
||||||
|
CALL srcNext
|
||||||
|
BNQ readDone
|
||||||
|
SETD.0 SrcChar
|
||||||
|
LDA.0
|
||||||
|
OUTA 0x00 ; Straight to the console: one character is not a string.
|
||||||
|
BRI readLoop
|
||||||
|
|
||||||
|
readDone:
|
||||||
|
SETD.0 LinesText
|
||||||
|
SWI osPrintString
|
||||||
|
SETD.0 SrcLine
|
||||||
|
LDA.0
|
||||||
|
INCD.0
|
||||||
|
LDB.0
|
||||||
|
SWI osPrintNumber
|
||||||
|
SETD.0 NewLine
|
||||||
|
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
|
||||||
|
|
||||||
|
LinesText:
|
||||||
|
"---- lines: "
|
||||||
|
NewLine:
|
||||||
|
"
|
||||||
|
"
|
||||||
|
AskText:
|
||||||
|
"say which file
|
||||||
|
"
|
||||||
|
NoFileText:
|
||||||
|
"no such file
|
||||||
|
"
|
||||||
|
|
||||||
|
; The libraries go last, after both segments have been based. An included file that carries
|
||||||
|
; code brings its own #Program and #Data lines with it, and a #Base has to come before
|
||||||
|
; anything is in the segment it bases - so the bases are set here and the code arrives after.
|
||||||
|
#Include scratch.asm
|
||||||
|
#Include numbers.asm
|
||||||
|
#Include source.asm
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
; Where the assembler's big buffers live.
|
||||||
|
;
|
||||||
|
; A map rather than a set of declarations, and it has a file of its own because the reader
|
||||||
|
; and the label table both need addresses out of it while neither includes the other.
|
||||||
|
;
|
||||||
|
#Data
|
||||||
|
|
||||||
|
; NOT #Reserve, AND THAT IS THE WHOLE POINT. Reserved space in a segment is written into
|
||||||
|
; the file as zeroes and copied at load, so 22K of scratch made a 34K file - and a loaded
|
||||||
|
; program is staged at 0x8000 before being put in place, which leaves exactly 32,768 bytes
|
||||||
|
; for the whole of it. The assembler could not load itself.
|
||||||
|
;
|
||||||
|
; None of this is initialised data. It is scratch, wanted only while the assembler is
|
||||||
|
; running, and while it is running everything above its own data is free: the system keeps
|
||||||
|
; below 0x1000, the staging area is only in use during a load, and the Stack comes down
|
||||||
|
; from the top. So the addresses are written down here and the file carries none of it.
|
||||||
|
;
|
||||||
|
; 0x8000 3072 the label index, 768 entries of four
|
||||||
|
; 0x8C00 8192 the label names, packed end to end
|
||||||
|
; 0xAC00 13312 the binary being built
|
||||||
|
; 0xE000 1792 the vector names, 64 entries of twenty eight
|
||||||
|
; 0xE700 1758 the reader's stack, six levels of 293
|
||||||
|
; 0xEE00 368 which files have been included, sixteen names of 23
|
||||||
|
;
|
||||||
|
; That ends at 0xEF70, with the Stack coming down from 0xFFFF above it - about four
|
||||||
|
; kilobytes, against the tens of bytes of CALL frames this ever nests.
|
||||||
|
;
|
||||||
|
; THE TWO THINGS THAT DECIDE THESE SIZES are the largest program it will be asked to build
|
||||||
|
; and the largest one it will be asked to read. CosmOS is 475 labels and 9,564 bytes of
|
||||||
|
; output; the assembler itself is 555 labels, about 6,800 bytes of name and 11,648 of output. The
|
||||||
|
; second is bigger than the first, which is worth knowing: the hardest thing this assembles
|
||||||
|
; is not the operating system, it is itself.
|
||||||
|
ScratchLabIndex:
|
||||||
|
0x80 0x00
|
||||||
|
ScratchLabArena:
|
||||||
|
0x8C 0x00
|
||||||
|
ScratchImage:
|
||||||
|
0xAC 0x00
|
||||||
|
ScratchVecNames:
|
||||||
|
0xE0 0x00
|
||||||
|
ScratchSrcStack:
|
||||||
|
0xE7 0x00
|
||||||
|
ScratchIncNames:
|
||||||
|
0xEE 0x00
|
||||||
@@ -0,0 +1,552 @@
|
|||||||
|
; The source reader: characters out of a file of any size.
|
||||||
|
;
|
||||||
|
; Everything else in the assembler sits on this, so it is the first thing built and the
|
||||||
|
; thing most worth getting right. It hands out one character at a time and keeps a line
|
||||||
|
; number, which is what lets an error say where it happened rather than only what it was.
|
||||||
|
;
|
||||||
|
; A FILE IS NEVER HELD WHOLE. It arrives a block at a time through osFileBlock, into one
|
||||||
|
; buffer of 256 bytes, and is fetched again when the buffer runs out. That is why the
|
||||||
|
; assembler can read a source file bigger than the memory it runs in - which cosmos.asm,
|
||||||
|
; at 56,047 bytes, already is.
|
||||||
|
;
|
||||||
|
; The file is read TWICE, once per pass, and srcRewind is how the second pass starts over.
|
||||||
|
; Nothing is kept between the passes but the label table.
|
||||||
|
;
|
||||||
|
; ---- A stack of readers ----
|
||||||
|
;
|
||||||
|
; #Include splices another file in where it stands, so the reader is a stack: srcInclude
|
||||||
|
; puts the current file's whole state aside, opens the new one, and the end of that file
|
||||||
|
; pops the old one back and carries on where it left off. Everything above works on "the
|
||||||
|
; current file" and does not know the stack is there.
|
||||||
|
;
|
||||||
|
; THE WHOLE STATE GOES ASIDE, buffer and all, 293 bytes of it. Keeping only the position
|
||||||
|
; and re-reading the block on the way back would be cheaper in memory and would cost a disk
|
||||||
|
; read per pop; at six levels of nesting the copy costs less than the arithmetic to avoid it.
|
||||||
|
; The buffer pointer survives the trip because it points into the buffer, which is always at
|
||||||
|
; the same address - the state is saved from and restored to the same variables.
|
||||||
|
;
|
||||||
|
; A FILE IS INCLUDED ONCE. Including it twice is not an error, it just does nothing, which
|
||||||
|
; is what lets two libraries depend on a third. The names are remembered for the length of
|
||||||
|
; one pass and forgotten between them, because the second pass has to walk exactly the same
|
||||||
|
; tree the first one did.
|
||||||
|
;
|
||||||
|
; Written by Anachronaut
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
; Opens the file DP0 names. Q is zero if it is there.
|
||||||
|
;
|
||||||
|
; The name is copied rather than pointed at, because the caller's copy is in the caller's
|
||||||
|
; memory and every later block read has to name the file again - there being no such thing
|
||||||
|
; as an open file to hold on to.
|
||||||
|
srcOpen:
|
||||||
|
SETD.1 SrcTopName
|
||||||
|
CALL srcKeepName ; Kept apart, so that each pass can open it again.
|
||||||
|
CALL srcRestart
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Back to the top of the tree: the first file, no includes taken yet, nothing on the stack.
|
||||||
|
; This is what starts each pass.
|
||||||
|
srcRestart:
|
||||||
|
RSTA
|
||||||
|
SETD.0 SrcDepth
|
||||||
|
STA.0
|
||||||
|
SETD.0 IncCount
|
||||||
|
STA.0
|
||||||
|
SETD.0 SrcTopName
|
||||||
|
SETD.1 SrcName
|
||||||
|
CALL srcKeepName
|
||||||
|
CALL srcRewind
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Back to the first character, for the second pass.
|
||||||
|
srcRewind:
|
||||||
|
SETD.0 SrcIndex
|
||||||
|
CALL numZero
|
||||||
|
SETD.0 SrcAt
|
||||||
|
CALL numZero
|
||||||
|
SETD.0 SrcCount
|
||||||
|
CALL numZero
|
||||||
|
RSTA
|
||||||
|
SETD.0 SrcEnded
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
; The line number counts from one, the way an editor does.
|
||||||
|
SETD.0 SrcLine
|
||||||
|
CALL numZero
|
||||||
|
SETD.0 SrcLine
|
||||||
|
CALL numStep
|
||||||
|
|
||||||
|
; Ask how big it is, which is both the answer to "is it there" and the thing that says
|
||||||
|
; when to stop asking for blocks.
|
||||||
|
SETD.0 SrcName
|
||||||
|
SWI osFileInfo
|
||||||
|
BNQ srcRewindNo
|
||||||
|
PSHD.3
|
||||||
|
POPB
|
||||||
|
POPA
|
||||||
|
SETD.0 SrcBlocks
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STB.0
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD ; Q is zero: it is there.
|
||||||
|
RET
|
||||||
|
|
||||||
|
srcRewindNo:
|
||||||
|
INIA 0d1
|
||||||
|
SETD.0 SrcEnded
|
||||||
|
STA.0
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD ; Q is not zero: it is not.
|
||||||
|
RET
|
||||||
|
|
||||||
|
; The next character of the file, into SrcChar. Q is zero if there was one, and something
|
||||||
|
; else at the end of the file.
|
||||||
|
srcNext:
|
||||||
|
SETD.0 SrcEnded
|
||||||
|
LDA.0
|
||||||
|
BNA srcAtEnd
|
||||||
|
|
||||||
|
; Is the buffer used up? SrcAt counts how far into it we have read and SrcCount how many
|
||||||
|
; of its bytes are the file's, which is 256 for every block but a short last one.
|
||||||
|
SETD.0 SrcAt
|
||||||
|
SETD.2 SrcCount
|
||||||
|
CALL numCompare
|
||||||
|
BNQ srcHaveByte
|
||||||
|
CALL srcLoad
|
||||||
|
BNQ srcAtEnd
|
||||||
|
|
||||||
|
srcHaveByte:
|
||||||
|
SETD.1 SrcPointer
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
INCD.0
|
||||||
|
STD.0.1
|
||||||
|
SETD.0 SrcChar
|
||||||
|
STA.0
|
||||||
|
SETD.0 SrcAt
|
||||||
|
CALL numStep
|
||||||
|
|
||||||
|
; A newline is what makes the next character part of the next line. Counting it here,
|
||||||
|
; as it is handed out, means the line number always describes the character just given.
|
||||||
|
SETD.0 SrcChar
|
||||||
|
LDA.0
|
||||||
|
INIB 0x0A
|
||||||
|
XOR
|
||||||
|
BNQ srcNextDone
|
||||||
|
SETD.0 SrcLine
|
||||||
|
CALL numStep
|
||||||
|
|
||||||
|
srcNextDone:
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD ; Q is zero: there was a character.
|
||||||
|
RET
|
||||||
|
|
||||||
|
srcAtEnd:
|
||||||
|
; This file is finished. If it was included by another, that one is not: it goes back on
|
||||||
|
; and the next character comes from where it left off, which is what makes an include
|
||||||
|
; read as though the text had been written there.
|
||||||
|
INIA 0d1
|
||||||
|
SETD.0 SrcEnded
|
||||||
|
STA.0
|
||||||
|
SETD.0 SrcDepth
|
||||||
|
LDA.0
|
||||||
|
BRA srcNothingLeft
|
||||||
|
CALL srcPop
|
||||||
|
BRI srcNext
|
||||||
|
|
||||||
|
srcNothingLeft:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD ; Q is not zero: there is no more source anywhere.
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ---- The stack ----
|
||||||
|
|
||||||
|
; Opens the file named at DP0 as though its text were written here. Q is zero if the
|
||||||
|
; reader is now inside it, or if it had already been included and there is nothing to do.
|
||||||
|
srcInclude:
|
||||||
|
SETD.1 IncWanted
|
||||||
|
CALL srcKeepName
|
||||||
|
CALL srcSeenAlready
|
||||||
|
BRQ srcIncludeSkip
|
||||||
|
|
||||||
|
SETD.0 SrcDepth
|
||||||
|
LDA.0
|
||||||
|
SETD.2 SrcDepthLimit
|
||||||
|
LDB.2
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNC srcTooDeep
|
||||||
|
|
||||||
|
CALL srcRemember
|
||||||
|
CALL srcPush
|
||||||
|
SETD.0 IncWanted
|
||||||
|
SETD.1 SrcName
|
||||||
|
CALL srcKeepName
|
||||||
|
CALL srcRewind
|
||||||
|
BNQ srcIncludeGone
|
||||||
|
RET ; Q is zero, out of srcRewind.
|
||||||
|
|
||||||
|
srcIncludeSkip:
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD ; Already in, so this line does nothing at all.
|
||||||
|
RET
|
||||||
|
|
||||||
|
srcIncludeGone:
|
||||||
|
; The file is not there. The stack is left as it is: the caller stops the assembly, and
|
||||||
|
; unwinding for the sake of tidiness would only hide where it happened.
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
srcTooDeep:
|
||||||
|
SETD.0 TooDeepText
|
||||||
|
SWI osPrintString
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Puts the current file aside and makes room for another.
|
||||||
|
srcPush:
|
||||||
|
CALL srcSlot
|
||||||
|
SETD.0 SrcState
|
||||||
|
SETD.1 SrcSlot
|
||||||
|
LDD.1.1
|
||||||
|
CALL srcCopyState
|
||||||
|
SETD.0 SrcDepth
|
||||||
|
LDA.0
|
||||||
|
INCA
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; And takes it back.
|
||||||
|
srcPop:
|
||||||
|
SETD.0 SrcDepth
|
||||||
|
LDA.0
|
||||||
|
DECA
|
||||||
|
STA.0
|
||||||
|
CALL srcSlot
|
||||||
|
SETD.1 SrcSlot
|
||||||
|
LDD.0.1
|
||||||
|
SETD.1 SrcState
|
||||||
|
CALL srcCopyState
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Puts one character back, so that the next read produces it again. The character itself
|
||||||
|
; is in A, because whether it was a newline decides whether a line goes back too.
|
||||||
|
;
|
||||||
|
; The tokenizer holds one character of lookahead, and at an #Include that character belongs
|
||||||
|
; to the file about to be put aside. Undoing the read is how it stays with that file: when
|
||||||
|
; the file is opened again the character is simply still there, and nothing has to be
|
||||||
|
; carried across the include or handed back at some moment chosen by the reader.
|
||||||
|
;
|
||||||
|
; CARRYING IT ACROSS WAS THE OBVIOUS THING AND IT WAS WRONG. A file runs out in the middle
|
||||||
|
; of whatever the tokenizer happens to be doing, so handing the character back then injects
|
||||||
|
; it into the middle of a word: `start:` came back as `s` and then `tart:`, which assembles
|
||||||
|
; into a file that looks entirely reasonable.
|
||||||
|
srcStepBack:
|
||||||
|
INIB 0x0A
|
||||||
|
XOR
|
||||||
|
BNQ srcStepBackAt
|
||||||
|
SETD.0 SrcLine
|
||||||
|
SETD.2 SrcOne
|
||||||
|
CALL numTake ; A newline not yet read has not started a line either.
|
||||||
|
|
||||||
|
srcStepBackAt:
|
||||||
|
SETD.0 SrcAt
|
||||||
|
SETD.2 SrcOne
|
||||||
|
CALL numTake
|
||||||
|
SETD.0 SrcPointer
|
||||||
|
SETD.2 SrcOne
|
||||||
|
CALL numTake
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Where the slot for the current depth is, into SrcSlot.
|
||||||
|
srcSlot:
|
||||||
|
SETD.0 SrcSlot
|
||||||
|
SETD.2 ScratchSrcStack
|
||||||
|
CALL numSet ; WHERE the stack is, not what is in it.
|
||||||
|
SETD.0 SrcSlotLeft
|
||||||
|
SETD.2 SrcDepth
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
srcSlotLoop:
|
||||||
|
SETD.0 SrcSlotLeft
|
||||||
|
LDA.0
|
||||||
|
BRA srcSlotDone
|
||||||
|
DECA
|
||||||
|
STA.0
|
||||||
|
SETD.0 SrcSlot
|
||||||
|
SETD.2 SrcStateBytes
|
||||||
|
CALL numAdd
|
||||||
|
BRI srcSlotLoop
|
||||||
|
srcSlotDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; The whole reader state, from DP0 to DP1.
|
||||||
|
srcCopyState:
|
||||||
|
SETD.2 SrcCopyFrom
|
||||||
|
STD.0.2
|
||||||
|
SETD.2 SrcCopyTo
|
||||||
|
STD.1.2
|
||||||
|
SETD.0 SrcCopyLeft
|
||||||
|
SETD.2 SrcStateBytes
|
||||||
|
CALL numSet
|
||||||
|
srcCopyLoop:
|
||||||
|
SETD.1 SrcCopyFrom
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
SETD.1 SrcCopyTo
|
||||||
|
LDD.0.1
|
||||||
|
STA.0
|
||||||
|
SETD.0 SrcCopyFrom
|
||||||
|
CALL numStep
|
||||||
|
SETD.0 SrcCopyTo
|
||||||
|
CALL numStep
|
||||||
|
SETD.0 SrcCopyLeft
|
||||||
|
SETD.2 SrcOne
|
||||||
|
CALL numTake
|
||||||
|
SETD.0 SrcCopyLeft
|
||||||
|
LDA.0
|
||||||
|
INCD.0
|
||||||
|
LDB.0
|
||||||
|
OR
|
||||||
|
BNQ srcCopyLoop
|
||||||
|
RET
|
||||||
|
|
||||||
|
; ---- Which files have been in ----
|
||||||
|
|
||||||
|
; Q is zero if IncWanted has already been included in this pass.
|
||||||
|
srcSeenAlready:
|
||||||
|
RSTA
|
||||||
|
SETD.0 IncLeft
|
||||||
|
STA.0
|
||||||
|
srcSeenLoop:
|
||||||
|
SETD.0 IncLeft
|
||||||
|
LDA.0
|
||||||
|
SETD.2 IncCount
|
||||||
|
LDB.2
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRQ srcSeenNo
|
||||||
|
CALL srcSeenSlot
|
||||||
|
SETD.1 IncSlot
|
||||||
|
LDD.0.1
|
||||||
|
SETD.1 IncWanted
|
||||||
|
CALL sameText
|
||||||
|
BRQ srcSeenYes
|
||||||
|
SETD.0 IncLeft
|
||||||
|
LDA.0
|
||||||
|
INCA
|
||||||
|
STA.0
|
||||||
|
BRI srcSeenLoop
|
||||||
|
srcSeenYes:
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
srcSeenNo:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Writes IncWanted down as having been included.
|
||||||
|
srcRemember:
|
||||||
|
SETD.0 IncLeft
|
||||||
|
SETD.2 IncCount
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
CALL srcSeenSlot
|
||||||
|
SETD.0 IncWanted
|
||||||
|
SETD.1 IncSlot
|
||||||
|
LDD.1.1
|
||||||
|
CALL srcKeepName
|
||||||
|
SETD.0 IncCount
|
||||||
|
LDA.0
|
||||||
|
INCA
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Where name number IncLeft sits, into IncSlot. Fixed fields of 23 bytes: there are few of
|
||||||
|
; these and they are short, so an arena would cost more code than it saved.
|
||||||
|
srcSeenSlot:
|
||||||
|
SETD.0 IncSlot
|
||||||
|
SETD.2 ScratchIncNames
|
||||||
|
CALL numSet
|
||||||
|
SETD.0 IncSlotLeft
|
||||||
|
SETD.2 IncLeft
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
srcSeenSlotLoop:
|
||||||
|
SETD.0 IncSlotLeft
|
||||||
|
LDA.0
|
||||||
|
BRA srcSeenSlotDone
|
||||||
|
DECA
|
||||||
|
STA.0
|
||||||
|
INIA 0d23
|
||||||
|
SETD.0 IncSlot
|
||||||
|
CALL numAddByte
|
||||||
|
BRI srcSeenSlotLoop
|
||||||
|
srcSeenSlotDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Fetches the block SrcIndex names, and steps SrcIndex past it. Q is zero if there was one.
|
||||||
|
;
|
||||||
|
; Running off the end is not a failure here: osFileBlock answers three for a block past the
|
||||||
|
; end of the file, which is how a reader finds out it has finished. Any other refusal is a
|
||||||
|
; real one, and both come back the same way because there is nothing useful to do about
|
||||||
|
; either except stop.
|
||||||
|
srcLoad:
|
||||||
|
SETD.0 SrcName
|
||||||
|
SETD.1 SrcBuffer
|
||||||
|
SETD.2 SrcIndex
|
||||||
|
LDA.2
|
||||||
|
INCD.2
|
||||||
|
LDB.2
|
||||||
|
SWI osFileBlock
|
||||||
|
BNQ srcLoadNo
|
||||||
|
|
||||||
|
; DP3 says how many of the block's bytes belong to the file: a whole 256 except in a
|
||||||
|
; short last one, which is why it comes back in a pointer and not a register.
|
||||||
|
PSHD.3
|
||||||
|
POPB
|
||||||
|
POPA
|
||||||
|
SETD.0 SrcCount
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STB.0
|
||||||
|
|
||||||
|
SETD.0 SrcAt
|
||||||
|
CALL numZero
|
||||||
|
SETD.0 SrcIndex
|
||||||
|
CALL numStep
|
||||||
|
|
||||||
|
; The walking pointer starts at the front of the buffer again.
|
||||||
|
SETD.0 SrcBuffer
|
||||||
|
SETD.1 SrcPointer
|
||||||
|
STD.0.1
|
||||||
|
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
srcLoadNo:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Copies the name at DP0 into DP1, up to 22 characters of it and the zero after them,
|
||||||
|
; which is as long as a name on this filesystem may be.
|
||||||
|
srcKeepName:
|
||||||
|
INIA 0d22
|
||||||
|
SETD.2 SrcLeft
|
||||||
|
STA.2
|
||||||
|
srcKeepLoop:
|
||||||
|
LDA.0
|
||||||
|
BRA srcKeepEnd
|
||||||
|
STA.1
|
||||||
|
INCD.0
|
||||||
|
INCD.1
|
||||||
|
LDA.2
|
||||||
|
DECA
|
||||||
|
STA.2
|
||||||
|
BNA srcKeepLoop
|
||||||
|
srcKeepEnd:
|
||||||
|
RSTA
|
||||||
|
STA.1 ; The zero that makes it a string.
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
; ---- The current file, as one block so that it can be put aside in one piece ----
|
||||||
|
;
|
||||||
|
SrcState:
|
||||||
|
SrcName:
|
||||||
|
#Reserve 0d23
|
||||||
|
SrcBlocks:
|
||||||
|
0x00 0x00
|
||||||
|
SrcIndex:
|
||||||
|
0x00 0x00
|
||||||
|
SrcCount:
|
||||||
|
0x00 0x00
|
||||||
|
SrcAt:
|
||||||
|
0x00 0x00
|
||||||
|
SrcLine:
|
||||||
|
0x00 0x00
|
||||||
|
SrcPointer:
|
||||||
|
0x00 0x00
|
||||||
|
SrcEnded:
|
||||||
|
0x00
|
||||||
|
|
||||||
|
; One block, which is the whole of what a source file costs in memory however big it is.
|
||||||
|
SrcBuffer:
|
||||||
|
#Reserve 0d256
|
||||||
|
|
||||||
|
; 292 bytes: a name of 23, six numbers of two, one single byte, and the buffer. NOTHING MAY
|
||||||
|
; BE ADDED IN THE MIDDLE OF THE BLOCK ABOVE without changing this to match.
|
||||||
|
SrcStateBytes:
|
||||||
|
0x01 0x24
|
||||||
|
SrcDepthLimit:
|
||||||
|
0d6
|
||||||
|
SrcOne:
|
||||||
|
0x00 0x01
|
||||||
|
|
||||||
|
SrcChar:
|
||||||
|
0x00
|
||||||
|
SrcLeft:
|
||||||
|
0x00
|
||||||
|
SrcDepth:
|
||||||
|
0x00
|
||||||
|
SrcSlot:
|
||||||
|
0x00 0x00
|
||||||
|
SrcSlotLeft:
|
||||||
|
0x00
|
||||||
|
SrcCopyFrom:
|
||||||
|
0x00 0x00
|
||||||
|
SrcCopyTo:
|
||||||
|
0x00 0x00
|
||||||
|
SrcCopyLeft:
|
||||||
|
0x00 0x00
|
||||||
|
|
||||||
|
; The file the assembly started from, so that each pass can open it again.
|
||||||
|
SrcTopName:
|
||||||
|
#Reserve 0d23
|
||||||
|
|
||||||
|
IncWanted:
|
||||||
|
#Reserve 0d23
|
||||||
|
IncCount:
|
||||||
|
0x00
|
||||||
|
IncLeft:
|
||||||
|
0x00
|
||||||
|
IncSlot:
|
||||||
|
0x00 0x00
|
||||||
|
IncSlotLeft:
|
||||||
|
0x00
|
||||||
|
|
||||||
|
TooDeepText:
|
||||||
|
"included files are nested deeper than this assembler will follow
|
||||||
|
"
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
; The instruction set, as the assembler needs to see it.
|
||||||
|
;
|
||||||
|
; A SECOND COPY, and it is worth saying why rather than hoping nobody notices. The monitor
|
||||||
|
; has one of these in cosmos.asm, and the assembler cannot use it: the monitor's copy lives
|
||||||
|
; in the system's data at an address that moves every time CosmOS is rebuilt, and there is
|
||||||
|
; no linker to reach it by name. So the assembler carries its own 448 bytes. That is the
|
||||||
|
; cost of having no libraries, paid where it is cheapest to pay.
|
||||||
|
;
|
||||||
|
; Both copies are generated by Tests/instructiontable.py from the C assembler's own list,
|
||||||
|
; and Tests/docs.sh checks both against it. Neither can drift without the suite saying so.
|
||||||
|
;
|
||||||
|
; Seven bytes an entry: the opcode, the shape, and four characters of name with the zero
|
||||||
|
; the assembler puts after a string. Every mnemonic is four characters or fewer, so a name
|
||||||
|
; padded to four is an exact match rather than a prefix.
|
||||||
|
;
|
||||||
|
; It goes in the DATA Segment, because the assembler has to read it and an instruction can
|
||||||
|
; only read Data Memory. A table in Program Memory could not be reached by the program
|
||||||
|
; holding it, except through the memory controller.
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
; How many bytes an instruction of each shape runs to, the opcode included. The assembler
|
||||||
|
; does not use this to size a token - the operand that follows is a token of its own and
|
||||||
|
; carries its own length - but it is what says an instruction is well formed.
|
||||||
|
AsmShapeLength:
|
||||||
|
0d1 0d3 0d2 0d2 0d3 0d4 0d3
|
||||||
|
|
||||||
|
; How many Data Pointer selectors an instruction of each shape names. This is what the
|
||||||
|
; assembler needs: a selector is part of the mnemonic rather than a token after it, so it
|
||||||
|
; is the one thing about an instruction's length that is not settled by the opcode alone.
|
||||||
|
;
|
||||||
|
; 0 no operand 4 a selector and a byte
|
||||||
|
; 1 an address 5 a selector and an address, which is SETD
|
||||||
|
; 2 a byte 6 two selectors, which is LDD and STD
|
||||||
|
; 3 a selector
|
||||||
|
AsmShapeSelectors:
|
||||||
|
0d0 0d0 0d0 0d1 0d1 0d1 0d2
|
||||||
|
|
||||||
|
AsmInstructionCount:
|
||||||
|
0d64
|
||||||
|
|
||||||
|
AsmInstructions:
|
||||||
|
0x00 0d0 "ADD "
|
||||||
|
0x01 0d0 "SUB "
|
||||||
|
0x02 0d0 "AND "
|
||||||
|
0x03 0d0 "OR "
|
||||||
|
0x04 0d0 "XOR "
|
||||||
|
0x05 0d0 "NOTA"
|
||||||
|
0x06 0d0 "NOTB"
|
||||||
|
0x07 0d0 "SHL "
|
||||||
|
0x08 0d0 "SHR "
|
||||||
|
0x10 0d1 "BRI "
|
||||||
|
0x11 0d1 "BRQ "
|
||||||
|
0x12 0d1 "BRA "
|
||||||
|
0x13 0d1 "BRB "
|
||||||
|
0x14 0d1 "BRC "
|
||||||
|
0x15 0d3 "BRD "
|
||||||
|
0x1A 0d1 "BNQ "
|
||||||
|
0x1B 0d1 "BNA "
|
||||||
|
0x1C 0d1 "BNB "
|
||||||
|
0x1D 0d1 "BNC "
|
||||||
|
0x17 0d1 "CALL"
|
||||||
|
0x18 0d2 "SWI "
|
||||||
|
0x19 0d0 "RETI"
|
||||||
|
0x1F 0d0 "RET "
|
||||||
|
0x20 0d0 "RSTA"
|
||||||
|
0x21 0d0 "RSTB"
|
||||||
|
0x22 0d0 "INCA"
|
||||||
|
0x23 0d0 "INCB"
|
||||||
|
0x24 0d0 "DECA"
|
||||||
|
0x25 0d0 "DECB"
|
||||||
|
0x26 0d2 "INIA"
|
||||||
|
0x27 0d2 "INIB"
|
||||||
|
0x28 0d0 "CCF "
|
||||||
|
0x29 0d0 "MVQA"
|
||||||
|
0x2A 0d0 "MVQB"
|
||||||
|
0x2B 0d0 "SIF "
|
||||||
|
0x2C 0d0 "CIF "
|
||||||
|
0x30 0d0 "PSHQ"
|
||||||
|
0x31 0d0 "PSHA"
|
||||||
|
0x32 0d0 "PSHB"
|
||||||
|
0x33 0d3 "PSHD"
|
||||||
|
0x34 0d0 "POPA"
|
||||||
|
0x35 0d0 "POPB"
|
||||||
|
0x36 0d3 "POPD"
|
||||||
|
0x40 0d3 "INCD"
|
||||||
|
0x41 0d3 "DECD"
|
||||||
|
0x42 0d3 "LDA "
|
||||||
|
0x43 0d3 "LDB "
|
||||||
|
0x44 0d3 "STQ "
|
||||||
|
0x45 0d3 "STA "
|
||||||
|
0x46 0d3 "STB "
|
||||||
|
0x47 0d5 "SETD"
|
||||||
|
0x48 0d4 "DPUP"
|
||||||
|
0x49 0d4 "DPDN"
|
||||||
|
0x4A 0d6 "LDD "
|
||||||
|
0x4B 0d6 "STD "
|
||||||
|
0x4C 0d3 "MVSD"
|
||||||
|
0x4D 0d3 "MVDS"
|
||||||
|
0xD0 0d2 "OUTQ"
|
||||||
|
0xD1 0d2 "OUTA"
|
||||||
|
0xD2 0d2 "OUTB"
|
||||||
|
0xE0 0d2 "INA "
|
||||||
|
0xE1 0d2 "INB "
|
||||||
|
0xF0 0d0 "NOP "
|
||||||
|
0xFF 0d0 "HALT"
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
; Tokens out of characters.
|
||||||
|
;
|
||||||
|
; A token is a run of characters with whitespace or a comment on either side, or anything
|
||||||
|
; between a pair of quotes. That is the whole of the lexical grammar: SplitBit assembly has
|
||||||
|
; no operators, no punctuation and no line continuation, so there is nothing here that has
|
||||||
|
; to look ahead more than one character.
|
||||||
|
;
|
||||||
|
; ONE CHARACTER OF LOOKAHEAD, and it is held here rather than in the reader. A word ends
|
||||||
|
; when something that is not part of it turns up, and that something has already been read
|
||||||
|
; by the time anyone knows - so it is put in TokPending and taken again next time. Keeping
|
||||||
|
; it at this level rather than pushing it back into the reader means the line number needs
|
||||||
|
; no arithmetic: srcNext counted the newline when it handed it out, and it stays counted.
|
||||||
|
;
|
||||||
|
; Zero means "nothing held", which is safe because a source file is text and a text file
|
||||||
|
; has no zero bytes in it. A file that did would be rejected as unassemblable long before
|
||||||
|
; the difference showed.
|
||||||
|
;
|
||||||
|
; Written by Anachronaut
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
; The next token, into TokText with a zero after it. Q is zero if there was one.
|
||||||
|
;
|
||||||
|
; TokLength is how long it is, TokString says whether it arrived in quotes, and TokLine is
|
||||||
|
; the line it STARTED on - captured before the token is read, because a token ending in a
|
||||||
|
; newline has already moved the reader on to the next line by the time it is finished.
|
||||||
|
tokNext:
|
||||||
|
SETD.0 TokHeld
|
||||||
|
LDA.0
|
||||||
|
BRA tokFresh
|
||||||
|
RSTA
|
||||||
|
STA.0
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD ; The one that was handed back, exactly as it was.
|
||||||
|
RET
|
||||||
|
|
||||||
|
tokFresh:
|
||||||
|
RSTA
|
||||||
|
SETD.0 TokString
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
tokSkip:
|
||||||
|
CALL tokGet
|
||||||
|
BNQ tokEnded
|
||||||
|
CALL tokIsSpace
|
||||||
|
BRQ tokSkip
|
||||||
|
|
||||||
|
SETD.0 TokChar
|
||||||
|
LDA.0
|
||||||
|
INIB 0x3B ; A semicolon starts a comment.
|
||||||
|
XOR
|
||||||
|
BNQ tokBegin
|
||||||
|
|
||||||
|
tokComment:
|
||||||
|
CALL tokGet
|
||||||
|
BNQ tokEnded
|
||||||
|
SETD.0 TokChar
|
||||||
|
LDA.0
|
||||||
|
INIB 0x0A
|
||||||
|
XOR
|
||||||
|
BNQ tokComment ; Everything up to the newline belongs to the comment.
|
||||||
|
BRI tokSkip
|
||||||
|
|
||||||
|
tokBegin:
|
||||||
|
; Where it starts, for anything that has to complain about it later.
|
||||||
|
SETD.0 TokLine
|
||||||
|
SETD.2 SrcLine
|
||||||
|
CALL numSet
|
||||||
|
|
||||||
|
RSTA
|
||||||
|
SETD.0 TokLength
|
||||||
|
STA.0
|
||||||
|
SETD.0 TokText
|
||||||
|
SETD.1 TokPointer
|
||||||
|
STD.0.1
|
||||||
|
|
||||||
|
SETD.0 TokChar
|
||||||
|
LDA.0
|
||||||
|
INIB 0x22 ; A quote starts a string.
|
||||||
|
XOR
|
||||||
|
BNQ tokWord
|
||||||
|
|
||||||
|
INIA 0d1
|
||||||
|
SETD.0 TokString
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
tokStringLoop:
|
||||||
|
CALL tokGet
|
||||||
|
BNQ tokDone ; The file ended inside a string. Take what there is; the
|
||||||
|
; classifier will have something to complain about.
|
||||||
|
SETD.0 TokChar
|
||||||
|
LDA.0
|
||||||
|
INIB 0x22
|
||||||
|
XOR
|
||||||
|
BRQ tokDone
|
||||||
|
CALL tokAppend
|
||||||
|
BRI tokStringLoop
|
||||||
|
|
||||||
|
tokWord:
|
||||||
|
CALL tokAppend
|
||||||
|
tokWordLoop:
|
||||||
|
CALL tokGet
|
||||||
|
BNQ tokDone
|
||||||
|
CALL tokIsSpace
|
||||||
|
BRQ tokHoldDone
|
||||||
|
SETD.0 TokChar
|
||||||
|
LDA.0
|
||||||
|
INIB 0x3B
|
||||||
|
XOR
|
||||||
|
BRQ tokHoldDone ; A comment butting straight up against a word ends it.
|
||||||
|
CALL tokAppend
|
||||||
|
BRI tokWordLoop
|
||||||
|
|
||||||
|
tokHoldDone:
|
||||||
|
; Whatever ended the word was not part of it, so it goes back to be looked at again.
|
||||||
|
SETD.0 TokChar
|
||||||
|
LDA.0
|
||||||
|
SETD.0 TokPending
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
tokDone:
|
||||||
|
SETD.1 TokPointer
|
||||||
|
LDD.0.1
|
||||||
|
RSTA
|
||||||
|
STA.0 ; The zero that makes it a string.
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD ; Q is zero: there was a token.
|
||||||
|
RET
|
||||||
|
|
||||||
|
tokEnded:
|
||||||
|
RSTA
|
||||||
|
SETD.0 TokLength
|
||||||
|
STA.0
|
||||||
|
SETD.0 TokText
|
||||||
|
STA.0
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD ; Q is not zero: the source is finished.
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Puts the held character back into the file it came from, leaving nothing in hand.
|
||||||
|
;
|
||||||
|
; This is what #Include calls before the reader puts the current file aside. The character
|
||||||
|
; the tokenizer is holding was read from that file and has not been used, so it goes back
|
||||||
|
; into it; there is then nothing to carry across the include and nothing to hand back at a
|
||||||
|
; moment that might land in the middle of a word.
|
||||||
|
tokUnread:
|
||||||
|
SETD.0 TokPending
|
||||||
|
LDA.0
|
||||||
|
BRA tokUnreadDone
|
||||||
|
CALL srcStepBack
|
||||||
|
RSTA
|
||||||
|
SETD.0 TokPending
|
||||||
|
STA.0
|
||||||
|
tokUnreadDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Hands the token just read back, so that the next tokNext produces it again.
|
||||||
|
;
|
||||||
|
; ONE TOKEN, and only where nothing has changed it since. The Vector Segment needs it: a
|
||||||
|
; name there may be followed by a number, by a handler, or by the next line's name, and
|
||||||
|
; which it is cannot be known without looking. Do NOT use it after anything that alters
|
||||||
|
; TokText - a label definition with its colon written over would come back as a use of the
|
||||||
|
; name rather than as a definition of it.
|
||||||
|
tokBack:
|
||||||
|
INIA 0d1
|
||||||
|
SETD.0 TokHeld
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; The next character, into TokChar. Q is zero if there was one. Takes the held one first.
|
||||||
|
tokGet:
|
||||||
|
SETD.0 TokPending
|
||||||
|
LDA.0
|
||||||
|
BRA tokGetFresh
|
||||||
|
SETD.0 TokChar
|
||||||
|
STA.0
|
||||||
|
RSTA
|
||||||
|
SETD.0 TokPending
|
||||||
|
STA.0
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
tokGetFresh:
|
||||||
|
CALL srcNext
|
||||||
|
BNQ tokGetNone
|
||||||
|
SETD.0 SrcChar
|
||||||
|
LDA.0
|
||||||
|
SETD.0 TokChar
|
||||||
|
STA.0
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
tokGetNone:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Adds TokChar to the token being built, unless it is already as long as one may be.
|
||||||
|
;
|
||||||
|
; A token that runs over is truncated rather than refused, and the classifier refuses it
|
||||||
|
; afterwards: nothing 255 characters long is a valid mnemonic, literal or label, so the
|
||||||
|
; error that comes out names what was wrong with it rather than only how long it was.
|
||||||
|
tokAppend:
|
||||||
|
SETD.0 TokLength
|
||||||
|
LDA.0
|
||||||
|
INIB 0xFF
|
||||||
|
XOR
|
||||||
|
BRQ tokAppendFull
|
||||||
|
SETD.1 TokPointer
|
||||||
|
LDD.0.1
|
||||||
|
SETD.2 TokChar
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STD.0.1
|
||||||
|
SETD.0 TokLength
|
||||||
|
LDA.0
|
||||||
|
INCA
|
||||||
|
STA.0
|
||||||
|
tokAppendFull:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Q is zero if TokChar is whitespace: a space, or anything in the run from tab to carriage
|
||||||
|
; return, which is what the C library calls a space and what the other assembler uses.
|
||||||
|
tokIsSpace:
|
||||||
|
SETD.0 TokChar
|
||||||
|
LDA.0
|
||||||
|
INIB 0x20
|
||||||
|
XOR
|
||||||
|
BRQ tokSpaceYes
|
||||||
|
|
||||||
|
SETD.0 TokChar
|
||||||
|
LDA.0
|
||||||
|
INIB 0x09
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRC tokSpaceNo ; Below a tab.
|
||||||
|
SETD.0 TokChar
|
||||||
|
LDA.0
|
||||||
|
INIB 0x0E
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNC tokSpaceNo ; Past a carriage return.
|
||||||
|
|
||||||
|
tokSpaceYes:
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
tokSpaceNo:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
TokLine:
|
||||||
|
0x00 0x00
|
||||||
|
TokPointer:
|
||||||
|
0x00 0x00
|
||||||
|
TokLength:
|
||||||
|
0x00
|
||||||
|
TokString:
|
||||||
|
0x00
|
||||||
|
TokChar:
|
||||||
|
0x00
|
||||||
|
TokPending:
|
||||||
|
0x00
|
||||||
|
TokHeld:
|
||||||
|
0x00
|
||||||
|
|
||||||
|
; As long as a token may be, and one more for the zero. The other assembler stops at the
|
||||||
|
; same 255, and the limit is worth matching rather than choosing again.
|
||||||
|
TokText:
|
||||||
|
#Reserve 0d256
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
; 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
|
||||||
|
INCD.0
|
||||||
|
LDB.0
|
||||||
|
SWI osPrintNumber ; Sixteen bits: a string of 255 characters is 256 bytes long.
|
||||||
|
|
||||||
|
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 scratch.asm
|
||||||
|
#Include numbers.asm
|
||||||
|
#Include source.asm
|
||||||
|
#Include token.asm
|
||||||
|
#Include classify.asm
|
||||||
|
#Include table.asm
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
; 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 eight bytes an entry:
|
||||||
|
;
|
||||||
|
; 0 23 the name, up to twenty two characters and a zero
|
||||||
|
; 23 1 the vector number
|
||||||
|
; 24 1 which table: 0 software, 1 hardware
|
||||||
|
; 25 2 the handler's address
|
||||||
|
; 27 1 whether a handler has been supplied
|
||||||
|
;
|
||||||
|
; A DECLARATION AND AN IMPLEMENTATION ARE THE SAME ENTRY. services.asm says a service is
|
||||||
|
; called osPrintString and has number 16; cosmos.asm says osPrintString is handled by
|
||||||
|
; handlePrintString. Both sides include the first file, so the name is met twice, and the
|
||||||
|
; second time fills in the handler rather than making a second entry. That is what lets one
|
||||||
|
; shared file serve a program that calls a service and the system that implements it.
|
||||||
|
;
|
||||||
|
; THE ORDER OF THIS TABLE IS THE ORDER OF THE OUTPUT FILE, and the order is declaration
|
||||||
|
; order rather than implementation order, because that is where the entry was made. It has
|
||||||
|
; to match what the other assembler does byte for byte.
|
||||||
|
;
|
||||||
|
; 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, in the table VecPutBase names. 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
|
||||||
|
INCD.0
|
||||||
|
SETD.2 VecPutBase
|
||||||
|
LDA.2
|
||||||
|
STA.0 ; Which table it lives in.
|
||||||
|
INCD.0
|
||||||
|
RSTA
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
STA.0 ; No handler yet, and no address to go with one.
|
||||||
|
|
||||||
|
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:
|
||||||
|
CALL vecReadFields
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
vecFindMissing:
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Everything the entry at VecSlot says, into VecNumber, VecBase, VecHandler and
|
||||||
|
; VecHasHandler. VecSlot is left pointing past the name, at the number.
|
||||||
|
vecReadFields:
|
||||||
|
INIA 0d23
|
||||||
|
SETD.0 VecSlot
|
||||||
|
CALL numAddByte
|
||||||
|
SETD.1 VecSlot
|
||||||
|
LDD.0.1
|
||||||
|
LDA.0
|
||||||
|
SETD.1 VecNumber
|
||||||
|
STA.1
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
SETD.1 VecBase
|
||||||
|
STA.1
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
SETD.1 VecHandler
|
||||||
|
STA.1
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
SETD.1 VecHandler
|
||||||
|
INCD.1
|
||||||
|
STA.1
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
SETD.1 VecHasHandler
|
||||||
|
STA.1
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Gives the entry at VecSlot the handler in VecPutHandler. VecSlot must already have been
|
||||||
|
; walked past the name by vecReadFields, which is how it is always reached.
|
||||||
|
;
|
||||||
|
; A VARIABLE OF ITS OWN, not VecHandler, and that is not tidiness. Finding the entry to
|
||||||
|
; write to means calling vecFind, which reads the entry's fields out - including the
|
||||||
|
; handler it does not have yet. An address resolved into VecHandler before the find was
|
||||||
|
; overwritten with zero by the find itself, and the file came out with a vector pointing
|
||||||
|
; at address zero: a slot that looked installed and went nowhere.
|
||||||
|
vecWriteHandler:
|
||||||
|
SETD.1 VecSlot
|
||||||
|
LDD.0.1
|
||||||
|
INCD.0
|
||||||
|
INCD.0
|
||||||
|
SETD.2 VecPutHandler
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
INCD.2
|
||||||
|
LDA.2
|
||||||
|
STA.0
|
||||||
|
INCD.0
|
||||||
|
INIA 0d1
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Where entry number VecWhich sits, into VecSlot. Twenty eight bytes an entry.
|
||||||
|
vecSlotAt:
|
||||||
|
SETD.0 VecSlot
|
||||||
|
SETD.2 ScratchVecNames
|
||||||
|
CALL numSet
|
||||||
|
SETD.0 VecSlotLeft
|
||||||
|
SETD.2 VecWhich
|
||||||
|
CALL numSet
|
||||||
|
vecSlotLoop:
|
||||||
|
SETD.0 VecSlotLeft
|
||||||
|
LDA.0
|
||||||
|
INCD.0
|
||||||
|
LDB.0
|
||||||
|
OR
|
||||||
|
BRQ vecSlotDone
|
||||||
|
INIA 0d28
|
||||||
|
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
|
||||||
|
VecPutBase:
|
||||||
|
0x00
|
||||||
|
VecHandler:
|
||||||
|
0x00 0x00
|
||||||
|
VecPutHandler:
|
||||||
|
0x00 0x00
|
||||||
|
VecHasHandler:
|
||||||
|
0x00
|
||||||
|
VecBase:
|
||||||
|
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
|
||||||
|
|
||||||
@@ -99,6 +99,7 @@ prompt rather than at the shell.
|
|||||||
| -- | -- |
|
| -- | -- |
|
||||||
| `x [address]` | Display 64 bytes as hexadecimal and as characters. |
|
| `x [address]` | Display 64 bytes as hexadecimal and as characters. |
|
||||||
| `d [address]` | Disassemble eight instructions. |
|
| `d [address]` | Disassemble eight instructions. |
|
||||||
|
| `a <address>` | Assemble instructions into memory until a line containing only a dot. |
|
||||||
| `s <address> <byte>...` | Write bytes into the bank being examined, including Program Memory. |
|
| `s <address> <byte>...` | Write bytes into the bank being examined, including Program Memory. |
|
||||||
| `b <program\|data\|bank>` | Select a memory space or a registered bank number. |
|
| `b <program\|data\|bank>` | Select a memory space or a registered bank number. |
|
||||||
| `g <address>` | Begin execution at an address. |
|
| `g <address>` | Begin execution at an address. |
|
||||||
@@ -179,6 +180,23 @@ the services CosmOS provides. The currently installed services are:
|
|||||||
| `osFileDelete` | DP0 names a file to remove; Q reports success. |
|
| `osFileDelete` | DP0 names a file to remove; Q reports success. |
|
||||||
| `osFileRename` | DP0 names an existing file and DP1 its new name; Q reports success. |
|
| `osFileRename` | DP0 names an existing file and DP1 its new name; Q reports success. |
|
||||||
| `osPrintNumber` | A with B give a number to print in decimal without leading zeroes. |
|
| `osPrintNumber` | A with B give a number to print in decimal without leading zeroes. |
|
||||||
|
| `osBreak` | Stops the application, shows every register as it had them, waits for a key, and carries on. |
|
||||||
|
| `osFileInfo` | DP0 names a file; Q reports whether it is there and DP3 returns how many blocks it occupies. |
|
||||||
|
| `osFileBlock` | DP0 names a file, DP1 a destination, and A with B give which block; Q reports success and DP3 returns how many of the block's bytes belong to the file. |
|
||||||
|
|
||||||
|
The largest application CosmOS has is the assembler in `Programs/CosmOS/Assembler/`, which
|
||||||
|
is why the streaming services below exist: it reads source a block at a time, twice, and
|
||||||
|
keeps only its label table in between. It travels with CosmOS rather than with the emulator,
|
||||||
|
for the same reason the C assembler travels with the emulator - it is part of the system it
|
||||||
|
is written for.
|
||||||
|
|
||||||
|
`osFileInfo` and `osFileBlock` are how an application reads a file too big to hold. A whole
|
||||||
|
file arrives through `osFileRead`, which cannot help with anything above 64K, and CosmOS's
|
||||||
|
own source is above it. Neither call keeps anything open: each one names the file and says
|
||||||
|
which block it wants, so an application that stops halfway leaves nothing behind. Both
|
||||||
|
report why they failed rather than only that they did - 1 for no disk, 2 for no such file,
|
||||||
|
3 for a block past the end, and 4 for a disk that would not read - because running off the
|
||||||
|
end is how a reader learns it has finished.
|
||||||
|
|
||||||
The filesystem services exist so that an application need not contain a second copy of the
|
The filesystem services exist so that an application need not contain a second copy of the
|
||||||
filesystem in order to keep a file. There is deliberately no service to mount a disk: the
|
filesystem in order to keep a file. There is deliberately no service to mount a disk: the
|
||||||
@@ -250,7 +268,7 @@ than merely checking its filesystem code against itself.
|
|||||||
|
|
||||||
CosmOS is early software for an experimental computer. It runs one application at a
|
CosmOS is early software for an experimental computer. It runs one application at a
|
||||||
time, has no privilege levels or process isolation, does not relocate applications, and
|
time, has no privilege levels or process isolation, does not relocate applications, and
|
||||||
does not yet provide a native assembler or linker. Its present purpose is to make
|
provides a line assembler but not yet a native assembler for source files, nor a linker. Its present purpose is to make
|
||||||
SplitBit usable from inside the machine: inspect it, manage persistent files, load
|
SplitBit usable from inside the machine: inspect it, manage persistent files, load
|
||||||
programs, provide common services, and return reliably to a command prompt.
|
programs, provide common services, and return reliably to a command prompt.
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,11 @@ promptSay:
|
|||||||
CALL textSame
|
CALL textSame
|
||||||
BRQ doGo
|
BRQ doGo
|
||||||
|
|
||||||
|
SETD.0 CommandLine
|
||||||
|
SETD.1 AsmName2
|
||||||
|
CALL textSame
|
||||||
|
BRQ doAssemble
|
||||||
|
|
||||||
promptUnknown:
|
promptUnknown:
|
||||||
; Nothing matched. Saying which word was not understood is worth the four instructions:
|
; Nothing matched. Saying which word was not understood is worth the four instructions:
|
||||||
; it tells somebody who mistyped what they actually typed.
|
; it tells somebody who mistyped what they actually typed.
|
||||||
@@ -576,6 +581,8 @@ doDelete:
|
|||||||
LDA.0
|
LDA.0
|
||||||
BRA deleteWhat
|
BRA deleteWhat
|
||||||
|
|
||||||
|
; What a name means on the disk is about to change, so the remembered file goes.
|
||||||
|
CALL fileForget
|
||||||
CALL sbfsDelete
|
CALL sbfsDelete
|
||||||
BNQ deleteFailed
|
BNQ deleteFailed
|
||||||
SETD.0 Deleted
|
SETD.0 Deleted
|
||||||
@@ -615,6 +622,8 @@ doRename:
|
|||||||
|
|
||||||
SETD.2 RenameFrom
|
SETD.2 RenameFrom
|
||||||
LDD.0.2
|
LDD.0.2
|
||||||
|
; What a name means on the disk is about to change, so the remembered file goes.
|
||||||
|
CALL fileForget
|
||||||
CALL sbfsRename
|
CALL sbfsRename
|
||||||
BNQ renameFailed
|
BNQ renameFailed
|
||||||
SETD.0 Renamed
|
SETD.0 Renamed
|
||||||
@@ -939,6 +948,8 @@ handleFileSave:
|
|||||||
SETD.2 SbfsFileTail
|
SETD.2 SbfsFileTail
|
||||||
STB.2
|
STB.2
|
||||||
|
|
||||||
|
; What a name means on the disk is about to change, so the remembered file goes.
|
||||||
|
CALL fileForget
|
||||||
CALL sbfsSaveFile
|
CALL sbfsSaveFile
|
||||||
MVQA
|
MVQA
|
||||||
MVSD.2
|
MVSD.2
|
||||||
@@ -959,6 +970,8 @@ handleFileDelete:
|
|||||||
SETD.2 DiskReady
|
SETD.2 DiskReady
|
||||||
LDA.2
|
LDA.2
|
||||||
BRA serviceNoDisk
|
BRA serviceNoDisk
|
||||||
|
; What a name means on the disk is about to change, so the remembered file goes.
|
||||||
|
CALL fileForget
|
||||||
CALL sbfsDelete
|
CALL sbfsDelete
|
||||||
MVQA
|
MVQA
|
||||||
MVSD.2
|
MVSD.2
|
||||||
@@ -971,6 +984,8 @@ handleFileRename:
|
|||||||
SETD.2 DiskReady
|
SETD.2 DiskReady
|
||||||
LDA.2
|
LDA.2
|
||||||
BRA serviceNoDisk
|
BRA serviceNoDisk
|
||||||
|
; What a name means on the disk is about to change, so the remembered file goes.
|
||||||
|
CALL fileForget
|
||||||
CALL sbfsRename
|
CALL sbfsRename
|
||||||
MVQA
|
MVQA
|
||||||
MVSD.2
|
MVSD.2
|
||||||
@@ -985,6 +1000,391 @@ serviceNoDisk:
|
|||||||
STA.2
|
STA.2
|
||||||
RETI
|
RETI
|
||||||
|
|
||||||
|
; ---- Reading a file that will not fit ----
|
||||||
|
;
|
||||||
|
; A file bigger than Data Memory cannot be handed over whole, and CosmOS's own source is
|
||||||
|
; now that file, so these two are how anything reads one: ask how many blocks, then ask for
|
||||||
|
; each block in turn. There is no open and no close. A program that stops halfway through
|
||||||
|
; leaves nothing behind, because there was never anything to leave.
|
||||||
|
|
||||||
|
; Finds a file, or remembers that it already did. DP0 names it. Q is zero if it is there,
|
||||||
|
; and then SbfsFileStart, SbfsFileBlocks and SbfsFileTail describe it exactly the way
|
||||||
|
; sbfsFind leaves them - whether the search happened or not, which is the whole point.
|
||||||
|
fileLookup:
|
||||||
|
SETD.2 FileCacheValid
|
||||||
|
LDA.2
|
||||||
|
BRA fileLookupSearch
|
||||||
|
|
||||||
|
SETD.1 FileCacheName
|
||||||
|
CALL textSame
|
||||||
|
BNQ fileLookupSearch
|
||||||
|
|
||||||
|
; The same file as last time. The description still has to be put back, because anything
|
||||||
|
; that went to the disk in between - a directory listing, a program being loaded - left
|
||||||
|
; its own answer in those three.
|
||||||
|
SETD.0 SbfsFileStart
|
||||||
|
SETD.2 FileCacheStart
|
||||||
|
CALL sbfsSetWord
|
||||||
|
SETD.0 SbfsFileBlocks
|
||||||
|
SETD.2 FileCacheBlocks
|
||||||
|
CALL sbfsSetWord
|
||||||
|
SETD.0 FileCacheTail
|
||||||
|
LDA.0
|
||||||
|
SETD.0 SbfsFileTail
|
||||||
|
STA.0
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD ; Q is zero: found.
|
||||||
|
RET
|
||||||
|
|
||||||
|
fileLookupSearch:
|
||||||
|
CALL sbfsFind
|
||||||
|
BNQ fileLookupMissing
|
||||||
|
|
||||||
|
; Remember it. DP0 still names the file: a CALL puts the pointers back, which is the one
|
||||||
|
; place that convention is a convenience rather than an obstacle.
|
||||||
|
SETD.1 FileCacheName
|
||||||
|
CALL sbfsKeepName
|
||||||
|
SETD.0 FileCacheStart
|
||||||
|
SETD.2 SbfsFileStart
|
||||||
|
CALL sbfsSetWord
|
||||||
|
SETD.0 FileCacheBlocks
|
||||||
|
SETD.2 SbfsFileBlocks
|
||||||
|
CALL sbfsSetWord
|
||||||
|
SETD.0 SbfsFileTail
|
||||||
|
LDA.0
|
||||||
|
SETD.0 FileCacheTail
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
; Marked good last, so that a cache half filled is never a cache believed.
|
||||||
|
INIA 0d1
|
||||||
|
SETD.0 FileCacheValid
|
||||||
|
STA.0
|
||||||
|
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD ; Q is zero: found.
|
||||||
|
RET
|
||||||
|
|
||||||
|
fileLookupMissing:
|
||||||
|
RET ; Q is not zero, and sbfsFind is what made it so.
|
||||||
|
|
||||||
|
; Throws the remembered file away. Everything that can change what a name means on the disk
|
||||||
|
; calls this before it does: a file saved over may have moved, a deleted one is gone, and a
|
||||||
|
; renamed one answers to something else. A remembered start block that survived any of
|
||||||
|
; those is a pointer at whatever took its place.
|
||||||
|
;
|
||||||
|
; Q is deliberately untouched, so this can be dropped into a handler without disturbing the
|
||||||
|
; answer that handler is in the middle of working out.
|
||||||
|
fileForget:
|
||||||
|
RSTA
|
||||||
|
SETD.0 FileCacheValid
|
||||||
|
STA.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; DP0 names it. Q is zero if it is there, and DP3 comes back holding how many blocks it
|
||||||
|
; occupies, counting a part one on the end.
|
||||||
|
;
|
||||||
|
; Blocks, not bytes, and that is forced rather than chosen: a file on a sixteen megabyte
|
||||||
|
; disk is up to twenty four bits long, which does not fit in a pointer. Blocks do, and the
|
||||||
|
; bytes in the last one come back from osFileBlock when the reader gets there.
|
||||||
|
handleFileInfo:
|
||||||
|
SETD.2 DiskReady
|
||||||
|
LDA.2
|
||||||
|
BRA fileInfoNoDisk
|
||||||
|
|
||||||
|
CALL fileLookup
|
||||||
|
BNQ fileInfoMissing
|
||||||
|
|
||||||
|
CALL sbfsFileExtent
|
||||||
|
SETD.2 SbfsWantBlocks
|
||||||
|
LDA.2
|
||||||
|
INCD.2
|
||||||
|
LDB.2
|
||||||
|
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d05 ; The saved DP3, high byte first.
|
||||||
|
STA.2
|
||||||
|
INCD.2
|
||||||
|
STB.2
|
||||||
|
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d02
|
||||||
|
RSTA
|
||||||
|
STA.2 ; And the saved Q: it is there.
|
||||||
|
RETI
|
||||||
|
|
||||||
|
fileInfoNoDisk:
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d02
|
||||||
|
INIA 0d1
|
||||||
|
STA.2
|
||||||
|
RETI
|
||||||
|
|
||||||
|
fileInfoMissing:
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d02
|
||||||
|
INIA 0d2
|
||||||
|
STA.2
|
||||||
|
RETI
|
||||||
|
|
||||||
|
; DP0 names it, DP1 says where to put it, and A and B together are which block, counting
|
||||||
|
; from zero. Q is zero if it read, and DP3 comes back holding how many of the block's bytes
|
||||||
|
; belong to the file.
|
||||||
|
handleFileBlock:
|
||||||
|
; Which block, before anything else, because finding out whether there is a disk needs A
|
||||||
|
; and there is nowhere else the number is written down.
|
||||||
|
SETD.2 SbfsIndex
|
||||||
|
STA.2
|
||||||
|
INCD.2
|
||||||
|
STB.2
|
||||||
|
|
||||||
|
SETD.2 DiskReady
|
||||||
|
LDA.2
|
||||||
|
BRA fileBlockNoDisk
|
||||||
|
|
||||||
|
CALL fileLookup
|
||||||
|
BNQ fileBlockMissing
|
||||||
|
|
||||||
|
; Running off the end is how a reader finds out it has finished, so it gets an answer of
|
||||||
|
; its own rather than being told the disk failed.
|
||||||
|
CALL sbfsFileExtent
|
||||||
|
SETD.0 SbfsIndex
|
||||||
|
SETD.2 SbfsWantBlocks
|
||||||
|
CALL sbfsCompareWord
|
||||||
|
BNC fileBlockPastEnd ; The Carry is set only when the index is the smaller.
|
||||||
|
|
||||||
|
CALL sbfsReadOne ; DP1 still says where. A CALL puts the pointers back.
|
||||||
|
BNQ fileBlockFailed
|
||||||
|
|
||||||
|
; How much of it is the file's. Every block but a short last one is a whole 256, and 256
|
||||||
|
; is why this answers in a pointer instead of a register.
|
||||||
|
SETD.2 SbfsFileTail
|
||||||
|
LDA.2
|
||||||
|
BRA fileBlockWhole ; Nothing partial on the end, so they are all whole.
|
||||||
|
SETD.0 SbfsIndex
|
||||||
|
SETD.2 SbfsFileBlocks
|
||||||
|
CALL sbfsCompareWord
|
||||||
|
BNQ fileBlockWhole ; Not the last one.
|
||||||
|
|
||||||
|
SETD.2 SbfsFileTail
|
||||||
|
LDB.2
|
||||||
|
RSTA
|
||||||
|
BRI fileBlockAnswer
|
||||||
|
|
||||||
|
fileBlockWhole:
|
||||||
|
INIA 0x01
|
||||||
|
RSTB
|
||||||
|
|
||||||
|
fileBlockAnswer:
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d05
|
||||||
|
STA.2
|
||||||
|
INCD.2
|
||||||
|
STB.2
|
||||||
|
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d02
|
||||||
|
RSTA
|
||||||
|
STA.2
|
||||||
|
RETI
|
||||||
|
|
||||||
|
fileBlockNoDisk:
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d02
|
||||||
|
INIA 0d1
|
||||||
|
STA.2
|
||||||
|
RETI
|
||||||
|
|
||||||
|
fileBlockMissing:
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d02
|
||||||
|
INIA 0d2
|
||||||
|
STA.2
|
||||||
|
RETI
|
||||||
|
|
||||||
|
fileBlockPastEnd:
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d02
|
||||||
|
INIA 0d3
|
||||||
|
STA.2
|
||||||
|
RETI
|
||||||
|
|
||||||
|
fileBlockFailed:
|
||||||
|
MVSD.2
|
||||||
|
DPUP.2 0d02
|
||||||
|
INIA 0d4
|
||||||
|
STA.2
|
||||||
|
RETI
|
||||||
|
|
||||||
|
; A breakpoint. Shows every register as the interrupted program had them, waits for a key,
|
||||||
|
; and returns as though nothing happened.
|
||||||
|
;
|
||||||
|
; EVERY VALUE COMES OUT OF THE FRAME, not out of the registers, because by the time this
|
||||||
|
; runs the registers belong to the handler. The frame is what the program had, and RETI is
|
||||||
|
; going to give it all back, so what is shown is what will be resumed with.
|
||||||
|
;
|
||||||
|
; DP3 holds the frame throughout. It survives a CALL, and console.asm promises not to
|
||||||
|
; disturb it, which is what lets the printing routines be used between one field and the
|
||||||
|
; next. The Stack Pointer comes back to the same place after a balanced call, so the frame
|
||||||
|
; stays where it was found.
|
||||||
|
;
|
||||||
|
; +1 Status +2 Q +3 A +4 B +5 DP3 +7 DP2 +9 DP1 +11 DP0 +13 where it resumes
|
||||||
|
handleBreak:
|
||||||
|
MVSD.3
|
||||||
|
|
||||||
|
; Where it broke, which is two before where it resumes: the SWI and the vector it names.
|
||||||
|
SETD.0 BreakText
|
||||||
|
CALL printString
|
||||||
|
PSHD.3
|
||||||
|
POPD.0
|
||||||
|
DPUP.0 0d13
|
||||||
|
LDA.0
|
||||||
|
PSHA ; The high half, while the low one is worked on.
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
INIB 0d2
|
||||||
|
CCF
|
||||||
|
SUB ; Two back from where it resumes: the SWI and the vector it names.
|
||||||
|
MVQA
|
||||||
|
POPB
|
||||||
|
BRC breakBorrowed ; It borrowed, so the high half comes down by one.
|
||||||
|
BRI breakAddress
|
||||||
|
breakBorrowed:
|
||||||
|
DECB
|
||||||
|
breakAddress:
|
||||||
|
PSHA ; low
|
||||||
|
PSHB ; high
|
||||||
|
POPA
|
||||||
|
CALL printByteHex
|
||||||
|
POPA
|
||||||
|
CALL printByteHex
|
||||||
|
CALL newLine
|
||||||
|
|
||||||
|
SETD.0 ARegText
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
DPUP.1 0d3
|
||||||
|
CALL breakByte
|
||||||
|
SETD.0 BRegText
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
DPUP.1 0d4
|
||||||
|
CALL breakByte
|
||||||
|
SETD.0 QRegText
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
DPUP.1 0d2
|
||||||
|
CALL breakByte
|
||||||
|
SETD.0 SRegText
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
DPUP.1 0d1
|
||||||
|
CALL breakByte
|
||||||
|
|
||||||
|
; And which bits of it those are, since a debug dump that makes you look the number up is
|
||||||
|
; only half of one. Halt is not among them: the machine is plainly not halted.
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
DPUP.1 0d1
|
||||||
|
LDA.1
|
||||||
|
PSHA
|
||||||
|
INIB 0x01
|
||||||
|
AND
|
||||||
|
BRQ breakNoCarry
|
||||||
|
SETD.0 CarryText
|
||||||
|
CALL printString
|
||||||
|
breakNoCarry:
|
||||||
|
POPA
|
||||||
|
PSHA
|
||||||
|
INIB 0x02
|
||||||
|
AND
|
||||||
|
BRQ breakNoFault
|
||||||
|
SETD.0 FaultText
|
||||||
|
CALL printString
|
||||||
|
breakNoFault:
|
||||||
|
POPA
|
||||||
|
INIB 0x04
|
||||||
|
AND
|
||||||
|
BRQ breakNoInts
|
||||||
|
SETD.0 IntsText
|
||||||
|
CALL printString
|
||||||
|
breakNoInts:
|
||||||
|
CALL newLine
|
||||||
|
|
||||||
|
SETD.0 DP0Text
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
DPUP.1 0d11
|
||||||
|
CALL breakWord
|
||||||
|
SETD.0 DP1Text
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
DPUP.1 0d9
|
||||||
|
CALL breakWord
|
||||||
|
SETD.0 DP2Text
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
DPUP.1 0d7
|
||||||
|
CALL breakWord
|
||||||
|
SETD.0 DP3Text
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
DPUP.1 0d5
|
||||||
|
CALL breakWord
|
||||||
|
|
||||||
|
; The Stack Pointer is not in the frame, because the frame is where the Stack Pointer is.
|
||||||
|
; What the program had is fourteen bytes above this one, that being what entering an
|
||||||
|
; interrupt puts down.
|
||||||
|
SETD.0 SPText
|
||||||
|
CALL printString
|
||||||
|
PSHD.3
|
||||||
|
POPD.0
|
||||||
|
DPUP.0 0d14
|
||||||
|
PSHD.0
|
||||||
|
POPB
|
||||||
|
POPA
|
||||||
|
CALL printByteHex
|
||||||
|
PSHB
|
||||||
|
POPA
|
||||||
|
CALL printByteHex
|
||||||
|
CALL newLine
|
||||||
|
|
||||||
|
; Anything typed carries on. Reading the data port waits however the console is set, which
|
||||||
|
; is one key if the program asked for key mode and a whole line if it did not - and either
|
||||||
|
; way it is the program's own console being borrowed for a moment.
|
||||||
|
SETD.0 ResumeText
|
||||||
|
CALL printString
|
||||||
|
INA 0x00
|
||||||
|
CALL newLine
|
||||||
|
RETI
|
||||||
|
|
||||||
|
; DP0 names a field and DP1 points at it in the frame. The caller does the stepping, with
|
||||||
|
; DPUP and a number written into the program, because a routine cannot hand a pointer back:
|
||||||
|
; CALL saves DP0 to DP2 and RET puts them back, so a walk done in here would be undone on
|
||||||
|
; the way out. Written that way first, and every field showed the frame's first byte.
|
||||||
|
breakByte:
|
||||||
|
CALL printString
|
||||||
|
LDA.1
|
||||||
|
CALL printByteHex
|
||||||
|
INIA 0x20
|
||||||
|
OUTA 0x00
|
||||||
|
RET
|
||||||
|
|
||||||
|
; The same for the two byte fields, most significant first the way the frame holds them.
|
||||||
|
breakWord:
|
||||||
|
CALL printString
|
||||||
|
LDA.1
|
||||||
|
CALL printByteHex
|
||||||
|
INCD.1
|
||||||
|
LDA.1
|
||||||
|
CALL printByteHex
|
||||||
|
INIA 0x20
|
||||||
|
OUTA 0x00
|
||||||
|
RET
|
||||||
|
|
||||||
; A and B together are a number. Prints it in decimal without leading zeroes, which covers
|
; A and B together are a number. Prints it in decimal without leading zeroes, which covers
|
||||||
; a line number and a byte count both, so there is no need for one service each.
|
; a line number and a byte count both, so there is no need for one service each.
|
||||||
handlePrintNumber:
|
handlePrintNumber:
|
||||||
@@ -1279,6 +1679,16 @@ dumpNoBank:
|
|||||||
; The cursor is left alone. Somebody poking a byte is usually looking at something else, and
|
; The cursor is left alone. Somebody poking a byte is usually looking at something else, and
|
||||||
; having the address they were reading move underneath them would be a poor reward.
|
; having the address they were reading move underneath them would be a poor reward.
|
||||||
doSet:
|
doSet:
|
||||||
|
; A bank can be present and still refuse to be written: the controller's own table is
|
||||||
|
; published read only, and writing to it is refused. A refusal nobody catches stops the
|
||||||
|
; machine, which is a poor answer to somebody looking around with b and then typing s.
|
||||||
|
CALL bankPresent
|
||||||
|
SETD.0 BankFlags
|
||||||
|
LDA.0
|
||||||
|
INIB 0x02 ; The read only bit of that bank's record.
|
||||||
|
AND
|
||||||
|
BNQ setReadOnly
|
||||||
|
|
||||||
SETD.1 TextRest
|
SETD.1 TextRest
|
||||||
LDD.0.1
|
LDD.0.1
|
||||||
CALL textHexWord
|
CALL textHexWord
|
||||||
@@ -1309,6 +1719,12 @@ setByte:
|
|||||||
POPD.0
|
POPD.0
|
||||||
BRI setByte
|
BRI setByte
|
||||||
|
|
||||||
|
setReadOnly:
|
||||||
|
SETD.0 ReadOnlyText
|
||||||
|
CALL printString
|
||||||
|
CALL newLine
|
||||||
|
BRI prompt
|
||||||
|
|
||||||
setWhat:
|
setWhat:
|
||||||
SETD.0 SetUsage
|
SETD.0 SetUsage
|
||||||
CALL printString
|
CALL printString
|
||||||
@@ -1666,6 +2082,385 @@ findFound:
|
|||||||
ADD
|
ADD
|
||||||
RET
|
RET
|
||||||
|
|
||||||
|
|
||||||
|
; ---- Assembling a line at a time ----
|
||||||
|
;
|
||||||
|
; a <address>, then instructions until a line that is just a dot. The syntax is the
|
||||||
|
; assembler's - a selector rides on the mnemonic as LDA.0 or LDD.0.1, and leaving one off
|
||||||
|
; means Data Pointer 0, exactly as it does in a source file - so nothing learned here has to
|
||||||
|
; be unlearned when writing a real program.
|
||||||
|
;
|
||||||
|
; NUMBERS ARE HEXADECIMAL AND BARE. A source file writes 0x2000 or 0d16 because it has both
|
||||||
|
; and must say which; a monitor has only one and says so once, in the manual, rather than on
|
||||||
|
; every line. It is the same reason x and d take bare addresses.
|
||||||
|
;
|
||||||
|
; What cannot be written here is a label, and that is the whole difference between this and
|
||||||
|
; the assembler proper: a label is a promise to fill an address in later, and later is what
|
||||||
|
; a line at a time does not have.
|
||||||
|
|
||||||
|
doAssemble:
|
||||||
|
SETD.1 TextRest
|
||||||
|
LDD.0.1
|
||||||
|
CALL textHexWord
|
||||||
|
BNQ assembleWhat
|
||||||
|
SETD.0 TextValue
|
||||||
|
SETD.1 DumpAt
|
||||||
|
CALL sbfsCopyWord ; Two bytes from DP0 to DP1, which sbfs already has.
|
||||||
|
|
||||||
|
assembleLine:
|
||||||
|
SETD.0 DumpAt
|
||||||
|
CALL printWordHex
|
||||||
|
SETD.0 AsmPrompt
|
||||||
|
CALL printString
|
||||||
|
|
||||||
|
SETD.0 AsmLine
|
||||||
|
INIB 0d40
|
||||||
|
CALL readLine
|
||||||
|
|
||||||
|
INA 0x01
|
||||||
|
INIB 0x02 ; ENDED, so there is nothing more to assemble.
|
||||||
|
AND
|
||||||
|
BNQ prompt
|
||||||
|
|
||||||
|
SETD.0 AsmLine
|
||||||
|
LDA.0
|
||||||
|
BRA assembleLine ; An empty line is somebody thinking.
|
||||||
|
|
||||||
|
SETD.0 AsmLine
|
||||||
|
SETD.1 DotText
|
||||||
|
CALL textSame
|
||||||
|
BRQ prompt
|
||||||
|
|
||||||
|
SETD.0 AsmLine
|
||||||
|
CALL textSplit ; The mnemonic, and whatever follows it.
|
||||||
|
CALL assembleOne
|
||||||
|
BRI assembleLine
|
||||||
|
|
||||||
|
assembleWhat:
|
||||||
|
SETD.0 AsmUsage
|
||||||
|
CALL printString
|
||||||
|
CALL newLine
|
||||||
|
BRI prompt
|
||||||
|
|
||||||
|
; The mnemonic is in CommandLine's place - AsmLine - and TextRest is what followed it.
|
||||||
|
; Puts the bytes down and steps the cursor past them.
|
||||||
|
assembleOne:
|
||||||
|
CALL takeMnemonic
|
||||||
|
CALL findByName
|
||||||
|
BNQ assembleUnknown
|
||||||
|
|
||||||
|
; WHATEVER IT NEEDS IS READ BEFORE ANYTHING IS WRITTEN. Emitting the opcode first and
|
||||||
|
; discovering the missing value afterwards leaves half an instruction in memory, which the
|
||||||
|
; next line usually covers up and the last line of a session does not.
|
||||||
|
SETD.0 AsmShape
|
||||||
|
LDA.0
|
||||||
|
BRA assemblePut ; 0, nothing to read.
|
||||||
|
DECA
|
||||||
|
BRA assembleWantAddress ; 1
|
||||||
|
DECA
|
||||||
|
BRA assembleWantByte ; 2
|
||||||
|
DECA
|
||||||
|
BRA assemblePut ; 3, a selector and nothing else.
|
||||||
|
DECA
|
||||||
|
BRA assembleWantByte ; 4
|
||||||
|
DECA
|
||||||
|
BRA assembleWantAddress ; 5
|
||||||
|
BRI assemblePut ; 6, two selectors.
|
||||||
|
|
||||||
|
assembleWantByte:
|
||||||
|
SETD.1 TextRest
|
||||||
|
LDD.0.1
|
||||||
|
CALL textHexWord
|
||||||
|
BNQ assembleNeedsValue
|
||||||
|
BRI assemblePut
|
||||||
|
|
||||||
|
assembleWantAddress:
|
||||||
|
SETD.1 TextRest
|
||||||
|
LDD.0.1
|
||||||
|
CALL textHexWord
|
||||||
|
BNQ assembleNeedsValue
|
||||||
|
|
||||||
|
assemblePut:
|
||||||
|
; Point the controller at the cursor. Every byte written steps it on by itself.
|
||||||
|
SETD.0 DumpBank
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE3
|
||||||
|
SETD.0 DumpAt
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE4
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE5
|
||||||
|
|
||||||
|
SETD.0 AsmOpcode
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE9
|
||||||
|
|
||||||
|
; What follows depends only on the shape, exactly as it does when reading one back.
|
||||||
|
SETD.0 AsmShape
|
||||||
|
LDA.0
|
||||||
|
BRA assembleDone ; 0
|
||||||
|
DECA
|
||||||
|
BRA assembleAddress ; 1
|
||||||
|
DECA
|
||||||
|
BRA assembleByte ; 2
|
||||||
|
DECA
|
||||||
|
BRA assembleSelector ; 3
|
||||||
|
DECA
|
||||||
|
BRA assembleSelByte ; 4
|
||||||
|
DECA
|
||||||
|
BRA assembleSelAddress ; 5
|
||||||
|
|
||||||
|
SETD.0 AsmSelOne
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE9
|
||||||
|
SETD.0 AsmSelTwo
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE9
|
||||||
|
BRI assembleDone
|
||||||
|
|
||||||
|
assembleSelector:
|
||||||
|
SETD.0 AsmSelOne
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE9
|
||||||
|
BRI assembleDone
|
||||||
|
|
||||||
|
assembleSelByte:
|
||||||
|
SETD.0 AsmSelOne
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE9
|
||||||
|
BRI assembleByte
|
||||||
|
|
||||||
|
assembleSelAddress:
|
||||||
|
SETD.0 AsmSelOne
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE9
|
||||||
|
BRI assembleAddress
|
||||||
|
|
||||||
|
assembleByte:
|
||||||
|
SETD.0 TextValue
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE9
|
||||||
|
BRI assembleDone
|
||||||
|
|
||||||
|
assembleAddress:
|
||||||
|
SETD.0 TextValue
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE9
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
OUTA 0xE9
|
||||||
|
|
||||||
|
assembleDone:
|
||||||
|
; Past what was just written. The length is the shape's, out of the same table the
|
||||||
|
; disassembler reads, so the two can never disagree about how much room one takes.
|
||||||
|
SETD.0 ShapeLength
|
||||||
|
SETD.1 AsmShape
|
||||||
|
LDB.1
|
||||||
|
assembleStep:
|
||||||
|
BRB assembleStepped
|
||||||
|
INCD.0
|
||||||
|
DECB
|
||||||
|
BRI assembleStep
|
||||||
|
assembleStepped:
|
||||||
|
LDA.0
|
||||||
|
SETD.0 DumpAt
|
||||||
|
CALL stepCursor
|
||||||
|
RET
|
||||||
|
|
||||||
|
assembleUnknown:
|
||||||
|
SETD.0 NoSuchOp
|
||||||
|
CALL printString
|
||||||
|
CALL newLine
|
||||||
|
RET
|
||||||
|
|
||||||
|
assembleNeedsValue:
|
||||||
|
SETD.0 NeedsValue
|
||||||
|
CALL printString
|
||||||
|
CALL newLine
|
||||||
|
RET
|
||||||
|
|
||||||
|
; DP0 is a two byte address and A is how far to move it on.
|
||||||
|
stepCursor:
|
||||||
|
DPUP.0 0d01
|
||||||
|
LDB.0
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
STQ.0
|
||||||
|
DPDN.0 0d01
|
||||||
|
LDA.0
|
||||||
|
RSTB
|
||||||
|
ADD
|
||||||
|
STQ.0
|
||||||
|
RET
|
||||||
|
|
||||||
|
; The typed mnemonic into four padded characters and up to two selectors, which is the shape
|
||||||
|
; the table holds. Folded to upper case, because the assembler does not care about the case
|
||||||
|
; of a mnemonic and neither should this.
|
||||||
|
;
|
||||||
|
; A selector that was not typed is Data Pointer 0, exactly as an omitted one means in a
|
||||||
|
; source file. That is worth matching rather than demanding: half the instruction set names
|
||||||
|
; a pointer, and most code only ever uses the first.
|
||||||
|
takeMnemonic:
|
||||||
|
RSTA
|
||||||
|
SETD.1 AsmSelOne
|
||||||
|
STA.1
|
||||||
|
SETD.1 AsmSelTwo
|
||||||
|
STA.1
|
||||||
|
INIA 0d4
|
||||||
|
SETD.1 AsmLeft
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
SETD.0 AsmLine
|
||||||
|
SETD.1 AsmName
|
||||||
|
|
||||||
|
takeMnemonicChar:
|
||||||
|
SETD.2 AsmLeft
|
||||||
|
LDA.2
|
||||||
|
BRA takeMnemonicPad ; Four is as long as a mnemonic gets.
|
||||||
|
LDA.0
|
||||||
|
BRA takeMnemonicPad ; The word ended.
|
||||||
|
INIB 0d46 ; A dot, so the selectors start here.
|
||||||
|
XOR
|
||||||
|
BRQ takeMnemonicPad
|
||||||
|
|
||||||
|
; Nothing below compares against A, so it survives all of this and is stored at the end.
|
||||||
|
INIB 0d97 ; a
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BRC takeMnemonicPut ; It borrowed, so this is below 'a'.
|
||||||
|
INIB 0d123 ; One past z.
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
BNC takeMnemonicPut ; No borrow, so it is 'z' or later.
|
||||||
|
INIB 0x20
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
MVQA ; Upper case, which is how the table holds them.
|
||||||
|
|
||||||
|
takeMnemonicPut:
|
||||||
|
STA.1
|
||||||
|
INCD.0
|
||||||
|
INCD.1
|
||||||
|
SETD.2 AsmLeft
|
||||||
|
LDA.2
|
||||||
|
DECA
|
||||||
|
STA.2
|
||||||
|
BRI takeMnemonicChar
|
||||||
|
|
||||||
|
takeMnemonicPad:
|
||||||
|
SETD.2 AsmLeft
|
||||||
|
LDA.2
|
||||||
|
BRA takeMnemonicSelectors
|
||||||
|
INIA 0x20
|
||||||
|
STA.1
|
||||||
|
INCD.1
|
||||||
|
SETD.2 AsmLeft
|
||||||
|
LDA.2
|
||||||
|
DECA
|
||||||
|
STA.2
|
||||||
|
BRI takeMnemonicPad
|
||||||
|
|
||||||
|
takeMnemonicSelectors:
|
||||||
|
RSTA
|
||||||
|
STA.1 ; The four are a string now, like the ones in the table.
|
||||||
|
|
||||||
|
LDA.0
|
||||||
|
INIB 0d46
|
||||||
|
XOR
|
||||||
|
BNQ takeMnemonicEnd ; No dot, so both selectors stay at nought.
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
INIB 0d48
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
MVQA
|
||||||
|
SETD.1 AsmSelOne
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
INIB 0d46
|
||||||
|
XOR
|
||||||
|
BNQ takeMnemonicEnd
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
INIB 0d48
|
||||||
|
CCF
|
||||||
|
SUB
|
||||||
|
MVQA
|
||||||
|
SETD.1 AsmSelTwo
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
takeMnemonicEnd:
|
||||||
|
RET
|
||||||
|
|
||||||
|
; Looks the four characters up. AsmOpcode and AsmShape are filled in and Q is zero, or Q is
|
||||||
|
; not zero and there is no such instruction.
|
||||||
|
;
|
||||||
|
; The same table the disassembler reads, searched the other way round. That is the point of
|
||||||
|
; it being a table rather than two lists: what can be written can be read back, and what can
|
||||||
|
; be read back can be written, and neither can drift from the other.
|
||||||
|
findByName:
|
||||||
|
SETD.3 Instructions
|
||||||
|
SETD.0 InstructionCount
|
||||||
|
LDA.0
|
||||||
|
SETD.1 AsmLeft
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
findByNameStep:
|
||||||
|
PSHD.3
|
||||||
|
POPD.0
|
||||||
|
DPUP.0 0d02
|
||||||
|
SETD.2 AsmName
|
||||||
|
INIA 0d4
|
||||||
|
SETD.1 AsmCount
|
||||||
|
STA.1
|
||||||
|
|
||||||
|
findByNameChar:
|
||||||
|
LDA.0
|
||||||
|
LDB.2
|
||||||
|
XOR
|
||||||
|
BNQ findByNameNext
|
||||||
|
INCD.0
|
||||||
|
INCD.2
|
||||||
|
SETD.1 AsmCount
|
||||||
|
LDA.1
|
||||||
|
DECA
|
||||||
|
STA.1
|
||||||
|
BNA findByNameChar
|
||||||
|
|
||||||
|
PSHD.3
|
||||||
|
POPD.0
|
||||||
|
LDA.0
|
||||||
|
SETD.1 AsmOpcode
|
||||||
|
STA.1
|
||||||
|
PSHD.3
|
||||||
|
POPD.0
|
||||||
|
INCD.0
|
||||||
|
LDA.0
|
||||||
|
SETD.1 AsmShape
|
||||||
|
STA.1
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
|
findByNameNext:
|
||||||
|
DPUP.3 0d07
|
||||||
|
SETD.1 AsmLeft
|
||||||
|
LDA.1
|
||||||
|
DECA
|
||||||
|
STA.1
|
||||||
|
BNA findByNameStep
|
||||||
|
RSTA
|
||||||
|
INIB 0d1
|
||||||
|
CCF
|
||||||
|
ADD
|
||||||
|
RET
|
||||||
|
|
||||||
; Is there such a bank? Q is zero if there is not.
|
; Is there such a bank? Q is zero if there is not.
|
||||||
;
|
;
|
||||||
; ASKING THE CONTROLLER FOR A BANK THAT IS NOT THERE IS REFUSED, and a refusal nobody
|
; ASKING THE CONTROLLER FOR A BANK THAT IS NOT THERE IS REFUSED, and a refusal nobody
|
||||||
@@ -1694,6 +2489,8 @@ bankPresent:
|
|||||||
LDA.0
|
LDA.0
|
||||||
OUTA 0xE2
|
OUTA 0xE2
|
||||||
INA 0xE9 ; The flags byte of that bank's record.
|
INA 0xE9 ; The flags byte of that bank's record.
|
||||||
|
SETD.0 BankFlags
|
||||||
|
STA.0 ; Kept whole, since present is not the only thing it says.
|
||||||
INIB 0x01
|
INIB 0x01
|
||||||
AND
|
AND
|
||||||
RET
|
RET
|
||||||
@@ -1788,6 +2585,34 @@ NothingLoaded:
|
|||||||
Finished:
|
Finished:
|
||||||
"finished"
|
"finished"
|
||||||
|
|
||||||
|
BreakText:
|
||||||
|
"break at "
|
||||||
|
ARegText:
|
||||||
|
"A "
|
||||||
|
BRegText:
|
||||||
|
"B "
|
||||||
|
QRegText:
|
||||||
|
"Q "
|
||||||
|
SRegText:
|
||||||
|
"status "
|
||||||
|
DP0Text:
|
||||||
|
"DP0 "
|
||||||
|
DP1Text:
|
||||||
|
"DP1 "
|
||||||
|
DP2Text:
|
||||||
|
"DP2 "
|
||||||
|
DP3Text:
|
||||||
|
"DP3 "
|
||||||
|
SPText:
|
||||||
|
"SP "
|
||||||
|
CarryText:
|
||||||
|
"carry "
|
||||||
|
FaultText:
|
||||||
|
"fault "
|
||||||
|
IntsText:
|
||||||
|
"interrupts "
|
||||||
|
ResumeText:
|
||||||
|
"press a key "
|
||||||
MonitorPrompt:
|
MonitorPrompt:
|
||||||
"* "
|
"* "
|
||||||
UnknownText:
|
UnknownText:
|
||||||
@@ -1796,7 +2621,7 @@ UnknownText:
|
|||||||
MonitorName:
|
MonitorName:
|
||||||
"monitor"
|
"monitor"
|
||||||
MonitorHelp:
|
MonitorHelp:
|
||||||
"x examine, d disassemble, s set, b bank, g go, exit leaves"
|
"x examine, d disassemble, a assemble, s set, b bank, g go, exit leaves"
|
||||||
ExamineName:
|
ExamineName:
|
||||||
"x"
|
"x"
|
||||||
DisName:
|
DisName:
|
||||||
@@ -1813,8 +2638,22 @@ BankIs:
|
|||||||
"bank "
|
"bank "
|
||||||
SetUsage:
|
SetUsage:
|
||||||
"s <address> <byte> <byte> ..."
|
"s <address> <byte> <byte> ..."
|
||||||
|
ReadOnlyText:
|
||||||
|
"that bank will not be written"
|
||||||
GoUsage:
|
GoUsage:
|
||||||
"g <address>"
|
"g <address>"
|
||||||
|
AsmName2:
|
||||||
|
"a"
|
||||||
|
AsmPrompt:
|
||||||
|
": "
|
||||||
|
DotText:
|
||||||
|
"."
|
||||||
|
AsmUsage:
|
||||||
|
"a <address>, then instructions, then a dot"
|
||||||
|
NoSuchOp:
|
||||||
|
"no such instruction"
|
||||||
|
NeedsValue:
|
||||||
|
"that one needs a value after it"
|
||||||
DirName:
|
DirName:
|
||||||
"dir"
|
"dir"
|
||||||
LoadName:
|
LoadName:
|
||||||
@@ -1855,6 +2694,37 @@ RunArgument:
|
|||||||
PrintNumber:
|
PrintNumber:
|
||||||
0x00 0x00
|
0x00 0x00
|
||||||
|
|
||||||
|
; ---- Where the last file anybody asked about lives ----
|
||||||
|
;
|
||||||
|
; osFileBlock is handed a name every time it is called, because a stateless service has no
|
||||||
|
; handle to leak and nothing left open by a program that stops in the middle. Taken at its
|
||||||
|
; word that means searching the directory once per block, so reading a four hundred block
|
||||||
|
; file walks the directory four hundred times over to be told the same thing.
|
||||||
|
;
|
||||||
|
; So the last answer is kept. A call naming the same file as the one before it skips the
|
||||||
|
; search and puts these back where sbfs keeps them. MEASURED, on the 329 block file the
|
||||||
|
; streaming test reads: 7% of the whole run saved when the file is the first entry in the
|
||||||
|
; directory, 11% when it is the sixteenth. Modest, and worth having for the shape rather
|
||||||
|
; than the size - what it really removes is a cost that grows with how full the disk is,
|
||||||
|
; on the one operation that is repeated once per block. NOTHING IS EVER TRUSTED THAT WAS NOT
|
||||||
|
; PUT HERE BY A SEARCH: this is a copy of an answer, not a second place where the truth
|
||||||
|
; about a file is written, and every path that could make it wrong calls fileForget. That
|
||||||
|
; is what makes it a speed rather than a promise - a cache that has been thrown away is
|
||||||
|
; indistinguishable from one that was never filled.
|
||||||
|
;
|
||||||
|
; The name is twenty three bytes for a name of twenty two, so that a name filling the
|
||||||
|
; field still has a zero after it and can be compared as a string.
|
||||||
|
FileCacheValid:
|
||||||
|
0x00
|
||||||
|
FileCacheName:
|
||||||
|
#Reserve 0d23
|
||||||
|
FileCacheStart:
|
||||||
|
0x00 0x00
|
||||||
|
FileCacheBlocks:
|
||||||
|
0x00 0x00
|
||||||
|
FileCacheTail:
|
||||||
|
0x00
|
||||||
|
|
||||||
; ---- The vectors a loaded program brought with it ----
|
; ---- The vectors a loaded program brought with it ----
|
||||||
;
|
;
|
||||||
; Six bytes each: where it goes, what goes there, and what was there before. The last two
|
; Six bytes each: where it goes, what goes there, and what was there before. The last two
|
||||||
@@ -1886,6 +2756,24 @@ DumpCount:
|
|||||||
0x00
|
0x00
|
||||||
BankWas:
|
BankWas:
|
||||||
0x00
|
0x00
|
||||||
|
BankFlags:
|
||||||
|
0x00
|
||||||
|
AsmSelOne:
|
||||||
|
0x00
|
||||||
|
AsmSelTwo:
|
||||||
|
0x00
|
||||||
|
AsmLeft:
|
||||||
|
0x00
|
||||||
|
AsmCount:
|
||||||
|
0x00
|
||||||
|
AsmOpcode:
|
||||||
|
0x00
|
||||||
|
AsmShape:
|
||||||
|
0x00
|
||||||
|
AsmName:
|
||||||
|
#Reserve 0d5
|
||||||
|
AsmLine:
|
||||||
|
#Reserve 0d41
|
||||||
ShowAsCode:
|
ShowAsCode:
|
||||||
0x00
|
0x00
|
||||||
DumpRecord:
|
DumpRecord:
|
||||||
@@ -2005,5 +2893,8 @@ CommandLine:
|
|||||||
osFileSave handleFileSave
|
osFileSave handleFileSave
|
||||||
osFileDelete handleFileDelete
|
osFileDelete handleFileDelete
|
||||||
osFileRename handleFileRename
|
osFileRename handleFileRename
|
||||||
|
osFileInfo handleFileInfo
|
||||||
|
osFileBlock handleFileBlock
|
||||||
osPrintNumber handlePrintNumber
|
osPrintNumber handlePrintNumber
|
||||||
|
osBreak handleBreak
|
||||||
Device 0x20 diskDone
|
Device 0x20 diskDone
|
||||||
|
|||||||
@@ -473,6 +473,48 @@ sbfsReadDone:
|
|||||||
ADD ; Q is zero: read.
|
ADD ; Q is zero: read.
|
||||||
RET
|
RET
|
||||||
|
|
||||||
|
; Reads one block of the file sbfsFind found, the one SbfsIndex names counting from zero,
|
||||||
|
; into Data Memory at DP1. Q is zero if it worked.
|
||||||
|
;
|
||||||
|
; This is the whole of streaming: a file too big to hold is read a block at a time by
|
||||||
|
; asking for each in turn, and nothing has to be kept between the calls but the number.
|
||||||
|
; sbfsRead is what this would be if it were called in a loop, which is why the two look
|
||||||
|
; alike; it stays as it is because reading a whole small file is what most callers want
|
||||||
|
; and doing it in one call is both shorter and faster.
|
||||||
|
;
|
||||||
|
; The whole block comes across, including a last one that the file only partly fills, so
|
||||||
|
; the bytes past the end of it are whatever else was on the disk there. SbfsFileTail says
|
||||||
|
; where the file stops and it is the caller's business to respect it, the same bargain
|
||||||
|
; sbfsRead offers.
|
||||||
|
;
|
||||||
|
; An index past the end of the file is not caught here. It reads whatever block that
|
||||||
|
; works out to, which is somebody else's file or free space; the caller knows how many
|
||||||
|
; blocks there are, because sbfsFileExtent tells it.
|
||||||
|
sbfsReadOne:
|
||||||
|
PSHD.1
|
||||||
|
POPD.3 ; Where it goes. DP3 is the one pointer a CALL does not put back.
|
||||||
|
|
||||||
|
SETD.0 SbfsBlock
|
||||||
|
SETD.2 SbfsFileStart
|
||||||
|
CALL sbfsSetWord
|
||||||
|
SETD.0 SbfsBlock
|
||||||
|
SETD.2 SbfsIndex
|
||||||
|
CALL sbfsAddWord
|
||||||
|
|
||||||
|
CALL sbfsReadBlock
|
||||||
|
BNQ sbfsReadOneDone ; The read failed, and Q says so.
|
||||||
|
|
||||||
|
PSHD.3
|
||||||
|
POPD.1
|
||||||
|
CALL sbfsBufferOut
|
||||||
|
|
||||||
|
RSTA
|
||||||
|
RSTB
|
||||||
|
CCF
|
||||||
|
ADD ; Q is zero: read.
|
||||||
|
sbfsReadOneDone:
|
||||||
|
RET
|
||||||
|
|
||||||
; ---- Writing ----
|
; ---- Writing ----
|
||||||
|
|
||||||
; Where the first block that can hold a file is: past the superblock and the directory.
|
; Where the first block that can hold a file is: past the superblock and the directory.
|
||||||
@@ -1239,6 +1281,8 @@ SbfsMatchLeft:
|
|||||||
0x00
|
0x00
|
||||||
SbfsLeft:
|
SbfsLeft:
|
||||||
0x00
|
0x00
|
||||||
|
SbfsIndex:
|
||||||
|
0x00 0x00
|
||||||
|
|
||||||
; ---- What a walk through the directory keeps between calls ----
|
; ---- What a walk through the directory keeps between calls ----
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,52 @@
|
|||||||
osFileDelete 0d22 ; DP0 names it. Q is zero if it went.
|
osFileDelete 0d22 ; DP0 names it. Q is zero if it went.
|
||||||
osFileRename 0d23 ; DP0 is the name it has, DP1 the name it should have. Q is zero if it moved.
|
osFileRename 0d23 ; DP0 is the name it has, DP1 the name it should have. Q is zero if it moved.
|
||||||
|
|
||||||
|
; ---- Reading a file that will not fit ----
|
||||||
|
;
|
||||||
|
; osFileRead answers with a whole file in Data Memory, which settles it for anything under
|
||||||
|
; 64K and settles nothing above. These two are the other way of asking: how big is it, and
|
||||||
|
; then give me one block of it at a time. Nothing is kept between the calls but the number
|
||||||
|
; of the block wanted, so there is no handle to open, none to close, and nothing left
|
||||||
|
; behind by a program that stops in the middle. The system remembers where the last file it
|
||||||
|
; was asked about lives, so asking for four hundred blocks of one file costs one search of
|
||||||
|
; the directory rather than four hundred; that is a speed, not a promise, and a caller
|
||||||
|
; never has to know about it.
|
||||||
|
;
|
||||||
|
; THESE TWO SAY WHY WHEN THE ANSWER IS NO, which the others do not. Everywhere else the
|
||||||
|
; only useful thing to do about a failure is to give up, so one value is enough. These
|
||||||
|
; exist to be asked questions with - is it there, is there any more of it - and the
|
||||||
|
; difference between "no disk", "no such file" and "that was the last block" is the answer
|
||||||
|
; rather than an excuse.
|
||||||
|
;
|
||||||
|
; 1 there is no disk
|
||||||
|
; 2 there is no file of that name
|
||||||
|
; 3 that block is past the end of the file (osFileBlock only)
|
||||||
|
; 4 the disk would not read it (osFileBlock only)
|
||||||
|
osFileInfo 0d26 ; DP0 names it. Q is zero if it is there, DP3 is how many blocks.
|
||||||
|
osFileBlock 0d27 ; DP0 names it, DP1 says where, A and B are which block from zero.
|
||||||
|
; Q is zero if it read, DP3 is how many of its bytes are the file's:
|
||||||
|
; a whole 0d256 except in a last block that is short. That count is
|
||||||
|
; why DP3 answers and not a register - 0d256 does not fit in a byte,
|
||||||
|
; and a count that lied about a full block would make every reader
|
||||||
|
; treat the end of a file as a special case.
|
||||||
|
|
||||||
; ---- And with the console ----
|
; ---- And with the console ----
|
||||||
;
|
;
|
||||||
; printString is already up there. This is the other half of what a program prints: a
|
; printString is already up there. This is the other half of what a program prints: a
|
||||||
; number, in decimal, without leading zeroes. A and B together, so one service covers both
|
; number, in decimal, without leading zeroes. A and B together, so one service covers both
|
||||||
; a line number and a byte count and there is no need for two.
|
; a line number and a byte count and there is no need for two.
|
||||||
osPrintNumber 0d24
|
osPrintNumber 0d24
|
||||||
|
|
||||||
|
; ---- Stopping to look ----
|
||||||
|
;
|
||||||
|
; A breakpoint. Put SWI osBreak anywhere in a program and the system shows every register as
|
||||||
|
; the program had them, waits for a key, and carries on.
|
||||||
|
;
|
||||||
|
; NOTHING IS OVERWRITTEN, which is what makes this simple. A breakpoint that replaced an
|
||||||
|
; instruction would have to put it back to continue, and putting it back disarms the
|
||||||
|
; breakpoint - so firing twice would need the instruction to be stepped over and the
|
||||||
|
; breakpoint replaced behind it, and this machine has no way to step one instruction. An SWI
|
||||||
|
; costs two bytes of the program and fires for ever, because there was never anything to
|
||||||
|
; restore. The price is that it is part of the program: a build with breakpoints in it has
|
||||||
|
; different addresses from one without.
|
||||||
|
osBreak 0d25
|
||||||
|
|||||||
+48
-3
@@ -69,15 +69,60 @@ $(BUILD)/CosmOS/Apps/%.sbx: CosmOS/Apps/%.asm
|
|||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
$(ASM) $(INCLUDES) -M $(@:.sbx=.d) -o $@ $<
|
$(ASM) $(INCLUDES) -M $(@:.sbx=.d) -o $@ $<
|
||||||
|
|
||||||
cosmos: $(COSMOS) $(APPS)
|
# The assembler that runs on the machine. It is not in Apps/ because it is not one file:
|
||||||
|
# it has a directory of its own, the way the C assembler does. Its own pieces are found
|
||||||
|
# beside it without being told, since an include is looked for next to the file that asked
|
||||||
|
# for it before anywhere else; only services.asm needs the include path.
|
||||||
|
NATIVE_ASM = $(BUILD)/CosmOS/Assembler/Asm.sbx
|
||||||
|
DEPENDENCIES += $(NATIVE_ASM:.sbx=.d)
|
||||||
|
|
||||||
|
$(NATIVE_ASM): CosmOS/Assembler/Asm.asm
|
||||||
|
@mkdir -p $(@D)
|
||||||
|
$(ASM) $(INCLUDES) -M $(@:.sbx=.d) -o $@ $<
|
||||||
|
|
||||||
|
cosmos: $(COSMOS) $(APPS) $(NATIVE_ASM)
|
||||||
|
|
||||||
# Made from scratch every time, so that what is on it is what is in Apps/ now and not
|
# Made from scratch every time, so that what is on it is what is in Apps/ now and not
|
||||||
# also whatever used to be.
|
# also whatever used to be.
|
||||||
$(COSMOS_DISK): $(APPS)
|
$(COSMOS_DISK): $(APPS) $(NATIVE_ASM) testPrograms/stringKeyword.asm \
|
||||||
|
CosmOS/Apps/hello.asm CosmOS/Apps/Say.asm CosmOS/Apps/Keys.asm \
|
||||||
|
CosmOS/Source/services.asm CosmOS/Source/console.asm \
|
||||||
|
$(wildcard CosmOS/Source/*.asm) $(wildcard CosmOS/Assembler/*.asm)
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
rm -f $@
|
rm -f $@
|
||||||
$(DISKTOOL) format $@ 256 2
|
$(DISKTOOL) format $@ 4096 8
|
||||||
@for app in $(APPS); do $(DISKTOOL) put $@ $$app; done
|
@for app in $(APPS); do $(DISKTOOL) put $@ $$app; done
|
||||||
|
$(DISKTOOL) put $@ $(NATIVE_ASM)
|
||||||
|
@# SOURCE goes on as well, because an assembler with nothing to assemble is a
|
||||||
|
@# demonstration of nothing.
|
||||||
|
@#
|
||||||
|
@# hello.asm and Say.asm are the APPLICATION versions, so each assembles to a .sbx
|
||||||
|
@# written straight over the one the host tool put there - which means the next
|
||||||
|
@# thing loaded is a program the machine built itself, in the same breath.
|
||||||
|
@#
|
||||||
|
@# Keys.asm is the one that brings a vector of its own, so assembling it exercises the
|
||||||
|
@# version two header and the Vector Segment: the loader installs its handler, the
|
||||||
|
@# console interrupts into it, and the shell takes the vector back at exit.
|
||||||
|
@#
|
||||||
|
@# strings.asm is the odd one out on purpose: it has no #Include and no #Base, so it
|
||||||
|
@# comes out as a boot image rather than a loadable program, and the difference
|
||||||
|
@# between the two is visible on one disk.
|
||||||
|
$(DISKTOOL) put $@ CosmOS/Apps/hello.asm
|
||||||
|
$(DISKTOOL) put $@ CosmOS/Apps/Say.asm
|
||||||
|
$(DISKTOOL) put $@ CosmOS/Apps/Keys.asm
|
||||||
|
$(DISKTOOL) put $@ CosmOS/Source/services.asm
|
||||||
|
$(DISKTOOL) put $@ CosmOS/Source/console.asm
|
||||||
|
$(DISKTOOL) put $@ testPrograms/stringKeyword.asm strings.asm
|
||||||
|
@# And the whole of CosmOS, and the whole of the assembler, so that the machine can
|
||||||
|
@# build the system it is running on and then build the thing that built it:
|
||||||
|
@#
|
||||||
|
@# > load Asm.sbx
|
||||||
|
@# > run cosmos.asm
|
||||||
|
@# > run Asm.asm
|
||||||
|
@#
|
||||||
|
@# Both come out byte for byte what the host tool makes from the same source.
|
||||||
|
@for f in CosmOS/Source/*.asm CosmOS/Assembler/*.asm; do \
|
||||||
|
$(DISKTOOL) put $@ $$f >/dev/null; done
|
||||||
|
|
||||||
# The system as well as the disk. Building only the image leaves whatever cosmos.bin was
|
# The system as well as the disk. Building only the image leaves whatever cosmos.bin was
|
||||||
# there before, or none at all, and then the disk is booted with a system that does not
|
# there before, or none at all, and then the disk is booted with a system that does not
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
; A program whose data contains the names of directives.
|
||||||
|
;
|
||||||
|
; The quotes are gone by the time the assembler looks at a token, so "#Program" written as
|
||||||
|
; a string looked exactly like the directive. It was read as one: the segment silently
|
||||||
|
; changed in the middle of the Data Segment, the string's nine bytes were charged to the
|
||||||
|
; Program cursor instead, and every label defined after it came out nine bytes wrong. The
|
||||||
|
; file still had a valid header and a plausible length, so nothing said a word - the only
|
||||||
|
; symptom was a program that jumped into the middle of an instruction.
|
||||||
|
;
|
||||||
|
; This is the fourth of that family. A string spelling a mnemonic assembled as that
|
||||||
|
; instruction, a string beginning with a zero was rejected as a malformed literal, a string
|
||||||
|
; in the Program Segment was discarded in silence, and now this.
|
||||||
|
;
|
||||||
|
; It came up because an assembler written FOR this machine has to compare tokens against
|
||||||
|
; the directive names, so it necessarily has them in its data. Nothing else ever did.
|
||||||
|
;
|
||||||
|
; What it checks: that the strings survive as strings, and that a label defined after them
|
||||||
|
; still points where it should. Everything after Directives would shift if the bug came
|
||||||
|
; back, so printing Message is the test.
|
||||||
|
;
|
||||||
|
; Written by Anachronaut
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
start:
|
||||||
|
SETD.0 Directives
|
||||||
|
CALL show
|
||||||
|
SETD.0 Message
|
||||||
|
CALL show
|
||||||
|
HALT
|
||||||
|
|
||||||
|
show:
|
||||||
|
LDA.0
|
||||||
|
BRA showDone
|
||||||
|
OUTA 0x00
|
||||||
|
INCD.0
|
||||||
|
BRI show
|
||||||
|
showDone:
|
||||||
|
RET
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
Directives:
|
||||||
|
"#Program #Data #Include #Vectors #Base #Align #Reserve
|
||||||
|
"
|
||||||
|
Message:
|
||||||
|
"and a label after them still points at itself
|
||||||
|
"
|
||||||
@@ -16,6 +16,8 @@ SplitBit is a custom 8 bit system designed for hobbyist projects and experimenta
|
|||||||
- Loadable Programs: A program that was not booted from carries a header saying where it belongs, and Programs/loader.asm reads one off a disk, puts it there, and runs it.
|
- Loadable Programs: A program that was not booted from carries a header saying where it belongs, and Programs/loader.asm reads one off a disk, puts it there, and runs it.
|
||||||
- An Operating System: CosmOS boots the machine, mounts a disk, lists what is on it, loads a program and runs it, and takes the machine back when it finishes. It comes with a library of programs to run, including a game and a line editor that writes files a person typed.
|
- An Operating System: CosmOS boots the machine, mounts a disk, lists what is on it, loads a program and runs it, and takes the machine back when it finishes. It comes with a library of programs to run, including a game and a line editor that writes files a person typed.
|
||||||
- System Services: A loaded program reaches the console and the disk through numbered software interrupts rather than carrying a copy of the code that drives them. The numbers are written down in one file that both sides include, so neither ever types one. It took the editor from 4941 bytes to 1983 without changing a line of what it does.
|
- System Services: A loaded program reaches the console and the disk through numbered software interrupts rather than carrying a copy of the code that drives them. The numbers are written down in one file that both sides include, so neither ever types one. It took the editor from 4941 bytes to 1983 without changing a line of what it does.
|
||||||
|
- A Native Assembler: SplitBit assembles SplitBit. Programs/CosmOS/Assembler/ is an assembler written in SplitBit assembly that runs under CosmOS, reads source off a SplitBit disk, and writes a binary back to it with no host involved. It builds boot images and loadable applications, following every directive the language has. IT ASSEMBLES COSMOS, AND IT ASSEMBLES ITSELF, both byte for byte identical to what the C assembler produces from the same source. Tests/native.sh then boots the CosmOS that CosmOS built and has that one assemble CosmOS again, so the machinery has been through itself: after that the host is a convenience rather than a necessity.
|
||||||
|
- Streaming Reads: A file bigger than the machine's memory is read a block at a time, through services that keep nothing open between calls. CosmOS's own source is 104K against 64K of Data Memory, so this is what a self-hosted assembler will stand on.
|
||||||
- Storage: A block device with 256 byte blocks and 16 megabytes of them, backed by an image file on the host. It knows blocks and not files, because a filesystem is meant to be software SplitBit runs.
|
- Storage: A block device with 256 byte blocks and 16 megabytes of them, backed by an image file on the host. It knows blocks and not files, because a filesystem is meant to be software SplitBit runs.
|
||||||
- Memory Controller: Reads and writes Program Memory, moves blocks between memory banks, reaches memory that devices bring with them, and guards a range against being written by accident. It is how a SplitBit machine loads a program.
|
- Memory Controller: Reads and writes Program Memory, moves blocks between memory banks, reaches memory that devices bring with them, and guards a range against being written by accident. It is how a SplitBit machine loads a program.
|
||||||
- Assembler: Assemble human readable assembly language files directly into SplitBit compatible binary files. Supports including external files, handling labels, alignment and reservation, and defining Program, Data and Vector segments.
|
- Assembler: Assemble human readable assembly language files directly into SplitBit compatible binary files. Supports including external files, handling labels, alignment and reservation, and defining Program, Data and Vector segments.
|
||||||
|
|||||||
@@ -44,6 +44,19 @@ void toUppercase(char *str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int checkIfKeyword(intermediateElement *currentElement) {
|
int checkIfKeyword(intermediateElement *currentElement) {
|
||||||
|
// A string is never a keyword, however it is spelled. The quotes are gone by the time
|
||||||
|
// a token is looked at, so a program with "#Program" in its data - which is exactly
|
||||||
|
// what an assembler written for this machine needs, to compare tokens against - had
|
||||||
|
// the string read as the directive. The segment silently changed in the middle of the
|
||||||
|
// Data Segment, the string's nine bytes were charged to the Program cursor, and every
|
||||||
|
// label after it was nine bytes out while the file itself still looked well formed.
|
||||||
|
//
|
||||||
|
// This is the fourth of this family: the instruction check and the literal check both
|
||||||
|
// carry the same guard, for the same reason. It lives inside this one rather than at
|
||||||
|
// the call site so that it cannot be left off again.
|
||||||
|
if (currentElement->type == STRING) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (currentElement->token[0] == '#') {
|
if (currentElement->token[0] == '#') {
|
||||||
if (debug) printf("Token: %s is a keyword.\n", currentElement->token);
|
if (debug) printf("Token: %s is a keyword.\n", currentElement->token);
|
||||||
currentElement->type = KEYWORD;
|
currentElement->type = KEYWORD;
|
||||||
|
|||||||
@@ -478,3 +478,87 @@ Broken:
|
|||||||
The output is `ready`, `trap`, then `device`.
|
The output is `ready`, `trap`, then `device`.
|
||||||
|
|
||||||
Note that a vector name and a routine name are kept apart, so naming both `announce` is allowed. If that reads as confusing, name them differently: nothing requires them to match.
|
Note that a vector name and a routine name are kept apart, so naming both `announce` is allowed. If that reads as confusing, name them differently: nothing requires them to match.
|
||||||
|
|
||||||
|
## The Assembler That Runs On SplitBit:
|
||||||
|
|
||||||
|
There are two assemblers now. This manual has been describing the one that runs on a host and writes a file; `Programs/CosmOS/Assembler/` holds one written in SplitBit assembly that runs on the machine itself, under CosmOS, and reads its source off a SplitBit disk.
|
||||||
|
|
||||||
|
```
|
||||||
|
> load Asm.sbx
|
||||||
|
> run hello.asm
|
||||||
|
wrote hello.bin: program 17, data 14, labels 2
|
||||||
|
```
|
||||||
|
|
||||||
|
Loading and running are separate commands in CosmOS, so the source file is the argument to `run`.
|
||||||
|
|
||||||
|
**Its output must be byte for byte what the host assembler produces from the same source**, and `Tests/native.sh` checks exactly that: it assembles `Programs/hello.asm` both ways and compares the files, then runs the one the machine built. This is the discipline SplitDisk and `sbfs.asm` already work under — two implementations of one written specification, each one checking the other. "It ran" is not good enough for an assembler, because a binary with a label one byte out runs right up until it jumps into the middle of an instruction.
|
||||||
|
|
||||||
|
### How It Differs Inside:
|
||||||
|
|
||||||
|
The host assembler reads every token of every file into one array and works on that. **That design cannot port and never could**: `cosmos.asm` alone is 56,047 bytes of source against 64K of Data Memory, and its token array would be several times that. So the native one streams its source through a 256 byte window, twice, and keeps only the label table between the passes.
|
||||||
|
|
||||||
|
Two passes are enough because **every length is known without resolving anything**. How many bytes a token comes to falls out of what the token is — an instruction's from its shape, a value's is one, a string's is its characters and a zero — and never from the value of anything named. So the first pass works out exactly where every label lands 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.
|
||||||
|
|
||||||
|
One thing is genuinely easier here than on a host. The host assembler searches a list of include directories, because a host has directories; **SBFS is flat**, so an include is a file name and there is nowhere else to look.
|
||||||
|
|
||||||
|
### Building Applications:
|
||||||
|
|
||||||
|
`#Include` splices another file in where it stands, so the reader is a stack of readers: the current file's whole state goes aside, the new one opens, and the end of it pops the old one back. A file is included **once** — including it twice is not an error, it just does nothing, which is what lets two libraries depend on a third.
|
||||||
|
|
||||||
|
`#Base` says where a segment is loaded, and a program that says so gets the SBEX loadable header instead of the SPBT boot one, with a `.sbx` name rather than a `.bin`. `#Reserve` and `#Align` lay down runs of zeroes; how many an `#Align` comes to depends on where the cursor has reached, which is why both passes keep a cursor rather than the second one keeping only a write pointer.
|
||||||
|
|
||||||
|
Names in `#Vectors` are read and numbered, pinned where the source pins them, so `SWI osPrintString` resolves. **What a name after `SWI` means is settled by what it follows**, not by anything about the name — the Vector Segment may live in a file included further down and may not have been read yet.
|
||||||
|
|
||||||
|
That is everything an application needs:
|
||||||
|
|
||||||
|
```
|
||||||
|
> 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Programs That Bring Vectors:
|
||||||
|
|
||||||
|
A `#Vectors` line that names a **handler** says this program implements that vector, and the file then carries a Vector Segment: four bytes an entry, the address of the slot and the address to put in it, after the code and the data so that everything before them sits where a loader knowing nothing about vectors already expects it. A loadable program carrying any says **version two**, and a loader that cannot install them refuses it rather than running the program without its handlers.
|
||||||
|
|
||||||
|
```
|
||||||
|
#Vectors
|
||||||
|
|
||||||
|
Boot start
|
||||||
|
Device 0x00 keyHandler
|
||||||
|
```
|
||||||
|
|
||||||
|
`Boot` in a loadable program fills the **entry** field rather than being installed. Vector zero is where the whole machine starts, and a program being loaded into a running system has no business saying anything about that. A boot image is the one thing that does, so there it is installed like any other.
|
||||||
|
|
||||||
|
`Device` is named by the port it is plugged into, because that is what decides which vector it arrives through. `Device`, `Boot`, `SoftReset`, `BadOpcode`, `GuardViolation` and `BankFault` are matched **without regard to case**, the way mnemonics are: they are part of the language rather than names the programmer chose.
|
||||||
|
|
||||||
|
**A declaration and an implementation are the same entry.** `services.asm` says a service is called `osPrintString` and has number 16; `cosmos.asm` says `osPrintString` is handled by `handlePrintString`. Both sides include the first file, so the name is met twice and the second time fills in the handler. That is what lets one shared file serve both a program that calls a service and the system that implements it — and it is why the first pass declares and the second implements, a handler being an address and no address being known until every label has been placed.
|
||||||
|
|
||||||
|
### Self Hosting:
|
||||||
|
|
||||||
|
It assembles CosmOS, and it assembles itself.
|
||||||
|
|
||||||
|
```
|
||||||
|
> load Asm.sbx
|
||||||
|
> run cosmos.asm
|
||||||
|
wrote cosmos.bin: program 7036, data 2448, labels 475
|
||||||
|
> run Asm.asm
|
||||||
|
wrote Asm.sbx: program 7533, data 4099, labels 555
|
||||||
|
```
|
||||||
|
|
||||||
|
Both come out **byte for byte identical** to what the host assembler builds from the same source. `make run-cosmos` puts every source file on the disk, so this can be done rather than read about.
|
||||||
|
|
||||||
|
**The check that matters most is the third one.** A binary that matches could still have been built by an assembler wrong in some way this particular source happens not to exercise. So `Tests/native.sh` boots the CosmOS that CosmOS built and has *that* assemble CosmOS again — and the second generation is identical to the first, down to the cycle count. It is a fixed point, which means the machinery has been through itself.
|
||||||
|
|
||||||
|
After that the host is a convenience rather than a necessity.
|
||||||
|
|
||||||
|
CosmOS takes about 80 million cycles, which is eighty seconds of emulated time and under a second under `--fast`. Most of that is the label table: a straight walk of 475 names, several thousand times. Sorting it or bucketing it on the first character are both easy, and neither was worth writing before there was something to measure.
|
||||||
|
|
||||||
|
**The hardest thing it assembles is not the operating system, it is itself** — 555 labels and 6,770 bytes of name against CosmOS's 475 and 5,881, and a larger output. That is what the buffer sizes are cut to.
|
||||||
|
|
||||||
|
### What It Does Not Do Yet:
|
||||||
|
|
||||||
|
Nothing in the language. Every directive this manual describes is understood, and every one of them is checked against the host assembler byte for byte on every test run.
|
||||||
|
|||||||
@@ -559,7 +559,10 @@ Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, w
|
|||||||
| osFileSave | DP0 names a file, DP1 is the bytes, A and B together are how many. Q is zero if it saved, whether or not it was there before. |
|
| osFileSave | DP0 names a file, DP1 is the bytes, A and B together are how many. Q is zero if it saved, whether or not it was there before. |
|
||||||
| osFileDelete | DP0 names a file. Q is zero if it went. |
|
| osFileDelete | DP0 names a file. Q is zero if it went. |
|
||||||
| osFileRename | DP0 is the name a file has, DP1 the name it should have. Q is zero if it moved. |
|
| osFileRename | DP0 is the name a file has, DP1 the name it should have. Q is zero if it moved. |
|
||||||
|
| osFileInfo | DP0 names a file. Q is zero if it is there, and DP3 comes back holding how many blocks it occupies. |
|
||||||
|
| osFileBlock | DP0 names a file, DP1 says where to put a block of it, A and B together are which block counting from zero. Q is zero if it read, and DP3 comes back holding how many of the block's bytes belong to the file. |
|
||||||
| osPrintNumber | A and B together are a number. Prints it in decimal, without leading zeroes. |
|
| osPrintNumber | A and B together are a number. Prints it in decimal, without leading zeroes. |
|
||||||
|
| osBreak | Stops the program, shows every register as it had them, waits for a key, and carries on. |
|
||||||
|
|
||||||
```
|
```
|
||||||
#Include services.asm
|
#Include services.asm
|
||||||
@@ -568,6 +571,27 @@ Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, w
|
|||||||
SWI osPrintString
|
SWI osPrintString
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Stopping To Look:
|
||||||
|
|
||||||
|
`SWI osBreak` is a breakpoint. It shows every register as the program had them, waits for a key, and returns as though nothing happened.
|
||||||
|
|
||||||
|
```
|
||||||
|
break at 200E
|
||||||
|
A 11 B 22 Q 00 status 00
|
||||||
|
DP0 1030 DP1 05EF DP2 039A DP3 2000 SP FFFF
|
||||||
|
press a key
|
||||||
|
```
|
||||||
|
|
||||||
|
Every value comes out of the interrupt frame rather than out of the registers, because by the time the handler runs the registers belong to the handler. The frame is what the program had and what RETI is about to give back, so what is shown is what will be resumed with. The address is two before where it resumes: the `SWI` and the vector it names.
|
||||||
|
|
||||||
|
**The Stack Pointer is the exception, because it is not in the frame** — the frame is *where* the Stack Pointer is. What the program had is fourteen bytes above the frame, that being what entering an interrupt puts down, so it is worked out rather than read. Breaking inside a subroutine shows it ten lower than breaking outside one, which is the size of a CALL frame and a quick way to see how deep you are.
|
||||||
|
|
||||||
|
The status byte is shown as a number and then as the bits that are up — `carry`, `fault`, `interrupts` — because a dump that makes you look the number up is only half a dump.
|
||||||
|
|
||||||
|
**Nothing is overwritten, and that is the whole of why it is simple.** A breakpoint poked into a running program has to replace an instruction, and putting that instruction back in order to continue is the same act as disarming the breakpoint. Firing a second time would mean stepping over the restored instruction and putting the breakpoint back behind it, and this machine has no way to step a single instruction. Two bytes of `SWI` cost a little space and fire for ever, because there was never anything to restore.
|
||||||
|
|
||||||
|
The price is that a breakpoint is part of the program. A build with breakpoints in it has different addresses from a build without — the same bargain every machine makes that has a break instruction.
|
||||||
|
|
||||||
### The Disk Without A Filesystem:
|
### The Disk Without A Filesystem:
|
||||||
|
|
||||||
A program that wants a file does not need to know what a filesystem is. Before these existed it had to include the whole of `sbfs.asm` — two and a half kilobytes of a private copy of code the system already had running — and then mount a disk that was already mounted.
|
A program that wants a file does not need to know what a filesystem is. Before these existed it had to include the whole of `sbfs.asm` — two and a half kilobytes of a private copy of code the system already had running — and then mount a disk that was already mounted.
|
||||||
@@ -578,6 +602,49 @@ Sizes fit the registers exactly, in both directions. A file that can be read int
|
|||||||
|
|
||||||
A file of 256 blocks or more is refused by `osFileRead` rather than partly read, because 64K will not fit in Data Memory and its length will not fit in the pointer that reports it. A length that lies would be worse than a file that will not open.
|
A file of 256 blocks or more is refused by `osFileRead` rather than partly read, because 64K will not fit in Data Memory and its length will not fit in the pointer that reports it. A length that lies would be worse than a file that will not open.
|
||||||
|
|
||||||
|
### Reading A File That Will Not Fit:
|
||||||
|
|
||||||
|
`osFileRead` hands over a whole file, which settles the question for anything under 64K and settles nothing above it. CosmOS's own source is above it: the sources together are a hundred kilobytes and Data Memory is sixty four. A machine that is one day going to assemble itself has to be able to read a file bigger than its memory.
|
||||||
|
|
||||||
|
So there is a second way to ask. `osFileInfo` says how big something is and `osFileBlock` hands over one block of it, and between them a program reads a file of any size through a buffer of 256 bytes.
|
||||||
|
|
||||||
|
```
|
||||||
|
SETD.0 Name
|
||||||
|
SWI osFileInfo ; DP3 is how many blocks, Q is zero if it is there.
|
||||||
|
BNQ noSuchFile
|
||||||
|
|
||||||
|
readLoop:
|
||||||
|
SETD.0 Name
|
||||||
|
SETD.1 Block
|
||||||
|
SETD.2 Index
|
||||||
|
LDA.2
|
||||||
|
INCD.2
|
||||||
|
LDB.2 ; Which block, most significant first.
|
||||||
|
SWI osFileBlock
|
||||||
|
BNQ readDone ; Three when there are no more.
|
||||||
|
... ; DP3 is how many of its bytes are the file's.
|
||||||
|
```
|
||||||
|
|
||||||
|
**There is no open and no close.** Every call names the file and says which block it wants, so nothing is held between them: a program that stops halfway leaves nothing behind, and there is no handle to run out of. The system does remember where the last file it was asked about lives, so reading four hundred blocks searches the directory once rather than four hundred times — but that is a speed and not a promise, and a caller never has to know about it.
|
||||||
|
|
||||||
|
`osFileInfo` answers in **blocks rather than bytes**, and that is forced rather than chosen. A file on a sixteen megabyte disk can be twenty four bits long and a Data Pointer holds sixteen. Blocks fit; the bytes in the last one come back from `osFileBlock` when the reader gets there.
|
||||||
|
|
||||||
|
`osFileBlock` answers a count in DP3 rather than in a register for the same kind of reason: every block but a short last one holds a whole **256** bytes, and 256 does not fit in a byte. A count that reported a full block as zero would make every reader treat the end of a file as a special case.
|
||||||
|
|
||||||
|
**These two say why when the answer is no**, which the other services do not. Everywhere else the only useful thing to do about a failure is to give up, so one value is enough. These exist to be asked questions with, and the difference between the answers is the answer:
|
||||||
|
|
||||||
|
| Q | Means |
|
||||||
|
| --- | --- |
|
||||||
|
| 0 | it worked |
|
||||||
|
| 1 | there is no disk |
|
||||||
|
| 2 | there is no file of that name |
|
||||||
|
| 3 | that block is past the end of the file |
|
||||||
|
| 4 | the disk would not read it |
|
||||||
|
|
||||||
|
Running off the end is how a reader finds out it has finished, so it gets an answer of its own rather than being reported as a disk that failed.
|
||||||
|
|
||||||
|
`Programs/CosmOS/Apps/Stream.asm` reads an 84,000 byte file through a 256 byte buffer, then reads a small file both ways — whole with `osFileRead` and streamed — and checks that the two agree.
|
||||||
|
|
||||||
`Programs/CosmOS/Apps/Files.asm` does the whole round trip — write, read, report, rename, delete — in 645 bytes, and includes nothing but the service names.
|
`Programs/CosmOS/Apps/Files.asm` does the whole round trip — write, read, report, rename, delete — in 645 bytes, and includes nothing but the service names.
|
||||||
|
|
||||||
`osArgument` is how a program is told what it is for. Everything written before it did the same thing however it was started, which is fine for a program that greets you and no use to one that edits a named document. What arrives is the whole rest of the line, spaces and all, rather than a list of words: what counts as an argument is the program's business, and handing over what was typed is the system's.
|
`osArgument` is how a program is told what it is for. Everything written before it did the same thing however it was started, which is fine for a program that greets you and no use to one that edits a named document. What arrives is the whole rest of the line, spaces and all, rather than a list of words: what counts as an argument is the program's business, and handing over what was typed is the system's.
|
||||||
@@ -668,7 +735,9 @@ Whoever does the loading keeps its own code and data below the addresses the loa
|
|||||||
| Keys | The console interrupting rather than being asked. The only one that brings a vector of its own, which is what the version two format exists for. |
|
| Keys | The console interrupting rather than being asked. The only one that brings a vector of its own, which is what the version two format exists for. |
|
||||||
| Say | Prints whatever it was told, which is the shortest thing that shows osArgument working. |
|
| Say | Prints whatever it was told, which is the shortest thing that shows osArgument working. |
|
||||||
| Files | Writes a file, reads it back, renames it and deletes it, in 645 bytes, including nothing but the service names. It is what says a program does not need a filesystem inside it. |
|
| Files | Writes a file, reads it back, renames it and deletes it, in 645 bytes, including nothing but the service names. It is what says a program does not need a filesystem inside it. |
|
||||||
|
| Break | Stops itself twice with SWI osBreak, so that the registers can be seen changing between one stop and the next. |
|
||||||
| Edit | A line editor. |
|
| Edit | A line editor. |
|
||||||
|
| Stream | Reads an 84,000 byte file through a buffer of 256, which is what says a file bigger than Data Memory can be read at all. |
|
||||||
|
|
||||||
### The Monitor:
|
### The Monitor:
|
||||||
|
|
||||||
@@ -696,10 +765,32 @@ bank 01
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `x [addr]` | Sixty-four bytes, as hex and as characters |
|
| `x [addr]` | Sixty-four bytes, as hex and as characters |
|
||||||
| `d [addr]` | Eight instructions, disassembled |
|
| `d [addr]` | Eight instructions, disassembled |
|
||||||
|
| `a addr` | Assemble instructions, until a line that is just a dot |
|
||||||
| `s addr b b …` | Put those bytes there |
|
| `s addr b b …` | Put those bytes there |
|
||||||
| `b program\|data\|n` | Which bank to look at |
|
| `b program\|data\|n` | Which bank to look at |
|
||||||
| `g addr` | Go there |
|
| `g addr` | Go there |
|
||||||
|
|
||||||
|
`a` writes the assembler's own syntax: a selector rides on the mnemonic as `LDA.0` or `LDD.0.1`, and leaving one off means Data Pointer 0 exactly as it does in a source file, so nothing learned at the monitor has to be unlearned when writing a program. Case does not matter, and the whole line is refused before anything is written, so a mistyped instruction leaves no half of itself behind.
|
||||||
|
|
||||||
|
```
|
||||||
|
* b data
|
||||||
|
* s 8100 68 65 6C 6C 6F 2C 20 74 79 70 65 64 0A 00
|
||||||
|
* b program
|
||||||
|
* a 8200
|
||||||
|
8200: SETD.0 8100
|
||||||
|
8204: SWI 10
|
||||||
|
8206: SWI 12
|
||||||
|
8208: .
|
||||||
|
* g 8200
|
||||||
|
hello, typed
|
||||||
|
```
|
||||||
|
|
||||||
|
A program and its data, both entered by hand, calling a system service and returning to the prompt they were written at. Note the two banks: instructions go into Program Memory and the string into Data Memory, because that is what a Harvard machine means and the monitor will not guess for you.
|
||||||
|
|
||||||
|
**Numbers here are hexadecimal and bare.** A source file writes `0x2000` or `0d16` because it has both and must say which; the monitor has one and says so once.
|
||||||
|
|
||||||
|
**What cannot be written is a label**, and that is the whole difference between this and the assembler proper. A label is a promise to fill an address in later, and later is what a line at a time does not have. It is also why the same instruction table serves both directions here: what `a` writes, `d` reads back, and neither can drift from the other or from the assembler they were generated from.
|
||||||
|
|
||||||
`x` and `d` share one cursor and each leaves it past what it showed, so without an address either carries on — reading through memory is one letter at a time, and you can switch between bytes and instructions without retyping where you are. `s` deliberately does not move it.
|
`x` and `d` share one cursor and each leaves it past what it showed, so without an address either carries on — reading through memory is one letter at a time, and you can switch between bytes and instructions without retyping where you are. `s` deliberately does not move it.
|
||||||
|
|
||||||
Everything else here does something; the monitor looks at what the others did. It shows memory as hex and as characters, disassembles it, writes bytes into it, and jumps to an address — all through the memory controller, which is the only thing that can reach Program Memory.
|
Everything else here does something; the monitor looks at what the others did. It shows memory as hex and as characters, disassembles it, writes bytes into it, and jumps to an address — all through the memory controller, which is the only thing that can reach Program Memory.
|
||||||
|
|||||||
+16
-7
@@ -241,21 +241,30 @@ if generated.returncode != 0:
|
|||||||
problems.append("the instruction table generator would not run")
|
problems.append("the instruction table generator would not run")
|
||||||
else:
|
else:
|
||||||
wanted = [line.rstrip() for line in generated.stdout.splitlines() if line.strip()]
|
wanted = [line.rstrip() for line in generated.stdout.splitlines() if line.strip()]
|
||||||
monitor = read("Programs/CosmOS/Source/cosmos.asm")
|
# TWO copies now, and both are checked. The monitor has one and the assembler that
|
||||||
if "\nInstructions:\n" not in monitor:
|
# runs on the machine has another, because they are separate programs and there is no
|
||||||
problems.append("the system has lost its instruction table")
|
# linker to let them share: the monitor's lives in the system's data at an address
|
||||||
else:
|
# that moves every rebuild. Duplication is the cost of having no libraries, and a
|
||||||
block = monitor.split("\nInstructions:\n")[1]
|
# check on every copy is what keeps the cost to bytes rather than to correctness.
|
||||||
|
copies = [("the system", "Programs/CosmOS/Source/cosmos.asm", "\nInstructions:\n"),
|
||||||
|
("the native assembler", "Programs/CosmOS/Assembler/table.asm",
|
||||||
|
"\nAsmInstructions:\n")]
|
||||||
|
for who, path, marker in copies:
|
||||||
|
text = read(path)
|
||||||
|
if marker not in text:
|
||||||
|
problems.append("%s has lost its instruction table" % who)
|
||||||
|
continue
|
||||||
|
block = text.split(marker)[1]
|
||||||
have = []
|
have = []
|
||||||
for line in block.splitlines():
|
for line in block.splitlines():
|
||||||
if not line.strip() or not line.startswith(" 0x"):
|
if not line.strip() or not line.startswith(" 0x"):
|
||||||
break
|
break
|
||||||
have.append(line.rstrip())
|
have.append(line.rstrip())
|
||||||
if have != wanted:
|
if have != wanted:
|
||||||
problems.append("the system's instruction table is not what the assembler's"
|
problems.append("%s's instruction table is not what the assembler's"
|
||||||
" instruction set generates: %d entries against %d, first"
|
" instruction set generates: %d entries against %d, first"
|
||||||
" difference at %s"
|
" difference at %s"
|
||||||
% (len(have), len(wanted),
|
% (who, len(have), len(wanted),
|
||||||
next((a or b for a, b in zip(have + [None] * len(wanted),
|
next((a or b for a, b in zip(have + [None] * len(wanted),
|
||||||
wanted + [None] * len(have))
|
wanted + [None] * len(have))
|
||||||
if a != b), "the end")))
|
if a != b), "the end")))
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
CosmOS
|
||||||
|
> loaded, starting at 2000
|
||||||
|
> two stops, and what the registers were at each
|
||||||
|
break at 200E
|
||||||
|
A 11 B 22 Q 00 status 00
|
||||||
|
DP0 1030 DP1 0663 DP2 039C DP3 2000 SP FFFF
|
||||||
|
press a key
|
||||||
|
break at 2023
|
||||||
|
A 44 B 55 Q 00 status 00
|
||||||
|
DP0 1000 DP1 0663 DP2 039C DP3 2000 SP FFF5
|
||||||
|
press a key
|
||||||
|
carried on to the end
|
||||||
|
finished
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -8,7 +8,7 @@ finished
|
|||||||
cd
|
cd
|
||||||
the console has been handed back
|
the console has been handed back
|
||||||
finished
|
finished
|
||||||
> > x examine, d disassemble, s set, b bank, g go, exit leaves
|
> > x examine, d disassemble, a assemble, s set, b bank, g go, exit leaves
|
||||||
* bank 00
|
* bank 00
|
||||||
* FE00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
* FE00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||||
FE10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
FE10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
CosmOS
|
CosmOS
|
||||||
> x examine, d disassemble, s set, b bank, g go, exit leaves
|
> x examine, d disassemble, a assemble, s set, b bank, g go, exit leaves
|
||||||
* b <program|data|number>
|
* b <program|data|number>
|
||||||
* there is no such bank
|
* there is no such bank
|
||||||
* loaded, starting at 2000
|
* loaded, starting at 2000
|
||||||
@@ -26,6 +26,49 @@ CosmOS
|
|||||||
0010 03 FF 08 00 00 00 00 00 01 20 01 00 00 00 00 00 ......... ......
|
0010 03 FF 08 00 00 00 00 00 01 20 01 00 00 00 00 00 ......... ......
|
||||||
0020 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
|
0020 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
|
||||||
0030 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
|
0030 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 ................
|
||||||
* Fault: The device on port 233 refused the access at Program Address 0x1359, and nothing is installed to deal with it.
|
* that bank will not be written
|
||||||
|
* bank 00
|
||||||
|
* * 8000 26 48 INIA 48
|
||||||
|
8002 D1 00 OUTA 00
|
||||||
|
8004 26 0A INIA 0A
|
||||||
|
8006 D1 00 OUTA 00
|
||||||
|
8008 18 12 SWI 12
|
||||||
|
800A 00 ADD
|
||||||
|
800B 00 ADD
|
||||||
|
800C 00 ADD
|
||||||
|
* H
|
||||||
|
finished
|
||||||
|
* 8000 26 48 INIA 48
|
||||||
|
8002 D1 00 OUTA 00
|
||||||
|
8004 26 0A INIA 0A
|
||||||
|
8006 D1 00 OUTA 00
|
||||||
|
8008 18 12 SWI 12
|
||||||
|
800A 00 ADD
|
||||||
|
800B 00 ADD
|
||||||
|
800C 00 ADD
|
||||||
|
* bank 01
|
||||||
|
* * bank 00
|
||||||
|
* 8200: 8204: 8206: 8208: 820B: no such instruction
|
||||||
|
820B: that one needs a value after it
|
||||||
|
820B: 820D: * 8200 47 00 81 00 SETD.0 8100
|
||||||
|
8204 18 10 SWI 10
|
||||||
|
8206 42 02 LDA.2
|
||||||
|
8208 4A 00 01 LDD.0.1
|
||||||
|
820B 18 12 SWI 12
|
||||||
|
820D 00 ADD
|
||||||
|
820E 00 ADD
|
||||||
|
820F 00 ADD
|
||||||
|
* hello, typed
|
||||||
|
finished
|
||||||
|
* > greet.sbx 210
|
||||||
|
hello.sbx 52
|
||||||
|
Life.sbx 1411
|
||||||
|
Snake.sbx 2175
|
||||||
|
Keys.sbx 663
|
||||||
|
Say.sbx 155
|
||||||
|
Break.sbx 132
|
||||||
|
notes.txt 21
|
||||||
|
8 files
|
||||||
|
> halted
|
||||||
Execution halted.
|
Execution halted.
|
||||||
[exit 1]
|
[exit 0]
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ Life.sbx 1411
|
|||||||
Snake.sbx 2175
|
Snake.sbx 2175
|
||||||
Keys.sbx 663
|
Keys.sbx 663
|
||||||
Say.sbx 155
|
Say.sbx 155
|
||||||
|
Break.sbx 132
|
||||||
notes.txt 21
|
notes.txt 21
|
||||||
7 files
|
8 files
|
||||||
> load what?
|
> load what?
|
||||||
> no such file
|
> no such file
|
||||||
> not a program
|
> not a program
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
CosmOS
|
||||||
|
> loaded, starting at 2000
|
||||||
|
> ; This is a basic hello world program for the SplitBit CPU.
|
||||||
|
; We'll create a loop that outputs each byte of our string to Output 0, the text console.
|
||||||
|
|
||||||
|
#Program
|
||||||
|
|
||||||
|
Start:
|
||||||
|
LDA ; Load a byte of the string into A.
|
||||||
|
BRA End ; If A is zero, branch out of the loop.
|
||||||
|
OUTA 0x00 ; Output the value in A to Port 0, the text console.
|
||||||
|
INCD ; Increment the Data Pointer to the next byte of the string.
|
||||||
|
BRI Start ; Branch immediately to the start of the loop.
|
||||||
|
|
||||||
|
End:
|
||||||
|
INIA 0x0A ; We'll load a linefeed into A and output it to make it look nice.
|
||||||
|
OUTA 0x00 ; Output it to the text console.
|
||||||
|
HALT ; Terminate the program.
|
||||||
|
|
||||||
|
#Data
|
||||||
|
|
||||||
|
"Hello, World!"
|
||||||
|
---- lines: 21
|
||||||
|
finished
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
CosmOS
|
||||||
|
> loaded, starting at 2000
|
||||||
|
> big.txt is 329 blocks
|
||||||
|
read 329 blocks, checksum 57368, stopped with 3
|
||||||
|
small.txt streamed is 15216, read whole is 15216
|
||||||
|
the same
|
||||||
|
the same block twice with another file between: the same
|
||||||
|
renamed small.txt
|
||||||
|
the old name now answers 2
|
||||||
|
the new name answers 0
|
||||||
|
a name that was never there answers 2
|
||||||
|
a block past the end answers 3
|
||||||
|
finished
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
CosmOS
|
||||||
|
> loaded, starting at 2000
|
||||||
|
> 4 keyword 0 [#Program]
|
||||||
|
6 label: 0 [Start:]
|
||||||
|
7 instr 2 [LDA]
|
||||||
|
8 instr 1 [BRA]
|
||||||
|
8 label 2 [End]
|
||||||
|
9 instr 1 [OUTA]
|
||||||
|
9 value 1 [0x00]
|
||||||
|
10 instr 2 [INCD]
|
||||||
|
11 instr 1 [BRI]
|
||||||
|
11 label 2 [Start]
|
||||||
|
13 label: 0 [End:]
|
||||||
|
14 instr 1 [INIA]
|
||||||
|
14 value 1 [0x0A]
|
||||||
|
15 instr 1 [OUTA]
|
||||||
|
15 value 1 [0x00]
|
||||||
|
16 instr 1 [HALT]
|
||||||
|
18 keyword 0 [#Data]
|
||||||
|
20 string 14 [Hello, World!]
|
||||||
|
---- no more tokens
|
||||||
|
finished
|
||||||
|
> halted
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#Program #Data #Include #Vectors #Base #Align #Reserve
|
||||||
|
and a label after them still points at itself
|
||||||
|
Execution halted.
|
||||||
|
[exit 0]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
load Break.sbx
|
||||||
|
run
|
||||||
|
|
||||||
|
|
||||||
|
exit
|
||||||
@@ -9,10 +9,26 @@ b data
|
|||||||
x 1000
|
x 1000
|
||||||
b 2
|
b 2
|
||||||
x 0
|
x 0
|
||||||
|
s 0 FF
|
||||||
|
b program
|
||||||
s 8000 26 48 D1 00 26 0A D1 00 18 12
|
s 8000 26 48 D1 00 26 0A D1 00 18 12
|
||||||
d 8000
|
d 8000
|
||||||
g 8000
|
g 8000
|
||||||
d 8000
|
d 8000
|
||||||
|
b data
|
||||||
|
s 8100 68 65 6C 6C 6F 2C 20 74 79 70 65 64 0A 00
|
||||||
|
b program
|
||||||
|
a 8200
|
||||||
|
SETD.0 8100
|
||||||
|
SWI 10
|
||||||
|
lda.2
|
||||||
|
LDD.0.1
|
||||||
|
frobnicate
|
||||||
|
INIA
|
||||||
|
SWI 12
|
||||||
|
.
|
||||||
|
d 8200
|
||||||
|
g 8200
|
||||||
exit
|
exit
|
||||||
dir
|
dir
|
||||||
exit
|
exit
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
load readTest.sbx
|
||||||
|
run hello.asm
|
||||||
|
exit
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
load Stream.sbx
|
||||||
|
run
|
||||||
|
exit
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
load tokenTest.sbx
|
||||||
|
run hello.asm
|
||||||
|
exit
|
||||||
+46
-1
@@ -59,7 +59,11 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
|
|||||||
# to the hardware, so something has to check that one still gives the machine back.
|
# to the hardware, so something has to check that one still gives the machine back.
|
||||||
#
|
#
|
||||||
# notes.txt is there so that loading something that is not a program can be tried too.
|
# notes.txt is there so that loading something that is not a program can be tried too.
|
||||||
"$TOOL" format "$DISKS/cosmos.img" 256 2 >/dev/null
|
# 512K, and four directory blocks for 32 files. It was 64K and 16, which was ample for a
|
||||||
|
# disk that held programs and nothing else - but the native assembler reads SOURCE from
|
||||||
|
# here, and the CosmOS sources alone are 104,142 bytes against the 65,536 a 256 block disk
|
||||||
|
# holds. A machine that is going to assemble itself has to be able to hold its own source.
|
||||||
|
"$TOOL" format "$DISKS/cosmos.img" 2048 4 >/dev/null
|
||||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
"$ROOT/Programs/CosmOS/Apps/greet.asm" -o "$WORK/greet.sbx" >/dev/null
|
"$ROOT/Programs/CosmOS/Apps/greet.asm" -o "$WORK/greet.sbx" >/dev/null
|
||||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/greet.sbx" >/dev/null
|
"$TOOL" put "$DISKS/cosmos.img" "$WORK/greet.sbx" >/dev/null
|
||||||
@@ -91,6 +95,11 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/
|
|||||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
|
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
|
||||||
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" >/dev/null
|
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" >/dev/null
|
||||||
|
# Break.sbx stops itself twice and shows what the registers were each time. It needs no disk
|
||||||
|
# of its own: it only prints.
|
||||||
|
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
|
"$ROOT/Programs/CosmOS/Apps/Break.asm" -o "$WORK/Break.sbx" >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Break.sbx" >/dev/null
|
||||||
printf 'this is not a program' > notes.txt
|
printf 'this is not a program' > notes.txt
|
||||||
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
|
"$TOOL" put "$DISKS/cosmos.img" notes.txt >/dev/null
|
||||||
|
|
||||||
@@ -133,3 +142,39 @@ printf 'the second one' > two.txt
|
|||||||
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
"$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/Files.sbx" >/dev/null
|
"$ROOT/Programs/CosmOS/Apps/Files.asm" -o "$WORK/Files.sbx" >/dev/null
|
||||||
"$TOOL" put "$DISKS/services.img" "$WORK/Files.sbx" >/dev/null
|
"$TOOL" put "$DISKS/services.img" "$WORK/Files.sbx" >/dev/null
|
||||||
|
|
||||||
|
# A disk for streaming, and the only one here that has to be big: the file on it is bigger
|
||||||
|
# than the machine's Data Memory, which is the entire point of the services it checks.
|
||||||
|
#
|
||||||
|
# THE FILE IS GENERATED RATHER THAN TAKEN FROM THE REPOSITORY. Concatenating the CosmOS
|
||||||
|
# sources would be a truer picture of what streaming is for, and would change the recorded
|
||||||
|
# checksum every time a line of CosmOS was edited - so a real difference would arrive in a
|
||||||
|
# crowd of meaningless ones, which is the same trap the cycle counts used to set. This is
|
||||||
|
# 84000 bytes, which is 328 whole blocks and 32 bytes over, so the short block at the end
|
||||||
|
# is exercised rather than assumed.
|
||||||
|
"$TOOL" format "$DISKS/stream.img" 1024 2 >/dev/null
|
||||||
|
awk 'BEGIN { for (i = 0; i < 4000; i++) printf "streaming line %05d\n", i }' > big.txt
|
||||||
|
awk 'BEGIN { for (i = 0; i < 50; i++) printf "small %05d\n", i }' > small.txt
|
||||||
|
"$TOOL" put "$DISKS/stream.img" big.txt >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/stream.img" small.txt >/dev/null
|
||||||
|
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
|
"$ROOT/Programs/CosmOS/Apps/Stream.asm" -o "$WORK/Stream.sbx" >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/stream.img" "$WORK/Stream.sbx" >/dev/null
|
||||||
|
|
||||||
|
# A disk for the assembler that runs on the machine. It holds a source file and the two
|
||||||
|
# programs that check the front end - the reader on its own and the tokenizer on its own -
|
||||||
|
# because a fault in either of those would otherwise turn up much later as a mysterious
|
||||||
|
# wrong byte in an output file.
|
||||||
|
#
|
||||||
|
# hello.asm is here rather than something written for the occasion because it is the
|
||||||
|
# oldest program in the repository: the first thing this machine ever ran is the first
|
||||||
|
# thing it assembles for itself.
|
||||||
|
"$TOOL" format "$DISKS/asm.img" 2048 4 >/dev/null
|
||||||
|
cp "$ROOT/Programs/hello.asm" hello.asm
|
||||||
|
"$TOOL" put "$DISKS/asm.img" hello.asm >/dev/null
|
||||||
|
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \
|
||||||
|
"$ROOT/Programs/CosmOS/Assembler/readTest.asm" -o "$WORK/readTest.sbx" >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/asm.img" "$WORK/readTest.sbx" >/dev/null
|
||||||
|
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \
|
||||||
|
"$ROOT/Programs/CosmOS/Assembler/tokenTest.asm" -o "$WORK/tokenTest.sbx" >/dev/null
|
||||||
|
"$TOOL" put "$DISKS/asm.img" "$WORK/tokenTest.sbx" >/dev/null
|
||||||
|
|||||||
@@ -221,6 +221,16 @@ cosmosRun | CosmOS/Source/cosmos.asm | run | cosmosRun
|
|||||||
# The last part is what the mode is FOR: a program typed in as bytes, run with g, and the
|
# The last part is what the mode is FOR: a program typed in as bytes, run with g, and the
|
||||||
# prompt that comes back is the monitor's own. A program giving the machine back lands where
|
# prompt that comes back is the monitor's own. A program giving the machine back lands where
|
||||||
# it was started from, so looking at something and running it do not interrupt each other.
|
# it was started from, so looking at something and running it do not interrupt each other.
|
||||||
|
#
|
||||||
|
# It also asks to write into bank 2, the controller's own table, which is published read
|
||||||
|
# only. That used to stop the machine, and the recorded output of this test contained the
|
||||||
|
# crash without anybody noticing, which is what blessing a result without reading it buys.
|
||||||
|
#
|
||||||
|
# It ends with a whole program typed in: a string poked into Data Memory, instructions
|
||||||
|
# assembled into Program Memory, and the result run - which prints something no assembler
|
||||||
|
# ever saw. The mnemonics deliberately include a lower case one, both selector forms, an
|
||||||
|
# instruction that does not exist and one missing its value, so that the refusals are
|
||||||
|
# recorded next to the successes and a failed line is shown writing nothing.
|
||||||
cosmosMonitor | CosmOS/Source/cosmos.asm | run | cosmosMonitor.in | - | disks/cosmos.img
|
cosmosMonitor | CosmOS/Source/cosmos.asm | run | cosmosMonitor.in | - | disks/cosmos.img
|
||||||
# The original hello.asm, brought over as an application. It is not much of a program,
|
# The original hello.asm, brought over as an application. It is not much of a program,
|
||||||
# but it is the one that talks to the hardware directly: it writes to port 0x00 instead
|
# but it is the one that talks to the hardware directly: it writes to port 0x00 instead
|
||||||
@@ -270,6 +280,13 @@ cosmosFiles | CosmOS/Source/cosmos.asm | run | cosmosFil
|
|||||||
# name, and with several words, since what arrives is the rest of the line rather than a
|
# name, and with several words, since what arrives is the rest of the line rather than a
|
||||||
# list and it is the program's business what to make of it.
|
# list and it is the program's business what to make of it.
|
||||||
cosmosSay | CosmOS/Source/cosmos.asm | run | cosmosSay.in | - | disks/cosmos.img
|
cosmosSay | CosmOS/Source/cosmos.asm | run | cosmosSay.in | - | disks/cosmos.img
|
||||||
|
# A program stopping itself to be looked at. Two breakpoints, so what is checked is not only
|
||||||
|
# that one fires but that the SECOND one does - which is the whole difference between this
|
||||||
|
# design and one that overwrites an instruction, since an overwritten instruction has to be
|
||||||
|
# put back to continue and putting it back is the same act as disarming the breakpoint.
|
||||||
|
# Every value shown comes out of the interrupt frame rather than the registers, because by
|
||||||
|
# the time the handler runs the registers are the handler's.
|
||||||
|
cosmosBreak | CosmOS/Source/cosmos.asm | run | cosmosBreak.in | - | disks/cosmos.img
|
||||||
# The editor, which is the first program on this machine that makes a file a person typed:
|
# The editor, which is the first program on this machine that makes a file a person typed:
|
||||||
# every byte on every other image here was put there by the host tool. It is run twice in
|
# every byte on every other image here was put there by the host tool. It is run twice in
|
||||||
# one session, and that is the test rather than a flourish - the second run reads back what
|
# one session, and that is the test rather than a flourish - the second run reads back what
|
||||||
@@ -283,6 +300,34 @@ cosmosEdit | CosmOS/Source/cosmos.asm | run | cosmosEdi
|
|||||||
# with files and carries the filesystem inside it. That difference is the whole case for the
|
# with files and carries the filesystem inside it. That difference is the whole case for the
|
||||||
# service layer, and this is where it is checked rather than argued.
|
# service layer, and this is where it is checked rather than argued.
|
||||||
cosmosServices | CosmOS/Source/cosmos.asm | run | cosmosFiles2.in | - | disks/services.img
|
cosmosServices | CosmOS/Source/cosmos.asm | run | cosmosFiles2.in | - | disks/services.img
|
||||||
|
# Streaming, which is what a file bigger than memory needs. The disk here holds 84000
|
||||||
|
# bytes in one file and the machine has 64K of Data Memory, so there is no arrangement of
|
||||||
|
# osFileRead that could get at it: the program reads it through a buffer of one block.
|
||||||
|
#
|
||||||
|
# THE CHECK THAT MATTERS IS THE SECOND ONE. A small file is read both ways - whole with
|
||||||
|
# osFileRead, and streamed - and the two checksums have to agree, so streaming is measured
|
||||||
|
# against the path that already worked rather than against a number somebody wrote down.
|
||||||
|
# The checksum is Fletcher's rather than a sum, because a sum is the same whatever order
|
||||||
|
# the bytes arrived in and the order is exactly what streaming has to get right.
|
||||||
|
#
|
||||||
|
# The rest of it is the lookup the system keeps so that reading four hundred blocks of one
|
||||||
|
# file does not search the directory four hundred times. Two files read alternately catch a
|
||||||
|
# memory that does not notice the name changed, and a rename catches one that does not
|
||||||
|
# notice the file moved - and that one would otherwise pass, since the blocks are still
|
||||||
|
# there and still hold the same bytes.
|
||||||
|
cosmosStream | CosmOS/Source/cosmos.asm | run | cosmosStream.in | - | disks/stream.img
|
||||||
|
# The assembler's front end, checked in two pieces before anything is built on it.
|
||||||
|
#
|
||||||
|
# cosmosSource reads a source file and prints it back. The file crosses a block boundary,
|
||||||
|
# so the seam is exercised rather than assumed, and the line count at the end says the
|
||||||
|
# reader knows where it is as well as what it holds.
|
||||||
|
#
|
||||||
|
# cosmosTokens says what each token IS. That is the part worth checking here rather than at
|
||||||
|
# the far end: a wrong classification does not produce a wrong byte somewhere obvious, it
|
||||||
|
# produces a right looking program of the wrong length with everything after it shifted,
|
||||||
|
# and by then the only symptom is a label pointing into the middle of an instruction.
|
||||||
|
cosmosSource | CosmOS/Source/cosmos.asm | run | cosmosSource.in | - | disks/asm.img
|
||||||
|
cosmosTokens | CosmOS/Source/cosmos.asm | run | cosmosTokens.in | - | disks/asm.img
|
||||||
# The programs CosmOS loads, checked on their own so that a failure here reads as "the app
|
# The programs CosmOS loads, checked on their own so that a failure here reads as "the app
|
||||||
# does not assemble" rather than as a broken disk image.
|
# does not assemble" rather than as a broken disk image.
|
||||||
app-greet | CosmOS/Apps/greet.asm | assemble | - | -
|
app-greet | CosmOS/Apps/greet.asm | assemble | - | -
|
||||||
@@ -291,8 +336,16 @@ app-Life | CosmOS/Apps/Life.asm | assemble | -
|
|||||||
app-Snake | CosmOS/Apps/Snake.asm | assemble | - | -
|
app-Snake | CosmOS/Apps/Snake.asm | assemble | - | -
|
||||||
app-Keys | CosmOS/Apps/Keys.asm | assemble | - | -
|
app-Keys | CosmOS/Apps/Keys.asm | assemble | - | -
|
||||||
app-Say | CosmOS/Apps/Say.asm | assemble | - | -
|
app-Say | CosmOS/Apps/Say.asm | assemble | - | -
|
||||||
|
app-Break | CosmOS/Apps/Break.asm | assemble | - | -
|
||||||
app-Edit | CosmOS/Apps/Edit.asm | assemble | - | -
|
app-Edit | CosmOS/Apps/Edit.asm | assemble | - | -
|
||||||
app-Files | CosmOS/Apps/Files.asm | assemble | - | -
|
app-Files | CosmOS/Apps/Files.asm | assemble | - | -
|
||||||
|
app-Stream | CosmOS/Apps/Stream.asm | assemble | - | -
|
||||||
|
# The assembler that runs on the machine, and its parts. Checked on their own so that a
|
||||||
|
# failure reads as "it does not assemble" rather than as a broken disk image.
|
||||||
|
asm-Asm | CosmOS/Assembler/Asm.asm | assemble | - | -
|
||||||
|
asm-readTest | CosmOS/Assembler/readTest.asm | assemble | - | -
|
||||||
|
asm-tokenTest | CosmOS/Assembler/tokenTest.asm | assemble | - | -
|
||||||
|
asm-scratch | CosmOS/Assembler/scratch.asm | assemble | - | -
|
||||||
|
|
||||||
# ---- Programs driven by console input ----
|
# ---- Programs driven by console input ----
|
||||||
inputTest | inputTest.asm | run | inputTest.in | -
|
inputTest | inputTest.asm | run | inputTest.in | -
|
||||||
@@ -352,6 +405,11 @@ diagUnbasedSegment | testPrograms/diagnostics/unbasedSegment.asm | xfail | -
|
|||||||
lib-print | Libraries/print.asm | xfail | - | -
|
lib-print | Libraries/print.asm | xfail | - | -
|
||||||
# These three call print.asm routines but have no #Include line at all. They are
|
# These three call print.asm routines but have no #Include line at all. They are
|
||||||
# only ever pulled in by printTest.asm, so they are not standalone programs.
|
# only ever pulled in by printTest.asm, so they are not standalone programs.
|
||||||
|
# A string that spells a directive is a string. The quotes are gone by the time a token is
|
||||||
|
# looked at, so "#Program" in a program's data was read as the directive: the segment
|
||||||
|
# changed in the middle of the Data Segment and every label after it came out nine bytes
|
||||||
|
# wrong, in a file that still had a valid header and a plausible length.
|
||||||
|
stringKeyword | testPrograms/stringKeyword.asm | run | - | -
|
||||||
printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | -
|
printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | -
|
||||||
printDigitTest | testPrograms/printDigitTest.asm | xfail | - | -
|
printDigitTest | testPrograms/printDigitTest.asm | xfail | - | -
|
||||||
printHexTest | testPrograms/printHexTest.asm | xfail | - | -
|
printHexTest | testPrograms/printHexTest.asm | xfail | - | -
|
||||||
|
|||||||
Executable
+184
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Checks the assembler that runs on SplitBit against the one that runs on the host.
|
||||||
|
#
|
||||||
|
# THE ONLY HONEST TEST OF AN ASSEMBLER IS THE BYTES IT PRODUCES. "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 this assembles the same source both ways and
|
||||||
|
# compares the two files byte for byte, the same discipline SplitDisk and sbfs.asm work
|
||||||
|
# under: two implementations of one written specification, each one checking the other.
|
||||||
|
#
|
||||||
|
# Then it runs what the machine built, because a file that matches and does not work would
|
||||||
|
# mean both assemblers were wrong together.
|
||||||
|
#
|
||||||
|
# It checks a boot image and four loadable programs. The loadable ones are the harder case:
|
||||||
|
# they include another file, they are based somewhere other than zero, and every service
|
||||||
|
# they call is a name declared in that included file. The last of them also brings a vector,
|
||||||
|
# which is the only thing here that asks anything of its loader.
|
||||||
|
#
|
||||||
|
# Written by Anachronaut
|
||||||
|
|
||||||
|
set -u
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
WORK="$ROOT/Tests/build/native"
|
||||||
|
ASM="$ROOT/Assembler"
|
||||||
|
TOOL="$ROOT/SplitDisk"
|
||||||
|
EMU="$ROOT/SplitBit"
|
||||||
|
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
FAILED_NAMES=()
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local name="$1"
|
||||||
|
shift
|
||||||
|
if "$@"; then
|
||||||
|
printf ' [ok ] %-22s %s\n' "$name" "${REPORT:-}"
|
||||||
|
PASS=$((PASS + 1))
|
||||||
|
else
|
||||||
|
printf ' [FAIL] %-22s %s\n' "$name" "${REPORT:-}"
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
FAILED_NAMES+=("$name")
|
||||||
|
fi
|
||||||
|
REPORT=""
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool in "$ASM" "$TOOL" "$EMU"; do
|
||||||
|
[ -x "$tool" ] || { echo "$(basename "$tool") is not built."; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -rf "$WORK"
|
||||||
|
mkdir -p "$WORK"
|
||||||
|
|
||||||
|
# The system it runs under, and the assembler itself, both built by the host assembler.
|
||||||
|
# Bootstrapping has to start somewhere, and this is the last place it does.
|
||||||
|
"$ASM" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
|
-o "$WORK/cosmos.bin" "$ROOT/Programs/CosmOS/Source/cosmos.asm" >/dev/null || exit 1
|
||||||
|
"$ASM" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \
|
||||||
|
-o "$WORK/Asm.sbx" "$ROOT/Programs/CosmOS/Assembler/Asm.asm" >/dev/null || exit 1
|
||||||
|
|
||||||
|
"$TOOL" format "$WORK/native.img" 2048 4 >/dev/null
|
||||||
|
"$TOOL" put "$WORK/native.img" "$ROOT/Programs/hello.asm" hello.asm >/dev/null
|
||||||
|
"$TOOL" put "$WORK/native.img" "$WORK/Asm.sbx" Asm.sbx >/dev/null
|
||||||
|
|
||||||
|
# The applications, and the file of service names they all include. These are the reason
|
||||||
|
# the second milestone exists: an assembler that cannot follow an #Include cannot build
|
||||||
|
# anything that asks the system for anything.
|
||||||
|
#
|
||||||
|
# Keys is the last of them and the hardest: it brings a vector of its own, so it needs a
|
||||||
|
# Vector Segment in the output and the version two header that says so. Nothing else here
|
||||||
|
# asks anything of its loader beyond code and data.
|
||||||
|
APPS="Say greet Files Keys"
|
||||||
|
"$TOOL" put "$WORK/native.img" "$ROOT/Programs/CosmOS/Source/services.asm" services.asm >/dev/null
|
||||||
|
"$TOOL" put "$WORK/native.img" "$ROOT/Programs/CosmOS/Source/console.asm" console.asm >/dev/null
|
||||||
|
for app in $APPS; do
|
||||||
|
"$TOOL" put "$WORK/native.img" "$ROOT/Programs/CosmOS/Apps/$app.asm" "$app.asm" >/dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "load Asm.sbx"
|
||||||
|
echo "run hello.asm"
|
||||||
|
for app in $APPS; do echo "run $app.asm"; done
|
||||||
|
echo "exit"
|
||||||
|
} | "$EMU" --fast --cycles 600000000 -D "$WORK/native.img" "$WORK/cosmos.bin" \
|
||||||
|
> "$WORK/session.txt" 2>&1
|
||||||
|
|
||||||
|
# ---- It got as far as writing something ----
|
||||||
|
check "it wrote a file" grep -q "wrote hello.bin" "$WORK/session.txt"
|
||||||
|
|
||||||
|
# ---- And the file is the one the host assembler makes ----
|
||||||
|
"$ASM" -o "$WORK/reference.bin" "$ROOT/Programs/hello.asm" >/dev/null
|
||||||
|
"$TOOL" get "$WORK/native.img" hello.bin "$WORK/native.bin" >/dev/null 2>&1
|
||||||
|
if [ -f "$WORK/native.bin" ]; then
|
||||||
|
REPORT="$(wc -c < "$WORK/native.bin" | tr -d ' ') bytes"
|
||||||
|
fi
|
||||||
|
check "byte for byte" cmp -s "$WORK/native.bin" "$WORK/reference.bin"
|
||||||
|
|
||||||
|
# ---- And what it built actually runs ----
|
||||||
|
"$EMU" --fast --cycles 100000 "$WORK/native.bin" > "$WORK/ran.txt" 2>&1
|
||||||
|
check "and it runs" grep -q "^Hello, World!$" "$WORK/ran.txt"
|
||||||
|
|
||||||
|
# ---- The report it printed says what it did ----
|
||||||
|
REPORT="$(grep -o 'program [0-9]*, data [0-9]*, labels [0-9]*' "$WORK/session.txt" | head -1 || true)"
|
||||||
|
check "it counted right" grep -q "program 17, data 14, labels 2" "$WORK/session.txt"
|
||||||
|
|
||||||
|
# ---- And the applications, which need an include, a base and the service names ----
|
||||||
|
#
|
||||||
|
# A loadable program is the harder case and the one that matters: #Include splices another
|
||||||
|
# file in, #Base moves every label to where the program will really live, and every SWI in
|
||||||
|
# them names a vector declared in a file the source never mentions by number.
|
||||||
|
for app in $APPS; do
|
||||||
|
"$ASM" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
|
||||||
|
-o "$WORK/ref-$app.sbx" "$ROOT/Programs/CosmOS/Apps/$app.asm" >/dev/null
|
||||||
|
rm -f "$WORK/got-$app.sbx"
|
||||||
|
"$TOOL" get "$WORK/native.img" "$app.sbx" "$WORK/got-$app.sbx" >/dev/null 2>&1
|
||||||
|
if [ -f "$WORK/got-$app.sbx" ]; then
|
||||||
|
REPORT="$(wc -c < "$WORK/got-$app.sbx" | tr -d ' ') bytes"
|
||||||
|
fi
|
||||||
|
check "$app byte for byte" cmp -s "$WORK/got-$app.sbx" "$WORK/ref-$app.sbx"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ---- Self hosting ----
|
||||||
|
#
|
||||||
|
# The two things that make this more than a demonstration: the machine assembling the
|
||||||
|
# operating system it is running on, and the assembler assembling itself. Both have to come
|
||||||
|
# out byte for byte identical to what the host tool builds from the same source.
|
||||||
|
#
|
||||||
|
# THE FIXED POINT IS THE THIRD CHECK AND THE ONE THAT MATTERS MOST. A binary that matches
|
||||||
|
# could still have been built by an assembler that is wrong in some way the source happens
|
||||||
|
# not to exercise; a binary that BUILDS ITSELF AGAIN and comes out the same has been through
|
||||||
|
# its own machinery. The host is no longer necessary after this point - it is a convenience.
|
||||||
|
|
||||||
|
SOURCES="cosmos console sbfs services text"
|
||||||
|
PARTS="Asm numbers source token classify labels vectors table"
|
||||||
|
|
||||||
|
"$TOOL" format "$WORK/self.img" 4096 8 >/dev/null
|
||||||
|
for f in $SOURCES; do
|
||||||
|
"$TOOL" put "$WORK/self.img" "$ROOT/Programs/CosmOS/Source/$f.asm" "$f.asm" >/dev/null
|
||||||
|
done
|
||||||
|
for f in $PARTS; do
|
||||||
|
"$TOOL" put "$WORK/self.img" "$ROOT/Programs/CosmOS/Assembler/$f.asm" "$f.asm" >/dev/null
|
||||||
|
done
|
||||||
|
"$TOOL" put "$WORK/self.img" "$WORK/Asm.sbx" Asm.sbx >/dev/null
|
||||||
|
|
||||||
|
printf 'load Asm.sbx\nrun cosmos.asm\nrun Asm.asm\nexit\n' \
|
||||||
|
| "$EMU" --fast --cycles 4000000000 -D "$WORK/self.img" "$WORK/cosmos.bin" \
|
||||||
|
> "$WORK/self.txt" 2>&1
|
||||||
|
|
||||||
|
rm -f "$WORK/built-cosmos.bin" "$WORK/built-Asm.sbx"
|
||||||
|
"$TOOL" get "$WORK/self.img" cosmos.bin "$WORK/built-cosmos.bin" >/dev/null 2>&1
|
||||||
|
"$TOOL" get "$WORK/self.img" Asm.sbx "$WORK/built-Asm.sbx" >/dev/null 2>&1
|
||||||
|
|
||||||
|
REPORT="$(grep -o 'program 7036, data 2448, labels [0-9]*' "$WORK/self.txt" || true)"
|
||||||
|
check "CosmOS builds CosmOS" cmp -s "$WORK/built-cosmos.bin" "$WORK/cosmos.bin"
|
||||||
|
REPORT="$(grep -o 'program 7533, data [0-9]*, labels [0-9]*' "$WORK/self.txt" || true)"
|
||||||
|
check "and builds itself" cmp -s "$WORK/built-Asm.sbx" "$WORK/Asm.sbx"
|
||||||
|
|
||||||
|
# ---- And the second generation is the first ----
|
||||||
|
#
|
||||||
|
# Boot the CosmOS that CosmOS built, and have that assemble CosmOS again.
|
||||||
|
if [ -s "$WORK/built-cosmos.bin" ]; then
|
||||||
|
"$TOOL" delete "$WORK/self.img" cosmos.bin >/dev/null 2>&1
|
||||||
|
printf 'load Asm.sbx\nrun cosmos.asm\nexit\n' \
|
||||||
|
| "$EMU" --fast --cycles 4000000000 -D "$WORK/self.img" "$WORK/built-cosmos.bin" \
|
||||||
|
> "$WORK/second.txt" 2>&1
|
||||||
|
rm -f "$WORK/second-cosmos.bin"
|
||||||
|
"$TOOL" get "$WORK/self.img" cosmos.bin "$WORK/second-cosmos.bin" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
REPORT="a fixed point"
|
||||||
|
check "second generation" cmp -s "$WORK/second-cosmos.bin" "$WORK/built-cosmos.bin"
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [ "$FAIL" -eq 0 ]; then
|
||||||
|
echo "All $PASS native assembler checks passed."
|
||||||
|
else
|
||||||
|
echo "$PASS passed, $FAIL failed: ${FAILED_NAMES[*]}"
|
||||||
|
echo
|
||||||
|
echo "The session was:"
|
||||||
|
sed 's/^/ /' "$WORK/session.txt"
|
||||||
|
if [ -f "$WORK/self.txt" ]; then
|
||||||
|
echo
|
||||||
|
echo "And the self hosting run:"
|
||||||
|
sed 's/^/ /' "$WORK/self.txt"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -78,6 +78,8 @@ test: $(EMU_TARGET) $(ASM_TARGET) $(DSK_TARGET)
|
|||||||
@echo
|
@echo
|
||||||
@./Tests/terminal.sh
|
@./Tests/terminal.sh
|
||||||
@echo
|
@echo
|
||||||
|
@./Tests/native.sh
|
||||||
|
@echo
|
||||||
@./Tests/docs.sh
|
@./Tests/docs.sh
|
||||||
|
|
||||||
# Rebuild both tools with the address and undefined behaviour sanitizers and run
|
# Rebuild both tools with the address and undefined behaviour sanitizers and run
|
||||||
|
|||||||
Reference in New Issue
Block a user