CosmOS pre-alpha and launchable application versions of old programs.

This commit is contained in:
Anachronaut
2026-08-17 15:31:49 -04:00
parent eff6902bcf
commit 91c9d49d1b
66 changed files with 5612 additions and 160 deletions
+140
View File
@@ -0,0 +1,140 @@
; Exercises the console's status and control ports.
;
; The console answers on three ports now: 0x00 for bytes as it always did, 0x01 for what
; it is doing, and 0x02 to say which mode it should be in. The point of the mode is that
; the data port never changed, so this checks the old behaviour is still there as much as
; it checks the new behaviour arrived.
;
; What the status bits mean:
; bit 0 READY reading the data port will not have to wait
; bit 1 ENDED input has run out for good
; bit 2 KEYMODE the console is in key mode
;
; This runs with input from a file rather than a terminal, so key mode has no terminal to
; put into another state and the mode bit is the only thing that changes. That is on
; purpose: the same program has to work either way, and a test that needed a terminal
; could not run here at all.
#Include console.asm
#Program
start:
SETD.0 Banner
CALL printString
CALL newLine
; ---- What mode does it start in ----
;
; Nothing has been written to the control port, so this is how the machine comes up, and
; every program written before these ports existed runs in exactly this.
SETD.0 AtStartLabel
CALL printString
CALL showStatus
; ---- Into key mode ----
INIA 0x01
OUTA 0x02
SETD.0 KeyModeLabel
CALL printString
CALL showStatus
; ---- Read what is waiting ----
;
; Reading until ENDED rather than until a count, which is the thing that could not be
; done before: 0xFF from the data port used to mean both "the byte 0xFF" and "there is
; no more", and nothing could tell those apart.
SETD.0 ReadLabel
CALL printString
CALL newLine
readLoop:
INA 0x01
INIB 0x02 ; ENDED
AND
BNQ readDone
INA 0x00
OUTA 0x00 ; Nothing echoes in key mode, so the program does it.
BRI readLoop
readDone:
CALL newLine
; ---- The status at the end of input ----
;
; READY is set as well as ENDED, because a read does answer at once. It just answers
; 0xFF forever. ENDED is what says so.
SETD.0 AtEndLabel
CALL printString
CALL showStatus
; ---- Back to line mode ----
RSTA
OUTA 0x02
SETD.0 LineModeLabel
CALL printString
CALL showStatus
HALT
; Prints the status byte as hex and then names the bits that are up, so that a change in
; the output says which bit moved rather than only that the number is different.
showStatus:
INA 0x01
PSHA
CALL printByteHex
INIA 0x20
OUTA 0x00
POPA
PSHA
INIB 0x01
AND
BRQ showNotReady
SETD.0 ReadyWord
CALL printString
showNotReady:
POPA
PSHA
INIB 0x02
AND
BRQ showNotEnded
SETD.0 EndedWord
CALL printString
showNotEnded:
POPA
INIB 0x04
AND
BRQ showNotKeys
SETD.0 KeysWord
CALL printString
showNotKeys:
CALL newLine
RET
#Data
Banner:
"console mode ports"
AtStartLabel:
"at start: "
KeyModeLabel:
"key mode: "
AtEndLabel:
"at the end: "
LineModeLabel:
"line mode: "
ReadLabel:
"what came in:"
ReadyWord:
"ready "
EndedWord:
"ended "
KeysWord:
"keys "
#Vectors
Boot start
+201
View File
@@ -0,0 +1,201 @@
; Exercises console.asm, the modern console library.
;
; Every routine in it is called here with the cases that are easy to get wrong: a zero,
; the largest thing that fits, the boundary either side of a carry, and a leading zero
; that should not print. The reading half is driven by consoleTest.in, which ends with a
; line longer than the buffer so that the truncation and the swallowing of the rest are
; both shown rather than assumed.
;
; There is no BRI at the top of console.asm, so this program says where it begins in its
; Vector Segment. That is the whole of the arrangement the old library needed a branch for.
#Include console.asm
#Program
start:
SETD.0 Banner
CALL printString
CALL newLine
; ---- Hexadecimal ----
SETD.0 HexLabel
CALL printString
RSTA
CALL printByteHex
INIA 0x20
OUTA 0x00
INIA 0x0F
CALL printByteHex
INIA 0x20
OUTA 0x00
INIA 0xA5
CALL printByteHex
INIA 0x20
OUTA 0x00
INIA 0xFF
CALL printByteHex
CALL newLine
SETD.0 WordHexLabel
CALL printString
SETD.0 Zero
CALL printWordHex
INIA 0x20
OUTA 0x00
SETD.0 Thousand
CALL printWordHex
INIA 0x20
OUTA 0x00
SETD.0 Largest
CALL printWordHex
CALL newLine
; ---- Decimal, one byte wide ----
; Zero has to print a digit, and a hundred has to not print its tens as a blank.
SETD.0 ByteLabel
CALL printString
RSTA
CALL printByteDecimal
CALL blank
INIA 0d7
CALL printByteDecimal
CALL blank
INIA 0d42
CALL printByteDecimal
CALL blank
INIA 0d100
CALL printByteDecimal
CALL blank
INIA 0d255
CALL printByteDecimal
CALL newLine
; ---- Decimal, two bytes wide ----
; Ten thousand and one is the case that catches a place that stops counting too soon,
; and the three zeroes in the middle of it are the case that catches a leading zero
; test applied after the first digit.
SETD.0 WordLabel
CALL printString
SETD.0 Zero
CALL printWordDecimal
CALL blank
SETD.0 Nine
CALL printWordDecimal
CALL blank
SETD.0 Ten
CALL printWordDecimal
CALL blank
SETD.0 TwoFiveFive
CALL printWordDecimal
CALL blank
SETD.0 Thousand
CALL printWordDecimal
CALL blank
SETD.0 TenThousandOne
CALL printWordDecimal
CALL blank
SETD.0 Largest
CALL printWordDecimal
CALL newLine
; ---- Spaces ----
; None of them is a fair number to ask for, and has to print nothing at all.
SETD.0 SpaceLabel
CALL printString
INIA 0x7C
OUTA 0x00
RSTA
CALL printSpaces
INIA 0x7C
OUTA 0x00
INIA 0d4
CALL printSpaces
INIA 0x7C
OUTA 0x00
CALL newLine
; ---- Reading ----
SETD.0 ReadLabel
CALL printString
CALL newLine
readLoop:
SETD.0 LineBuffer
INIB 0d16
CALL readLine
SETD.0 LineLength
STQ.0
; Running out is not an empty line, and this is where the difference shows.
SETD.0 ConsoleEndOfInput
LDA.0
BNA readDone
INIA 0x5B
OUTA 0x00
SETD.0 LineBuffer
CALL printString
INIA 0x5D
OUTA 0x00
CALL blank
SETD.0 LineLength
LDA.0
CALL printByteDecimal
CALL newLine
BRI readLoop
readDone:
SETD.0 Ended
CALL printString
CALL newLine
HALT
; One space. Used between the numbers so that each line reads as a list.
blank:
INIA 0x20
OUTA 0x00
RET
#Data
Banner:
"console test"
HexLabel:
"byte hex: "
WordHexLabel:
"word hex: "
ByteLabel:
"byte dec: "
WordLabel:
"word dec: "
SpaceLabel:
"spaces: "
ReadLabel:
"lines read:"
Ended:
"end of input"
Zero:
0x00 0x00
Nine:
0x00 0x09
Ten:
0x00 0x0A
TwoFiveFive:
0x00 0xFF
Thousand:
0x03 0xE8
TenThousandOne:
0x27 0x11
Largest:
0xFF 0xFF
LineLength:
0x00
LineBuffer:
#Reserve 0d17
#Vectors
Boot start
@@ -0,0 +1,22 @@
; A program that bases one segment and forgets the other.
;
; This is the shape of a real bug: the original Fib-8, brought over to CosmOS as an
; application, based its code at 0x2000 but got its Data Segment from an included library
; and never based it. The three bytes went to 0x0000, on top of the console's own
; variables, and it appeared to work because those three bytes are scratch that readLine
; rewrites before every use. A larger data segment would have reached the decimal table
; and the mounted filesystem behind it, and failed somewhere else entirely.
;
; Nothing relocates, so the assembler is the last place to catch it.
#Program
#Base 0x2000
start:
HALT
#Data
value:
0x00
+132
View File
@@ -0,0 +1,132 @@
; Walking the directory of a SplitBit filesystem.
;
; sbfsFind answers a question about one name. This is the other thing a directory is for:
; being listed. The disk was made by SplitDisk, so the order and contents here are what
; the other implementation of the format wrote, not what this one assumed.
;
; The cases that matter are all on this disk already. There are twelve files, which is
; more than the eight an entry block holds, so the walk has to cross from the first
; directory block into the second. aName22CharactersLong! fills the name field exactly and
; so has no zero byte to end it, which is what the twenty third byte of SbfsName is for.
; empty.txt has no blocks at all.
;
; The count at the end is the proof that the walk stopped where the directory did. A walk
; that stepped its pointer only when it took an entry would loop forever on a free one,
; and one that stepped only when it skipped would hand out every eighth file.
;
; Correct output is the twelve files with their sizes, then how many there were.
#Include console.asm
#Include sbfs.asm
#Program
start:
CALL sbfsMount
BRQ mounted
SETD.0 NoMount
CALL printString
CALL newLine
HALT
mounted:
; Nothing found yet.
RSTA
SETD.0 Seen
STA.0
CALL sbfsFirst
BRI walkCheck
walkStep:
CALL sbfsNext
walkCheck:
BNQ walkDone
SETD.0 Seen
LDA.0
INCA
STA.0
SETD.0 SbfsName
CALL printString
; Line the sizes up, so a name that fills the field and a short one both read cleanly.
SETD.0 SbfsName
CALL nameWidth
MVQA
CALL printSpaces
; A file is its blocks times 256 plus its tail, which is the block count in the high
; byte and the tail in the low one. Nothing has to multiply anything.
SETD.0 SbfsFileBlocks
DPUP.0 0d01
LDA.0
SETD.1 Size
STA.1
SETD.0 SbfsFileTail
LDA.0
SETD.1 Size
INCD.1
STA.1
SETD.0 Size
CALL printWordDecimal
CALL newLine
BRI walkStep
walkDone:
SETD.0 Total
CALL printString
SETD.0 Seen
LDA.0
CALL printByteDecimal
CALL newLine
HALT
; DP0 names a string. Q is how many spaces would pad it out to twenty four characters.
; A name that is already that long gets one space, because none at all would run the
; name into the number.
nameWidth:
INIA 0d24
SETD.1 Padding
STA.1
widthLoop:
LDA.0
BRA widthDone
SETD.1 Padding
LDA.1
DECA
STA.1
BRA widthFloor ; It is already too long to pad.
INCD.0
BRI widthLoop
widthFloor:
INIA 0d1
SETD.1 Padding
STA.1
widthDone:
SETD.1 Padding
LDA.1
RSTB
CCF
ADD
RET
#Data
NoMount:
"no filesystem on that disk"
Total:
"files: "
Seen:
0x00
Padding:
0x00
Size:
0x00 0x00
#Vectors
Boot start
+210
View File
@@ -0,0 +1,210 @@
; Tests MVDS, which copies a Data Pointer into the Stack Pointer.
;
; This is the dangerous one. Moving the Stack Pointer abandons everything below the new
; position: return addresses, saved registers, interrupt frames, all of it. Nothing is
; unwound, because moving the Stack does not move what is on it.
;
; It is here for one job, and that job is what this program acts out. A system that runs
; other programs and takes the machine back afterwards cannot do so without it. A program
; that gives up part way through leaves whatever it pushed behind, and nothing is ever
; going to return and tidy it away. Without MVDS the Stack only ever moves downward, a
; little further with every program run, and a shell cannot outlive many of them.
;
; The other half of the story is that moving the Stack does not destroy what was on it.
; A routine that puts the Stack Pointer back where it found it returns perfectly normally,
; because the frame was only stepped away from. Both halves are acted out here, and the
; nesting case with them: two routines that each borrow a Stack, one inside the other,
; each keeping its own saved Stack Pointer on the Stack it borrowed. A fixed location in
; Data Memory would fail exactly there, and would fail silently.
;
; Both sequences are the ones the Programming Manual prints under "The Stack Pointer,
; Set By Hand", so if either example ever stops working this test says so.
;
; Correct output is:
; stack reclaim test
; at start: FFFF
; spent: FFF7
; reclaimed: FFFF
; still good: 33
; borrowed: 7 and 9
; back home: FFFF
#Include console.asm
#Program
start:
SETD.0 Banner
CALL printString
CALL newLine
; Where the Stack is before anything has touched it. Read first, so that no call has
; had a chance to move it.
MVSD.0
SETD.1 SavedStack
STD.0.1
SETD.0 StartLabel
CALL printString
SETD.0 SavedStack
CALL printWordHex
CALL newLine
; Spend some Stack and never give it back. This is not a subroutine, because a
; subroutine that pushed without popping would take its own return address with it.
; It stands in for a program that stops half way through.
INIA 0x11
PSHA PSHA PSHA PSHA
PSHA PSHA PSHA PSHA
MVSD.0
SETD.1 Spent
STD.0.1
SETD.0 SpentLabel
CALL printString
SETD.0 Spent
CALL printWordHex
CALL newLine
; Take it back. Nothing pops those eight bytes: they are simply no longer on the Stack,
; which is the whole of what reclaiming means.
SETD.1 SavedStack
LDD.0.1
MVDS.0
MVSD.0
SETD.1 Reclaimed
STD.0.1
SETD.0 ReclaimedLabel
CALL printString
SETD.0 Reclaimed
CALL printWordHex
CALL newLine
; A Stack Pointer in the right place is not proof that the Stack works. Push something
; through it and get it back, so that the reclaimed Stack is shown to be usable and not
; merely well positioned.
SETD.0 GoodLabel
CALL printString
INIA 0d33
PSHA
RSTA
POPA
CALL printByteDecimal
CALL newLine
; ---- Moving the Stack and coming back from it ----
; borrower runs on a Stack of its own and returns with a plain RET, which only works
; because putting the Stack Pointer back leaves the frame exactly as CALL left it.
CALL borrower
SETD.0 BorrowedLabel
CALL printString
SETD.0 OuterResult
LDA.0
CALL printByteDecimal
SETD.0 AndText
CALL printString
SETD.0 InnerResult
LDA.0
CALL printByteDecimal
CALL newLine
; The Stack has to be back where it started, or the nesting quietly lost track of it.
MVSD.0
SETD.1 Reclaimed
STD.0.1
SETD.0 HomeLabel
CALL printString
SETD.0 Reclaimed
CALL printWordHex
CALL newLine
HALT
; Borrows a Stack, works on it, calls something that does the same thing again, then puts
; the Stack back and returns. The old Stack Pointer goes onto the borrowed Stack rather
; than into a fixed place in memory, which is what makes the nesting safe: the inner
; routine keeps its own copy on its own Stack and cannot tread on this one.
borrower:
MVSD.3
SETD.0 OuterTop
MVDS.0
PSHD.3
INIA 0d7
PSHA
RSTA
POPA
SETD.1 OuterResult
STA.1
CALL innerBorrower
POPD.3
MVDS.3
RET
; The same again, one level down and on a Stack of its own.
innerBorrower:
MVSD.3
SETD.0 InnerTop
MVDS.0
PSHD.3
INIA 0d9
PSHA
RSTA
POPA
SETD.1 InnerResult
STA.1
POPD.3
MVDS.3
RET
#Data
Banner:
"stack reclaim test"
StartLabel:
"at start: "
SpentLabel:
"spent: "
ReclaimedLabel:
"reclaimed: "
GoodLabel:
"still good: "
BorrowedLabel:
"borrowed: "
AndText:
" and "
HomeLabel:
"back home: "
SavedStack:
0x00 0x00
Spent:
0x00 0x00
Reclaimed:
0x00 0x00
OuterResult:
0x00
InnerResult:
0x00
; Two places a Stack can live, sixty four bytes each. The label is on the last byte of
; each, because a Stack grows downward from wherever it is set.
OuterStack:
#Reserve 0d63
OuterTop:
0x00
InnerStack:
#Reserve 0d63
InnerTop:
0x00
#Vectors
Boot start
+204
View File
@@ -0,0 +1,204 @@
; Exercises text.asm, which is how CosmOS understands what was typed at it.
;
; The cases here are the ones that decide whether a shell feels right or feels broken.
; textSame has to say that "dir" and "dirty" are different words, or every command would
; match its own prefix. textSplit has to give an empty rest rather than a missing one when
; there is no argument, so that "load" with nothing after it is a question the shell can
; answer. And textHexWord has to take upper and lower case alike, refuse things that only
; look like numbers, and push digits off the top rather than refusing a fifth one.
;
; Correct output is:
; split: [dump] [program 2000]
; split: [dir] []
; same: yes no no no
; hex: 2000 00FF 00FF BEEF FFFF 0000
; 0 is a fine way to begin a string
; no number here
#Include console.asm
#Include text.asm
#Program
start:
; ---- Splitting ----
SETD.0 LineOne
CALL textSplit
SETD.0 SplitLabel
CALL printString
INIA 0x5B
OUTA 0x00
SETD.0 LineOne
CALL printString
INIA 0x5D
OUTA 0x00
CALL blank
INIA 0x5B
OUTA 0x00
SETD.1 TextRest
LDD.0.1
CALL printString
INIA 0x5D
OUTA 0x00
CALL newLine
; One word on its own. The rest has to be empty rather than absent.
SETD.0 LineTwo
CALL textSplit
SETD.0 SplitLabel
CALL printString
INIA 0x5B
OUTA 0x00
SETD.0 LineTwo
CALL printString
INIA 0x5D
OUTA 0x00
CALL blank
INIA 0x5B
OUTA 0x00
SETD.1 TextRest
LDD.0.1
CALL printString
INIA 0x5D
OUTA 0x00
CALL newLine
; ---- Comparing ----
; The same, then longer, then shorter, then different. The middle two are what a
; comparison that stopped at the first ending would get wrong.
SETD.0 SameLabel
CALL printString
SETD.0 WordDir
SETD.1 WordDir2
CALL textSame
CALL sayQ
SETD.0 WordDir
SETD.1 WordDirty
CALL textSame
CALL sayQ
SETD.0 WordDirty
SETD.1 WordDir2
CALL textSame
CALL sayQ
SETD.0 WordDir
SETD.1 WordRun
CALL textSame
CALL sayQ
CALL newLine
; ---- Reading numbers ----
SETD.0 HexLabel
CALL printString
SETD.0 HexPlain
CALL showHex
SETD.0 HexUpper
CALL showHex
SETD.0 HexLower
CALL showHex
SETD.0 HexMixed
CALL showHex
SETD.0 HexTooMany
CALL showHex
SETD.0 HexStops
CALL showHex
CALL newLine
; A string may begin with anything, including a zero. The assembler strips the quotes
; before it decides what a token is, so a string starting with a zero looked exactly
; like a malformed literal and was rejected as one. This line is here so that it cannot
; go back to being rejected quietly.
SETD.0 ZeroString
CALL printString
CALL newLine
; Something that is not a number at all has to be refused rather than read as zero.
SETD.0 HexNone
CALL textHexWord
BRQ hexUnexpected
SETD.0 NoNumber
CALL printString
CALL newLine
HALT
hexUnexpected:
SETD.0 Unexpected
CALL printString
CALL newLine
HALT
; DP0 names text. Reads a number from it and prints what came out.
showHex:
CALL textHexWord
SETD.0 TextValue
CALL printWordHex
CALL blank
RET
; Prints yes if Q is zero and no if it is not. Q survives a RET, which is what lets the
; answer get this far.
sayQ:
BNQ sayNo
SETD.0 YesText
CALL printString
RET
sayNo:
SETD.0 NoText
CALL printString
RET
blank:
INIA 0x20
OUTA 0x00
RET
#Data
SplitLabel:
"split: "
SameLabel:
"same: "
HexLabel:
"hex: "
YesText:
"yes "
NoText:
"no "
NoNumber:
"no number here"
ZeroString:
"0 is a fine way to begin a string"
Unexpected:
"a number was found where there is none"
; textSplit writes over the space it cuts at, so these are written out rather than shared.
LineOne:
"dump program 2000"
LineTwo:
"dir"
WordDir:
"dir"
WordDir2:
"dir"
WordDirty:
"dirty"
WordRun:
"run"
HexPlain:
"2000"
HexUpper:
"FF"
HexLower:
"ff"
HexMixed:
"bEeF"
HexTooMany:
"12FFFF"
HexStops:
"0 and more"
HexNone:
"zebra"
#Vectors
Boot start