Start a program by typing its name

A word the shell has no command for is now looked for on the disk as
"<name>.sbx", and if it is there it is loaded and started exactly as load and
run would do it. Whatever followed the word reaches the program through
osArgument by the same route as whatever follows run, so "Say hello there"
and "Type notes.txt" work without either program knowing how it was started.

load and run are unchanged and both stay. load is how the monitor puts an
arbitrary file in front of itself, which typing a name deliberately cannot do:
the extension is added rather than assumed, so "notes.txt" looks for
notes.txt.sbx and a text file is unreachable by name whatever is inside it.

Three things this had to get right:

The built-ins are tried first and always win. The search hangs off the end of
the dispatch chain, so a file called dir.sbx cannot become dir, and the
commands worth trusting when the disk is what you are doubting stay
trustworthy. The invoke disk carries a working dir.sbx so that this is checked
rather than asserted.

A file that is found but is broken says so. "not a program" and "I do not
know" are different answers, and giving the second about a file sitting on the
disk would send somebody looking in the wrong place. loadProgram therefore
hands back a status as well as a message, since only "no file of that name"
can fairly be reported as anything other than a fault.

doLoad became that subroutine rather than being copied. It ends in RET instead
of a jump to the prompt, and each way of failing sets its number and its text
together so a new one cannot leave half of the answer behind.

cosmosBreak moves because Break prints the pointers it was handed and those
are the shell's leftovers, which a CALL now puts back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-24 17:25:50 -04:00
co-authored by Claude Opus 5
parent dbe58db660
commit 2b0aeeefd4
8 changed files with 358 additions and 35 deletions
+44 -3
View File
@@ -20,6 +20,8 @@ for itself.
- Loadable Applications: Validate SBEX files, copy their Program and Data segments into - Loadable Applications: Validate SBEX files, copy their Program and Data segments into
the addresses for which they were assembled, and start them at their declared entry the addresses for which they were assembled, and start them at their declared entry
point. point.
- Invocation By Name: A word the shell has no command for is looked for on the disk as
`<name>.sbx`, and loaded and started if it is there. Built-in commands are tried first.
- Resident Services: Applications can print strings and numbers, read lines, receive their - Resident Services: Applications can print strings and numbers, read lines, receive their
command arguments, read and write files, and return to the shell through named software command arguments, read and write files, and return to the shell through named software
interrupts. interrupts.
@@ -83,6 +85,7 @@ CosmOS currently provides these built-in commands:
| `dir` | List the files on the mounted disk and their sizes. | | `dir` | List the files on the mounted disk and their sizes. |
| `load <file>` | Read and validate an SBEX application, then place its code and data where its header requests. | | `load <file>` | Read and validate an SBEX application, then place its code and data where its header requests. |
| `run [words]` | Start the loaded application and make the rest of the line available to it as an argument. | | `run [words]` | Start the loaded application and make the rest of the line available to it as an argument. |
| `<name> [words]` | Any word the shell does not recognise is looked for on the disk as `<name>.sbx`, and loaded and started if it is there. |
| `delete <file>` | Remove a file from the filesystem and release its blocks. | | `delete <file>` | Remove a file from the filesystem and release its blocks. |
| `rename <file> <to>` | Give a file a different name without moving its contents. | | `rename <file> <to>` | Give a file a different name without moving its contents. |
| `monitor` | Enter monitor mode, in which the prompt becomes `*` and the commands below are also available. | | `monitor` | Enter monitor mode, in which the prompt becomes `*` and the commands below are also available. |
@@ -115,9 +118,47 @@ For example:
> run > run
``` ```
Loading and running are separate operations for now. A loaded program may be run again Or, equivalently:
without being read from disk again, which is useful both as a monitor facility and as a
test that CosmOS correctly restores its Stack and vector table after every run. ```text
> Snake
```
Loading and running remain separate operations, and both of them remain. A loaded program
may be run again without being read from disk again, which is useful both as a monitor
facility and as a test that CosmOS correctly restores its Stack and vector table after
every run; and `load` is how the monitor puts an arbitrary file in front of itself, which
is a thing typing a name deliberately cannot do.
### Starting An Application By Name:
A word the shell has no command for is not immediately an error. Before saying so, the
shell adds `.sbx` to it unless it is already there, looks for a file of that name, and if
one is there loads it and starts it exactly as `load` and `run` would. Whatever followed
the word reaches the program through `osArgument`, the same way and by the same route as
whatever follows `run`.
Three properties of this are deliberate:
**Built-in commands are tried first and always win.** The search happens only after the
whole dispatch chain has failed to match, so a file named `dir.sbx` cannot become `dir`.
The commands that are worth trusting when the disk is the thing being doubted stay
trustworthy.
**The extension is what makes a file reachable by name.** Typing `notes` looks for
`notes.sbx`, and typing `notes.txt` looks for `notes.txt.sbx`. A text file therefore
cannot be started by typing what it is called, whatever happens to be inside it. Only
`load` reaches a file by its literal name.
**A file that is found but is broken says so.** If `notes.sbx` exists and is not an SBEX
program, typing `notes` reports `not a program` rather than `I do not know: notes`.
Reporting an unknown command about a file that is sitting on the disk would send somebody
looking in the wrong place.
Names are matched exactly, including case, because every other name on the filesystem is.
A directory entry holds twenty two characters and four are spoken for by the extension, so
eighteen is the longest a bare name can be; a longer word is reported as unknown, which is
the truth, since no file of that name can exist.
CosmOS also boots without a disk. It reports that no filesystem was found, leaves the CosmOS also boots without a disk. It reports that no filesystem was found, leaves the
shell and memory monitor available, and refuses commands that require a mounted disk shell and memory monitor available, and refuses commands that require a mounted disk
+237 -30
View File
@@ -185,8 +185,39 @@ promptSay:
BRQ doAssemble BRQ doAssemble
promptUnknown: promptUnknown:
; Nothing matched. Saying which word was not understood is worth the four instructions: ; Nothing built in matched, so the disk is asked before anybody is told they are wrong. A
; it tells somebody who mistyped what they actually typed. ; word this shell does not know is very often the name of a program sitting right there,
; and looking costs a walk of the directory.
;
; THE BUILT-IN COMMANDS ARE TRIED FIRST AND ALWAYS WIN. Nothing that turns up on a disk
; can quietly become "dir" or "exit", which is what makes those two worth trusting at the
; moment the disk is the thing being doubted.
SETD.0 DiskReady
LDA.0
BRA promptSayUnknown ; No filesystem, so there is nothing to look through.
CALL nameProgram
SETD.0 NameOk
LDA.0
BRA promptSayUnknown ; Too long to be a file name, so it is not the name of one.
CALL loadProgram
SETD.0 LoadStatus
LDA.0
BRA runLoaded ; It loaded, and the machine is its now.
; No file of that name is not a fault. It is the ordinary case of a word this shell does
; not know, and it is reported in those words. Anything else means a file of that name IS
; there and something is wrong with it, and answering "I do not know: Snake" about a
; Snake.sbx that is sitting on the disk would send somebody looking in the wrong place.
INIB 0d2
CCF
SUB
BNQ loadFailed
promptSayUnknown:
; Saying which word was not understood is worth the four instructions: it tells somebody
; who mistyped what they actually typed.
SETD.0 Unknown SETD.0 Unknown
CALL printString CALL printString
SETD.0 CommandLine SETD.0 CommandLine
@@ -329,6 +360,93 @@ widthDone:
ADD ADD
RET RET
; ---- Making a file name out of a typed word ----
;
; Turns what was typed into the name of a file to go and look for. ".sbx" goes on the end
; unless it is already there, so that "Snake" and "Snake.sbx" both find the same file.
;
; THE EXTENSION IS WHAT MAKES A FILE REACHABLE BY NAME. Typing "notes" looks for notes.sbx
; and typing "notes.txt" looks for notes.txt.sbx, so a text file cannot be started by
; typing what it is called, whatever is inside it. Only load reaches a file by its whole
; name, which is why the monitor can still put any file at all in front of itself.
;
; NameOk is one if there is a name in ProgramName and zero if the word could not be made
; into one. A directory entry holds twenty two characters and four of those are spoken for
; by the extension, so eighteen is as long as a bare name can be. Being refused here reads
; as an unknown command, which is the truth: no file of that name can exist.
nameProgram:
RSTA
SETD.0 NameOk
STA.0 ; Not a name until it turns out to be one.
SETD.0 CommandLine
SETD.1 ProgramName
INIB 0d22 ; How much of the twenty two is left.
nameCopy:
LDA.0
BRA nameCopied
STA.1
INCD.0
INCD.1
DECB
BNB nameCopy
RET ; Twenty two characters and still going. Not a name.
nameCopied:
; DP1 is on the byte after the word, which is where an extension would go, and B is what
; is left. B is written down before anything else wants the registers.
RSTA
STA.1
SETD.0 NameLeft
STB.0
; Only a word of four characters or more can already end in ".sbx". Stepping back four to
; look at a shorter one would read whatever happens to sit in front of the buffer.
INIA 0d18
CCF
SUB
BRC nameAppend
PSHD.1
POPD.2
DPDN.2 0d4
SETD.0 SbxSuffix
nameSuffixSame:
LDA.0
BRA nameMade ; The suffix ran out with all of it matched.
LDB.2
CCF
SUB
BNQ nameAppend
INCD.0
INCD.2
BRI nameSuffixSame
nameAppend:
SETD.0 NameLeft
LDA.0
INIB 0d4
CCF
SUB
BRC nameDone ; Not four characters of room, so there is no name to be made.
; The suffix carries its own zero, so copying it to the end of the word ends the word.
SETD.0 SbxSuffix
nameSuffixCopy:
LDA.0
STA.1
BRA nameMade
INCD.0
INCD.1
BRI nameSuffixCopy
nameMade:
INIA 0x01
SETD.0 NameOk
STA.0
nameDone:
RET
; ---- load ---- ; ---- load ----
; ;
; Reads a program off the disk and puts it where its header asks to go. Nothing relocates ; Reads a program off the disk and puts it where its header asks to go. Nothing relocates
@@ -337,17 +455,78 @@ widthDone:
; ;
; The whole file is staged at 0x8000 first and then blitted into place, because where the ; The whole file is staged at 0x8000 first and then blitted into place, because where the
; pieces belong is not known until the header has been read, and the header is in the file. ; pieces belong is not known until the header has been read, and the header is in the file.
;
; The command is a thin thing over loadProgram, which is a subroutine because typing a
; program's name loads it too and neither caller should own the loading. What differs
; between them is only what a fault means: "load Snake.sbx" on a disk without it has been
; given a wrong name, and "Snake" on the same disk has typed a word this shell does not
; know.
doLoad: doLoad:
SETD.0 DiskReady
LDA.0
BRA loadNoDisk
SETD.1 TextRest SETD.1 TextRest
LDD.0.1 LDD.0.1
LDA.0 LDA.0
BRA loadNothingNamed BRA loadNothingNamed
; The name exactly as typed. load is how a file is reached by its whole name, so nothing
; is added to it and nothing is assumed about what it ends in.
SETD.1 ProgramName
INIB 0d23
CALL copyText
CALL loadProgram
SETD.0 LoadStatus
LDA.0
BNA loadFailed
SETD.0 LoadedText
CALL printString
SETD.0 LoadedEntry
CALL printWordHex
CALL newLine
BRI prompt
loadFailed:
SETD.1 LoadMessage
LDD.0.1
CALL printString
CALL newLine
BRI prompt
loadNothingNamed:
SETD.0 LoadWhat
CALL printString
CALL newLine
BRI prompt
; ---- loadProgram ----
;
; The name is in ProgramName. LoadStatus says what happened and LoadMessage names the text
; for it, because a subroutine cannot hand anything back in a register that a RET puts
; back, and here there are two things to hand back:
;
; 0 loaded, and LoadedEntry says where it starts
; 1 no filesystem on the disk
; 2 no file of that name
; 3 the disk would not read it
; 4 it is not a program
; 5 a version of the format this loader does not know
; 6 more vectors than there is room to keep
;
; Two is the one worth telling apart from the others. It is the only outcome where nothing
; was wrong with the disk or with a file, and so the only one a caller can fairly report as
; something other than a fault.
loadProgram:
RSTA
SETD.0 LoadStatus
STA.0
SETD.0 DiskReady
LDA.0
BRA loadNoDisk
SETD.0 ProgramName
CALL sbfsFind CALL sbfsFind
BNQ loadMissing BNQ loadMissing
@@ -532,38 +711,40 @@ loadVectorsCopied:
INIA 0x01 INIA 0x01
SETD.0 LoadedOk SETD.0 LoadedOk
STA.0 STA.0
RET ; LoadStatus is still the zero it was started at.
SETD.0 LoadedText ; Which of the seven happened, and where the words for it are. The two are set together so
CALL printString ; that no caller has to know both, and so that adding a way to fail cannot leave one of
SETD.0 LoadedEntry ; them behind.
CALL printWordHex
CALL newLine
BRI prompt
loadNoDisk: loadNoDisk:
INIA 0d1
SETD.0 NoDisk SETD.0 NoDisk
BRI loadComplain BRI loadRefuse
loadNothingNamed:
SETD.0 LoadWhat
BRI loadComplain
loadTooManyVectors:
SETD.0 TooManyVectors
BRI loadComplain
loadMissing: loadMissing:
INIA 0d2
SETD.0 NoSuchFile SETD.0 NoSuchFile
BRI loadComplain BRI loadRefuse
loadUnreadable: loadUnreadable:
INIA 0d3
SETD.0 Unreadable SETD.0 Unreadable
BRI loadComplain BRI loadRefuse
loadNotProgram: loadNotProgram:
INIA 0d4
SETD.0 NotProgram SETD.0 NotProgram
BRI loadComplain BRI loadRefuse
loadWrongVersion: loadWrongVersion:
INIA 0d5
SETD.0 WrongVersion SETD.0 WrongVersion
loadComplain: BRI loadRefuse
CALL printString loadTooManyVectors:
CALL newLine INIA 0d6
BRI prompt SETD.0 TooManyVectors
loadRefuse:
SETD.1 LoadMessage
STD.0.1
SETD.1 LoadStatus
STA.1
RET
; ---- delete and rename ---- ; ---- delete and rename ----
; ;
@@ -658,6 +839,11 @@ doRun:
LDA.0 LDA.0
BRA runNothing BRA runNothing
; Where typing a program's name arrives, having loaded it on the way. The argument comes
; out of TextRest either way: after "run" that is what followed the word, and after a
; program's own name it is what followed the name, which is the same thing meaning the
; same thing.
runLoaded:
MVSD.0 MVSD.0
SETD.1 SystemStack SETD.1 SystemStack
STD.0.1 STD.0.1
@@ -2538,10 +2724,11 @@ HelpText:
"dir list what is on the disk "dir list what is on the disk
load <file> read a program off the disk load <file> read a program off the disk
run [words] start what was loaded, and tell it those words run [words] start what was loaded, and tell it those words
delete <file> take it off the disk <name> [words] load and start that program off the disk"
rename <file> <to> call it something else"
HelpMoreText: HelpMoreText:
"monitor look at memory, change it, and jump into it "delete <file> take it off the disk
rename <file> <to> call it something else
monitor look at memory, change it, and jump into it
help this help this
exit stop, or leave the monitor if you are in it" exit stop, or leave the monitor if you are in it"
@@ -2556,6 +2743,11 @@ DataWord:
ExecMagic: ExecMagic:
"SBEX" "SBEX"
; What every program on the disk is called, and the four characters that make one
; reachable by typing its name.
SbxSuffix:
".sbx"
LoadWhat: LoadWhat:
"load what?" "load what?"
NoSuchFile: NoSuchFile:
@@ -2673,6 +2865,21 @@ DiskReady:
0x00 0x00
LoadedOk: LoadedOk:
0x00 0x00
; What loadProgram found, and the words for it. See loadProgram for what the numbers mean.
LoadStatus:
0x00
LoadMessage:
0x00 0x00
; The name of the file to load, which load copies out of the line as typed and a typed
; program name is built into. Twenty two characters and the zero that ends them.
ProgramName:
#Reserve 0d23
NameOk:
0x00
NameLeft:
0x00
LoadedEntry: LoadedEntry:
0x00 0x00 0x00 0x00
LoadCount: LoadCount:
+1
View File
@@ -2,6 +2,7 @@ CosmOS
> dir list what is on the disk > dir list what is on the disk
load <file> read a program off the disk load <file> read a program off the disk
run [words] start what was loaded, and tell it those words run [words] start what was loaded, and tell it those words
<name> [words] load and start that program off the disk
delete <file> take it off the disk delete <file> take it off the disk
rename <file> <to> call it something else rename <file> <to> call it something else
monitor look at memory, change it, and jump into it monitor look at memory, change it, and jump into it
+2 -2
View File
@@ -3,11 +3,11 @@ CosmOS
> two stops, and what the registers were at each > two stops, and what the registers were at each
break at 200E break at 200E
A 11 B 22 Q 00 status 00 A 11 B 22 Q 00 status 00
DP0 1030 DP1 0663 DP2 039C DP3 2000 SP FFFF DP0 1030 DP1 06C2 DP2 0000 DP3 2000 SP FFFF
press a key press a key
break at 2023 break at 2023
A 44 B 55 Q 00 status 00 A 44 B 55 Q 00 status 00
DP0 1000 DP1 0663 DP2 039C DP3 2000 SP FFF5 DP0 1000 DP1 06C2 DP2 0000 DP3 2000 SP FFF5
press a key press a key
carried on to the end carried on to the end
finished finished
+30
View File
@@ -0,0 +1,30 @@
CosmOS
> it says: hello there
finished
> it says: spelled out in full
finished
> it says: once more
finished
> Say.sbx 155
dir.sbx 155
notes.txt 21
notes.sbx 21
4 files
> not a program
> not a program
> I do not know: notes.txt
> I do not know: nosuchprogram
> I do not know: abcdefghijklmnopqr
> I do not know: abcdefghijklmnopqrs
> dir list what is on the disk
load <file> read a program off the disk
run [words] start what was loaded, and tell it those words
<name> [words] load and start that program off the disk
delete <file> take it off the disk
rename <file> <to> call it something else
monitor look at memory, change it, and jump into it
help this
exit stop, or leave the monitor if you are in it
> halted
Execution halted.
[exit 0]
+12
View File
@@ -0,0 +1,12 @@
Say hello there
Say.sbx spelled out in full
run once more
dir
notes
notes.sbx
notes.txt
nosuchprogram
abcdefghijklmnopqr
abcdefghijklmnopqrs
help
exit
+19
View File
@@ -177,6 +177,25 @@ awk 'BEGIN { for (i = 0; i < 30; i++) printf "line %02d: ABCDEFGHIJKLMNOPQRSTUVW
"$ROOT/Programs/CosmOS/Apps/More.asm" -o "$WORK/More.sbx" >/dev/null "$ROOT/Programs/CosmOS/Apps/More.asm" -o "$WORK/More.sbx" >/dev/null
"$TOOL" put "$DISKS/type.img" "$WORK/More.sbx" >/dev/null "$TOOL" put "$DISKS/type.img" "$WORK/More.sbx" >/dev/null
# A disk for invoking a program by typing its name. Four files, each there to say one
# thing about how a typed word turns into a file name.
#
# dir.sbx is a working program under the name of a built-in command, which is the only way
# to check that the built-ins really are tried first. If the search ever moved ahead of the
# dispatch chain, this disk would start answering "dir" with a program.
#
# notes.sbx is text under a program's name, so that a file that IS found and IS NOT a
# program can be told apart from a word that names nothing. notes.txt is the same text
# under its own name, which no typed word can reach: "notes.txt" looks for notes.txt.sbx.
"$TOOL" format "$DISKS/invoke.img" 64 2 >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null
"$TOOL" put "$DISKS/invoke.img" "$WORK/Say.sbx" >/dev/null
"$TOOL" put "$DISKS/invoke.img" "$WORK/Say.sbx" dir.sbx >/dev/null
printf 'this is not a program' > notes.txt
"$TOOL" put "$DISKS/invoke.img" notes.txt >/dev/null
"$TOOL" put "$DISKS/invoke.img" notes.txt notes.sbx >/dev/null
# A disk for the assembler that runs on the machine. It holds a source file and the two # 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 - # 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 # because a fault in either of those would otherwise turn up much later as a mysterious
+13
View File
@@ -324,6 +324,19 @@ cosmosType | CosmOS/Source/cosmos.asm | run | cosmosTyp
# with Space, and one line with Return. The input is intentionally packed so that the one # with Space, and one line with Return. The input is intentionally packed so that the one
# byte the pager consumes leaves the next shell command immediately behind it. # byte the pager consumes leaves the next shell command immediately behind it.
cosmosMore | CosmOS/Source/cosmos.asm | run | cosmosMore.in | - | disks/type.img cosmosMore | CosmOS/Source/cosmos.asm | run | cosmosMore.in | - | disks/type.img
# Typing a program's name starts it. The disk is built so that each line of the input asks
# a different question of the one rule: "Say hello there" is a bare name with an argument,
# "Say.sbx" is the same file spelled out in full, and "run once more" says the program that
# was invoked really did land in the loaded slot and can be started again from it.
#
# Then the four ways a word does not become a running program. "dir" is a real program on
# this disk and must still list the disk, because the built-ins are tried first. "notes"
# finds notes.sbx and says it is not a program, which is the answer that has to differ from
# "I do not know" - the file is there. "notes.txt" looks for notes.txt.sbx and finds
# nothing, which is how a text file stays unreachable by name. And the last two are the
# length boundary: eighteen characters is the longest bare name that leaves room for the
# extension, and nineteen cannot be a file name at all, so both are unknown words.
cosmosInvoke | CosmOS/Source/cosmos.asm | run | cosmosInvoke.in | - | disks/invoke.img
# The assembler's front end, checked in two pieces before anything is built on it. # 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, # cosmosSource reads a source file and prints it back. The file crosses a block boundary,