diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index 413c323..cab94b7 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -20,6 +20,8 @@ for itself. - 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 point. +- Invocation By Name: A word the shell has no command for is looked for on the disk as + `.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 command arguments, read and write files, and return to the shell through named software interrupts. @@ -83,6 +85,7 @@ CosmOS currently provides these built-in commands: | `dir` | List the files on the mounted disk and their sizes. | | `load ` | 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. | +| ` [words]` | Any word the shell does not recognise is looked for on the disk as `.sbx`, and loaded and started if it is there. | | `delete ` | Remove a file from the filesystem and release its blocks. | | `rename ` | 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. | @@ -115,9 +118,47 @@ For example: > run ``` -Loading and running are separate operations for now. 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. +Or, equivalently: + +```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 shell and memory monitor available, and refuses commands that require a mounted disk diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index eed37c7..5c31602 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -185,8 +185,39 @@ promptSay: BRQ doAssemble promptUnknown: - ; Nothing matched. Saying which word was not understood is worth the four instructions: - ; it tells somebody who mistyped what they actually typed. + ; Nothing built in matched, so the disk is asked before anybody is told they are wrong. A + ; 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 CALL printString SETD.0 CommandLine @@ -329,6 +360,93 @@ widthDone: ADD 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 ---- ; ; 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 ; 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: - SETD.0 DiskReady - LDA.0 - BRA loadNoDisk - SETD.1 TextRest LDD.0.1 LDA.0 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 BNQ loadMissing @@ -532,38 +711,40 @@ loadVectorsCopied: INIA 0x01 SETD.0 LoadedOk STA.0 + RET ; LoadStatus is still the zero it was started at. - SETD.0 LoadedText - CALL printString - SETD.0 LoadedEntry - CALL printWordHex - CALL newLine - BRI prompt - +; Which of the seven happened, and where the words for it are. The two are set together so +; that no caller has to know both, and so that adding a way to fail cannot leave one of +; them behind. loadNoDisk: + INIA 0d1 SETD.0 NoDisk - BRI loadComplain -loadNothingNamed: - SETD.0 LoadWhat - BRI loadComplain -loadTooManyVectors: - SETD.0 TooManyVectors - BRI loadComplain + BRI loadRefuse loadMissing: + INIA 0d2 SETD.0 NoSuchFile - BRI loadComplain + BRI loadRefuse loadUnreadable: + INIA 0d3 SETD.0 Unreadable - BRI loadComplain + BRI loadRefuse loadNotProgram: + INIA 0d4 SETD.0 NotProgram - BRI loadComplain + BRI loadRefuse loadWrongVersion: + INIA 0d5 SETD.0 WrongVersion -loadComplain: - CALL printString - CALL newLine - BRI prompt + BRI loadRefuse +loadTooManyVectors: + INIA 0d6 + SETD.0 TooManyVectors +loadRefuse: + SETD.1 LoadMessage + STD.0.1 + SETD.1 LoadStatus + STA.1 + RET ; ---- delete and rename ---- ; @@ -658,6 +839,11 @@ doRun: LDA.0 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 SETD.1 SystemStack STD.0.1 @@ -2538,10 +2724,11 @@ HelpText: "dir list what is on the disk load read a program off the disk run [words] start what was loaded, and tell it those words -delete take it off the disk -rename call it something else" + [words] load and start that program off the disk" HelpMoreText: -"monitor look at memory, change it, and jump into it +"delete take it off the disk +rename 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" @@ -2556,6 +2743,11 @@ DataWord: ExecMagic: "SBEX" + +; What every program on the disk is called, and the four characters that make one +; reachable by typing its name. +SbxSuffix: +".sbx" LoadWhat: "load what?" NoSuchFile: @@ -2673,6 +2865,21 @@ DiskReady: 0x00 LoadedOk: 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: 0x00 0x00 LoadCount: diff --git a/Tests/expected/cosmos.out b/Tests/expected/cosmos.out index 7cf852a..6677c71 100644 --- a/Tests/expected/cosmos.out +++ b/Tests/expected/cosmos.out @@ -2,6 +2,7 @@ CosmOS > dir list what is on the disk load read a program off the disk run [words] start what was loaded, and tell it those words + [words] load and start that program off the disk delete take it off the disk rename call it something else monitor look at memory, change it, and jump into it diff --git a/Tests/expected/cosmosBreak.out b/Tests/expected/cosmosBreak.out index 1d0bf2e..bdbe709 100644 --- a/Tests/expected/cosmosBreak.out +++ b/Tests/expected/cosmosBreak.out @@ -3,11 +3,11 @@ CosmOS > 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 +DP0 1030 DP1 06C2 DP2 0000 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 +DP0 1000 DP1 06C2 DP2 0000 DP3 2000 SP FFF5 press a key carried on to the end finished diff --git a/Tests/expected/cosmosInvoke.out b/Tests/expected/cosmosInvoke.out new file mode 100644 index 0000000..523df27 --- /dev/null +++ b/Tests/expected/cosmosInvoke.out @@ -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 read a program off the disk +run [words] start what was loaded, and tell it those words + [words] load and start that program off the disk +delete take it off the disk +rename 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] diff --git a/Tests/input/cosmosInvoke.in b/Tests/input/cosmosInvoke.in new file mode 100644 index 0000000..bb04af6 --- /dev/null +++ b/Tests/input/cosmosInvoke.in @@ -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 diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index 0429c85..e2a19c2 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -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 "$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 # 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 diff --git a/Tests/manifest b/Tests/manifest index 892c8bc..e3e7b7d 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -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 # byte the pager consumes leaves the next shell command immediately behind it. 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. # # cosmosSource reads a source file and prints it back. The file crosses a block boundary,