A program can say how it went

SWI osExit takes a status in A, and the shell keeps it. Fifty eight exits
across twenty three programs now say deliberately whether they worked: 25
did what they were asked, 24 did not, 9 were asked wrongly. Compare is the
exception and says so - one there means the files differ, which is a result
rather than a failure, the way diff has always had it.

IN A RATHER THAN Q, which is not a departure from the rule that a service
answers in Q. This one takes an ARGUMENT, the way osPrintNumber takes A and
B, and it never returns to answer anything. A is free precisely because a
return would have put it back - and Q is the ALU's output, so a small
number costs four instructions there against one in A.

The shell does not print it. A program that failed has already said so in
words and a number beside that is noise, so osLastStatus hands it back and
Status is the program that shows it. That indirection is the point: the
number exists for the thing that cannot read words.

MARKING THE EXITS FOUND A DEFECT ON THE FIRST RUN. Type and More printed
why they had failed and then fell through into the success exit, reporting
that all was well. Nobody had noticed, because while the only reader was a
person, the person could see both the complaint and the claim.

Two smaller things. Snake sets the console to line mode and then exits with
zero, and the linter flagged the second RSTA as redundant - an exit status
and a console mode, equal by accident, which is the class that must never
be collapsed. And the README still taught answering by writing into the
frame, three months of habit that SRET replaced yesterday; that section is
gone and the one describing SRET stands in its place.
This commit is contained in:
Anachronaut
2026-08-27 19:16:21 -04:00
parent b4206673a6
commit 87d819847e
40 changed files with 297 additions and 47 deletions
+1
View File
@@ -58,6 +58,7 @@ start:
SETD.0 DoneText
SWI osPrintString
RSTA
SWI osExit
deeper:
+5
View File
@@ -47,6 +47,7 @@ start:
BNQ refused
SETD.0 Allowed
SWI osPrintString
RSTA
SWI osExit
refused:
@@ -60,19 +61,23 @@ refused:
BNQ noHonest
SETD.0 Honest
SWI osPrintString
RSTA
SWI osExit
noHonest:
SETD.0 NoHonest
SWI osPrintString
INIA 0d1
SWI osExit
noStart:
SETD.0 NoStart
SWI osPrintString
INIA 0d1
SWI osExit
noWrite:
SETD.0 NoWrite
SWI osPrintString
INIA 0d1
SWI osExit
#Data
+7
View File
@@ -202,30 +202,37 @@ byteBorrow:
alike:
SETD.0 SameText
SWI osPrintString
RSTA
SWI osExit
different:
SETD.0 DifferentText
SWI osPrintString
INIA 0d1
SWI osExit
usage:
SETD.0 Usage
SWI osPrintString
INIA 0d2
SWI osExit
firstFailed:
SETD.0 FirstError
SWI osPrintString
INIA 0d2
SWI osExit
secondFailed:
SETD.0 SecondError
SWI osPrintString
INIA 0d2
SWI osExit
firstReadFailed:
SETD.0 FirstReadError
SWI osPrintString
INIA 0d2
SWI osExit
secondReadFailed:
SETD.0 SecondReadError
SWI osPrintString
INIA 0d2
SWI osExit
#Data
+7
View File
@@ -154,6 +154,7 @@ copyDone:
BNQ doneFailed
SETD.0 Copied
SWI osPrintString
RSTA
SWI osExit
; DP0 names the source word and DP1 the destination word.
@@ -202,26 +203,32 @@ takeBorrow:
usage:
SETD.0 Usage
SWI osPrintString
INIA 0d2
SWI osExit
sourceFailed:
SETD.0 SourceError
SWI osPrintString
INIA 0d1
SWI osExit
readFailed:
SETD.0 ReadError
SWI osPrintString
INIA 0d1
SWI osExit
startFailed:
SETD.0 StartError
SWI osPrintString
INIA 0d1
SWI osExit
writeFailed:
SETD.0 WriteError
SWI osPrintString
INIA 0d1
SWI osExit
doneFailed:
SETD.0 DoneError
SWI osPrintString
INIA 0d1
SWI osExit
#Data
+2
View File
@@ -161,12 +161,14 @@ commandArgument:
BRI commandLoop
quit:
RSTA
SWI osExit
noName:
SETD.0 NoNameText
SWI osPrintString
CALL newLine
INIA 0d2
SWI osExit
+1
View File
@@ -67,6 +67,7 @@ start:
BRI start
end:
CALL lineFeed
RSTA
SWI osExit
#Data
+1
View File
@@ -123,6 +123,7 @@ start:
end:
CALL lineFeed
;HALT
RSTA
SWI osExit
#Data
+1
View File
@@ -33,6 +33,7 @@ start:
end:
CALL lineFeed
;HALT
RSTA
SWI osExit ; Return to CosmOS.
#Data
+6
View File
@@ -101,28 +101,34 @@ atEnd:
BRQ stillThere
SETD.0 GoneText
SWI osPrintString
RSTA
SWI osExit
stillThere:
SETD.0 StillText
SWI osPrintString
RSTA
SWI osExit
noSave:
SETD.0 NoSaveText
SWI osPrintString
INIA 0d1
SWI osExit
noRead:
SETD.0 NoReadText
SWI osPrintString
INIA 0d1
SWI osExit
noRename:
SETD.0 NoRenameText
SWI osPrintString
INIA 0d1
SWI osExit
noDelete:
SETD.0 NoDeleteText
SWI osPrintString
INIA 0d1
SWI osExit
#Data
+1
View File
@@ -56,6 +56,7 @@ spin:
SETD.0 DoneText
CALL printString
CALL newLine
RSTA
SWI osExit
; Entered because the console had something to say. Never called.
+1
View File
@@ -105,6 +105,7 @@ lifeEnd:
CALL lineFeed
CALL printString ; DP0 still holds the words: CALL puts DP0 back.
CALL lineFeed
RSTA
SWI osExit
seedGlider:
+8
View File
@@ -109,6 +109,7 @@ fullPage:
noName:
SETD.0 Usage
SWI osPrintString
INIA 0d2
SWI osExit
openFailed:
SETD.0 OpenError
@@ -123,7 +124,14 @@ printError:
SWI osPrintNumber
SETD.0 NewLine
SWI osPrintString
; ITS OWN EXIT, and it did not have one. This fell through into finished and reported
; that everything was fine, having just printed the reason it was not - which nobody
; noticed while the only reader was a person, who could see both.
INIA 0d1
SWI osExit
finished:
RSTA
SWI osExit
#Data
+4
View File
@@ -118,6 +118,7 @@ theTail:
SWI osPrintNumber
SETD.0 AndTail
SWI osPrintString
RSTA
SWI osExit
; The block becomes 256 copies of the block number plus a fixed byte, so that a block
@@ -198,14 +199,17 @@ tenLoop:
startFailed:
SETD.0 NoStart
SWI osPrintString
INIA 0d1
SWI osExit
writeFailed:
SETD.0 NoWrite
SWI osPrintString
INIA 0d1
SWI osExit
doneFailed:
SETD.0 NoDone
SWI osPrintString
INIA 0d1
SWI osExit
#Data
+1
View File
@@ -40,6 +40,7 @@ sayNothing:
sayEnd:
SETD.0 NewLine
SWI osPrintString
RSTA
SWI osExit
#Data
+3
View File
@@ -54,16 +54,19 @@ doSettle:
BNA settleFailed
SETD.0 Settled
SWI osPrintString
RSTA
SWI osExit
settleFailed:
SETD.0 NoDisk
SWI osPrintString
INIA 0d1
SWI osExit
alreadySettled:
SETD.0 Already
SWI osPrintString
RSTA
SWI osExit
#Data
+1
View File
@@ -74,6 +74,7 @@ advancePage:
finished:
CALL lineFeed
RSTA
SWI osExit ; Return to CosmOS.
; DP0 points at a PrimeStates entry. CALL restores it on return.
+1
View File
@@ -38,6 +38,7 @@ start:
BRI markMultiples ; Otherwise, loop again to mark the next multiple as nonprime.
end:
CALL lineFeed ; Print a linefeed to make it look nice.
RSTA
SWI osExit ; The program is done, we found all the primes!
+1
View File
@@ -105,6 +105,7 @@ gameOverSay:
CALL newLine
RSTA
OUTA 0x02 ; Line mode, the way it was found.
RSTA ; splitlint[redundant-assignment]: an exit status, not a console mode
SWI osExit
; ---- Reaching a square ----
+87
View File
@@ -0,0 +1,87 @@
; Status.asm
; Says what the last program made of what it was asked to do.
;
; The shell keeps the number and does not print it, because a program that failed has
; already said so in words and a number beside that would be noise. But a number nobody can
; see is a number nobody can trust, so this is how a person looks.
;
; 0 it did what it was asked
; 1 it did not
; 2 it was asked wrongly
;
; A program may give its own meanings if it says so, and Compare does: one there means the
; files differ, which is a result rather than a failure.
;
; Written by Anachronaut
#Include services.asm
#Program
#Base 0x4000
start:
SWI osLastStatus
MVQA
SETD.0 Was
STA.0
SETD.0 Prefix
SWI osPrintString
; A and B together, most significant first - so the status is the LOW half. Put in A the
; first time this was written, which printed a status of two as five hundred and twelve.
RSTA
SETD.0 Was
LDB.0
SWI osPrintNumber
; And in words, for the three the system itself uses.
SETD.0 Was
LDA.0
BRA sayWorked
INIB 0d1
XOR
BRQ sayFailed
SETD.0 Was
LDA.0
INIB 0d2
XOR
BRQ sayAsked
BRI done
sayWorked:
SETD.0 WorkedText
SWI osPrintString
BRI done
sayFailed:
SETD.0 FailedText
SWI osPrintString
BRI done
sayAsked:
SETD.0 AskedText
SWI osPrintString
done:
SETD.0 NewLine
SWI osPrintString
RSTA
SWI osExit
#Data
#Base 0x2000
Prefix:
"the last program left "
WorkedText:
", which is: it did what it was asked"
FailedText:
", which is: it did not"
AskedText:
", which is: it was asked wrongly"
NewLine:
"
"
Was:
0x00
+4
View File
@@ -216,6 +216,7 @@ missing:
SWI osPrintString
CALL printWhy
INIA 0d1
SWI osExit
noBig:
@@ -223,14 +224,17 @@ noBig:
SETD.0 NoBigText
SWI osPrintString
CALL printWhy
INIA 0d1
SWI osExit
noSmall:
SETD.0 NoSmallText
SWI osPrintString
INIA 0d1
SWI osExit
noRename:
SETD.0 NoRenameText
SWI osPrintString
INIA 0d1
SWI osExit
; ---- Routines ----
+8
View File
@@ -44,6 +44,7 @@ printLoop:
noName:
SETD.0 Usage
SWI osPrintString
INIA 0d2
SWI osExit
openFailed:
SETD.0 OpenError
@@ -58,7 +59,14 @@ printError:
SWI osPrintNumber
SETD.0 NewLine
SWI osPrintString
; ITS OWN EXIT, and it did not have one. This fell through into finished and reported
; that everything was fine, having just printed the reason it was not - which nobody
; noticed while the only reader was a person, who could see both.
INIA 0d1
SWI osExit
finished:
RSTA
SWI osExit
#Data
+4
View File
@@ -65,19 +65,23 @@ wanderByte:
BRI wanderBlock
wanderDone:
RSTA
SWI osExit
noWhere:
SETD.0 NoWhereText
SWI osPrintString
INIA 0d1
SWI osExit
noSuchPlace:
SETD.0 NoPlaceText
SWI osPrintString
INIA 0d1
SWI osExit
noFile:
SETD.0 NoFileText
SWI osPrintString
INIA 0d1
SWI osExit
#Data
+1
View File
@@ -39,6 +39,7 @@ greet:
; Give the machine back. The system takes its Stack back at this point, so everything
; this program pushed goes with it.
RSTA
SWI osExit
#Data
+1
View File
@@ -21,6 +21,7 @@ End:
OUTA 0x00 ; Output it to the text console.
;HALT ; Terminate the program.
; Instead, let's call osExit to return the system nicely. Fourth change.
RSTA
SWI osExit
#Data
+28 -25
View File
@@ -361,8 +361,9 @@ from every assembly file in it. Several are old programs written for the bare ma
| Snake | A game. Draws a whole screen with cursor addressing and steers with single keys, asking the console once a frame and never waiting. |
| 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. |
| Settle | Says how the last start went and tells the machine to stop falling back, in 349 bytes. A program rather than a shell word, because the shell is for what cannot be done without it. |
| Files | Writes a file, reads it back, renames it and deletes it, in 665 bytes, including nothing but the service names. It is what says a program does not need a filesystem inside it. |
| Status | Says what the last program made of what it was asked to do, in 222 bytes. The shell keeps the number and does not print it; this is how a person looks. |
| Settle | Says how the last start went and tells the machine to stop falling back, in 353 bytes. A program rather than a shell word, because the shell is for what cannot be done without it. |
| Files | Writes a file, reads it back, renames it and deletes it, in 675 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. |
| 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. |
@@ -463,7 +464,7 @@ Typed in as bytes, checked by disassembling it back, and run. It ends with `SWI
`Edit` is the first program on this machine that makes a file a person typed - every byte on every disk before it was put there by the host tool. It is line oriented in the manner of `ed`: `l` lists, `a` adds at the end, `i` and `c` and `d` take a line number, `w` writes and `q` stops.
It includes nothing but `services.asm` and `text.asm`: the filesystem and the console are the system's, asked for rather than carried. That is what brought `Edit` down from 4,941 bytes to 1,983 bytes without a line of its own logic changing - and the way that was checked is worth knowing, because the recorded output of the `cosmosEdit` test did not move by a single byte across the rewrite.
It includes nothing but `services.asm` and `text.asm`: the filesystem and the console are the system's, asked for rather than carried. That is what brought `Edit` down from 4,941 bytes to 1,986 bytes without a line of its own logic changing - and the way that was checked is worth knowing, because the recorded output of the `cosmosEdit` test did not move by a single byte across the rewrite.
It keeps the document as a **linked list of lines** rather than one buffer with newlines in it. Each line says where the next one is, how long it is, and then its bytes. Inserting is two pointers changed and nothing moved; with a flat buffer it would mean shifting every byte after the edit, on a machine whose only block move is a device asked politely. The price is that deleted lines are not reused, so a heavy session uses more room than the document needs and writing it out is what tidies up.
@@ -591,6 +592,7 @@ Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, w
| osFileFetch | DP1 is where a block should go, A and B together are which block. Reads back a block of the file being written. |
| 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. |
| osLastStatus | Q answers what the last program exited with: 0 it did what it was asked, 1 it did not, 2 it was asked wrongly. A program may give its own meanings if it says so. |
| osBootState | Q answers how the last start went: 0 settled, 1 trying, 2 fell back. A machine with no disk answers settled, because there is nothing there to be unsettled about. |
| osBootSettle | Puts it back to settled, which is how a machine that fell back is told the situation has changed. Q is zero if the disk took it. **Settling is the only write a program gets** - marking a start as trying or fallen back is the loader's business, and a service that let a program claim either would let it lie about something the loader cannot check. |
@@ -675,33 +677,12 @@ Running off the end is how a reader finds out it has finished, so it gets an ans
`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 665 bytes, and includes nothing but the service names.
`Programs/CosmOS/Apps/Files.asm` does the whole round trip - write, read, report, rename, delete - in 675 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.
A handler is entered with the caller's registers exactly as they were, because an interrupt frame is pushed rather than cleared. That is why a service can be given a pointer in DP0 and a count in B without any of it being copied anywhere first.
### How A Service Answers:
The same thing that makes an interrupt safe makes a service mute. RETI restores every register from the frame, so whatever a handler worked out is thrown away on the way out - which is exactly right for a device interrupting at a moment nobody chose, and useless for a service that was asked a question.
A service answers by **writing into its own frame**, over the saved register, and letting RETI put it back. MVSD copies the Stack Pointer into a Data Pointer and the frame sits just above it, so returning a byte in Q is three instructions:
```
answer:
INIA 0d42
MVSD.1
DPUP.1 0d02 ; The saved Q. See the frame table under Interrupts.
STA.1
RETI
```
**Which registers a service may answer in is the convention CALL already has: Q and DP3.** A subroutine cannot hand back A, B or Data Pointers 0 to 2 because RET puts them back; a service *could* write over any of them and should not, for exactly the reason that list exists. A caller is entitled to find what it kept still there.
**Only the handler itself can do this.** The offsets are from wherever the Stack Pointer is, and a CALL moves it by ten - so a routine called by a handler that tried the same thing would be writing into its own return address. The poke belongs inline, next to the RETI.
A service that has nothing to say does nothing, and the caller's registers arrive back untouched. That is worth knowing from the other side too: a service cannot corrupt a register by accident, only by deciding to.
## The Filesystem Library:
The disk knows blocks and nothing else, so a filesystem is software. Programs/CosmOS/Source/sbfs.asm is one.
@@ -884,6 +865,28 @@ A file's length is its block count times 256 plus its tail, which is the same as
The other implementation of this format is SplitDisk, on the host. Nothing is shared between the two but the specification, so a change to either has to be a change to both.
### What A Program Made Of It:
`SWI osExit` takes a status **in A**: zero if the program did what it was asked, one if it
did not, two if it was asked wrongly. A program may give its own meanings if it says so, and
`Compare` does - one there means the files differ, which is a result rather than a failure.
**In A rather than Q**, which is not a departure from the rule that a service answers in Q.
This one *takes an argument*, the way `osPrintNumber` takes A and B, and it never returns to
answer anything. A is free precisely because a return would have put it back - and Q is the
ALU's output, so setting it to a small number costs four instructions where A costs one.
**The shell keeps the number and does not print it.** A program that failed has already said
so in words, and a number beside that would be noise. `osLastStatus` hands it back and
`Status` is the program that shows it. The indirection is the point: this number is for the
thing that cannot read words - whatever comes to run programs in sequence and has to decide
whether to run the next one.
Marking all fifty eight exits found a defect on its first run. `Type` and `More` printed why
they had failed and then **fell through into the success exit**, reporting that all was
well. Nobody had noticed, because while the only reader was a person, the person could see
both.
### How A Service Answers:
A handler arrives with the caller's registers pushed rather than cleared, and **`RETI`
+28
View File
@@ -2242,6 +2242,18 @@ handlePrintNumber:
;
; Which is exactly why this cannot RETI. Its return address is on the Stack it just walked
; away from, so it branches to the prompt instead.
; What the last program made of what it was asked to do. Kept rather than shown: a program
; that failed has already said so in words, and a number beside that would be noise. It is
; here for the thing that cannot read words - whatever comes to run programs in sequence and
; has to decide whether to run the next one.
handleLastStatus:
SETD.2 LastStatus
LDA.2
RSTB
CCF
ADD ; A is the answer, so Q becomes it.
SRET
; ---- How the last start went, and settling it ----
;
; The answer goes in Q by writing into this handler's own frame, which is how every service
@@ -2292,6 +2304,17 @@ bootSettleNo:
SRET
handleExit:
; ---- What the program made of it ----
;
; A, before anything below disturbs it. IN A RATHER THAN Q, and that is not a departure
; from the rule that a service answers in Q: this one takes an argument, the way
; osPrintNumber takes A and B, and it never returns to answer anything. The register a
; subroutine cannot hand anything back in is exactly the one that is free here - and Q is
; the ALU's output, so setting it to a small number costs four instructions where A costs
; one.
SETD.1 LastStatus
STA.1
SETD.1 SystemStack
LDD.0.1
MVDS.0
@@ -3417,6 +3440,10 @@ Banner:
"CosmOS"
PromptText:
"> "
; Nothing has run yet, so nothing has failed yet.
LastStatus:
0x00
OnFallback:
"this is the fallback: what boot.cfg asks for did not start
"
@@ -3913,6 +3940,7 @@ CommandLine:
osFileFetch handleFileFetch
osPrintNumber handlePrintNumber
osBreak handleBreak
osLastStatus handleLastStatus
osBootState handleBootState
osBootSettle handleBootSettle
Device 0x20 diskDone
+14
View File
@@ -153,3 +153,17 @@
; about something the loader has no way to check.
osBootState 0d33
osBootSettle 0d34
; ---- What a program made of it ----
;
; osExit takes a status in A: zero if the program did what it was asked, and a number of
; its own choosing if it did not. IN A RATHER THAN Q, which is not a departure from the
; rule that a service answers in Q - this one takes an argument, the way osPrintNumber
; does, and never returns to answer anything. A is free precisely because a return would
; have put it back, and Q is the ALU's output, so a small number costs four instructions
; there and one in A.
;
; osLastStatus answers in Q with what the last program exited with. The shell does not
; print it: a program that failed has already said so in words, and a number beside that
; would be noise. This is for the thing that cannot read words.
osLastStatus 0d35