diff --git a/Programs/CosmOS/Apps/Wander.asm b/Programs/CosmOS/Apps/Wander.asm new file mode 100644 index 0000000..5d51715 --- /dev/null +++ b/Programs/CosmOS/Apps/Wander.asm @@ -0,0 +1,105 @@ +; Goes somewhere else and reads a file by a bare name once it is there. +; +; This exists to prove two things the shell promises and nothing else could test. First +; that osChangeDir works: the file it prints is named with no path at all, so the only way +; to reach it is to be standing in the right place. Second that the shell puts the working +; directory back afterwards: the prompt after this returns says where the shell was, not +; where this went. +; +; Written by Anachronaut + +#Include services.asm +#Program + #Base 0x4000 + +start: + ; Where to go is the argument. Nothing else about this program says a directory name, so + ; running it anywhere else moves it anywhere else. + SETD.0 Where + INIB 0d40 + SWI osArgument + SETD.0 Where + LDA.0 + BRA noWhere + + SETD.0 Where + SWI osChangeDir + BNQ noSuchPlace + + SETD.0 Went + SWI osPrintString + + ; And now a bare name, which means nothing until you are somewhere. + SETD.0 Bare + CALL fileStreamOpen + BNQ noFile + +wanderBlock: + CALL fileStreamNext + BNQ noFile + PSHD.3 + POPB + POPA + SETD.2 Left + STA.2 + INCD.2 + STB.2 + + SETD.2 Left + LDA.2 + INCD.2 + LDB.2 + OR + BRQ wanderDone + + SETD.0 FileStreamBlock + SETD.2 Left + INCD.2 + LDB.2 +wanderByte: + LDA.0 + OUTA 0x00 + INCD.0 + DECB + BNB wanderByte + BRI wanderBlock + +wanderDone: + SWI osExit + +noWhere: + SETD.0 NoWhereText + SWI osPrintString + SWI osExit +noSuchPlace: + SETD.0 NoPlaceText + SWI osPrintString + SWI osExit +noFile: + SETD.0 NoFileText + SWI osPrintString + SWI osExit + +#Data + #Base 0x2000 + +Where: + #Reserve 0d40 +Left: + 0x00 0x00 +Went: +"moved, and reading a bare name from there: +" +NoWhereText: +"wander where? +" +NoPlaceText: +"cannot go there +" +NoFileText: +"nothing of that name here +" +Bare: +"notes.txt" + +#Include fileStream.asm diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index 72c993c..8ea475f 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -18,8 +18,11 @@ for itself. the end of input. - SBFS Filesystem: Mount, list, read, write, delete, and rename files on a SplitBit disk. - Paths: Anywhere a filename is taken, a path may be given instead - names with `/` - between them, walked from the root, with `.` and `..`. Directories are read but not yet - made; the host tool makes them. + between them, with `.` and `..`. Directories are read but not yet made; the host tool + makes them. +- Working Directory: `cd` moves the machine, `dir` lists where it is, and the prompt says + where that is once it is not the root. A program may move too, and the shell puts the + working directory back when the program stops. - 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. @@ -88,6 +91,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. | +| `cd [path]` | Go to a directory, or to the root with nothing after it. | | ` [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. | @@ -155,11 +159,45 @@ Each *name* along a path is still the 22 characters a directory entry holds, and one is refused rather than cut short, because a name cut to 22 characters is a different name that might well be some other file's. -At this stage CosmOS **reads** directories and does not make them. There is no `cd`, no -`mkdir`, and no working directory: every path is from the root. Files a program writes go -in the root. Deleting or renaming a directory is refused - deleting one would free its -entry index, and since a parent is written as an index, the next file created would take -that index and inherit its children. +### The Working Directory: + +`cd` moves the machine. A path beginning with `/` is measured from the root and anything +else from where you are, so a bare name means a file in the current directory - which is +the whole of what a working directory is, and no program had to be told. + +```text +> cd /Apps +/Apps> dir +/Apps> cd Deep +/Apps/Deep> cd .. +/Apps> cd +> +``` + +`cd` with nothing after it goes to the root, which is the only place always there. + +**The prompt says where you are, but only when that is not the root**, so a machine nobody +has moved about on looks exactly as it always did. Nothing stores the path: the working +directory is an entry index and two bytes, and the text on the prompt is worked out again +each time by walking the chain of parents upward. + +`dir` lists the directory you are in rather than the whole disk. + +A program can move too, with `osChangeDir`, and **the shell puts the working directory +back when the program stops** - the same discipline it already applies to the Stack and to +the vector table, and for the same reason. A program is entitled to move about; the shell +is entitled to find itself where it left off. + +Whenever what a relative path means changes - a `cd`, a program calling `osChangeDir`, a +program exiting - CosmOS forgets the file it was remembering. That cache is keyed on the +path as it was typed, so `notes.txt` is the same key in two directories and nothing about +the entry it holds would look wrong. It is the kind of stale that gets believed rather +than noticed. + +At this stage CosmOS **reads** directories and does not make them: there is no `mkdir`, and +files a program writes go in the directory you are in. Deleting or renaming a directory is +refused - deleting one would free its entry index, and since a parent is written as an +index, the next file created would take that index and inherit its children. ### Starting An Application By Name: @@ -183,6 +221,13 @@ cannot be started by typing what it is called, whatever happens to be inside it. A path works here too, so `/Apps/Say hello` starts `/Apps/Say.sbx` and gives it `hello`. +**Two places are tried, in order: where you are, and then `/Apps`.** The first is what makes +a program you are working on the one that runs; the second is what lets `Snake` work from +anywhere without a copy of it in every directory. A word that already begins with `/` has +said where to look, so only that place is tried. Neither is stored anywhere, so there is +nothing to configure and nothing to go stale - a search path somebody could set would need +somewhere to live between one boot and the next, and there is no such place yet. + **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 @@ -214,6 +259,7 @@ from every assembly file in it. Several are old programs written for the bare ma | 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. | | Type | Prints a named text file a block at a time, including one too large to fit in Data Memory. | +| Wander | Goes to the directory it is given and reads a file there by a bare name. The only thing that moves the machine from inside a program, and so the only thing that can check the shell puts the working directory back afterwards. | | More | A forward-only pager. Space advances a screen, Return one line, and q stops. | ### The Monitor: @@ -332,7 +378,6 @@ fails later, in whatever part of the shell the program happened to cover. `make measures both segments against the numbers in this table, so the table is checked rather than merely written down. - Applications state their actual Program and Data addresses with `#Base`. The SplitBit assembler then writes an SBEX loadable image containing those addresses, the entry point, the segment lengths, and any vectors the application needs. CosmOS does not relocate @@ -396,6 +441,7 @@ Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, w | 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. | +| osChangeDir | DP0 names a directory. Q is zero if the machine is now in it. What a program changes here, the shell puts back when the program stops. | | 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. | diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index 29a1385..d234d57 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -79,6 +79,21 @@ bootNoDisk: ; shell. Only saying so leaves the monitor, or a program breaking the machine badly enough ; to need starting again. prompt: + ; Where you are, but only when that is not obvious. At the root the prompt is the one it + ; has always been, so a machine nobody has moved about on looks exactly as it did - and + ; every recorded test that never says "cd" keeps its recorded prompt. + SETD.0 SbfsCwd + LDA.0 + INCD.0 + LDB.0 + OR + BRQ promptMode + CALL shellPath + SETD.0 CwdAt + LDD.0.0 + CALL printString + +promptMode: SETD.0 Mode LDA.0 BRA promptPlain @@ -122,6 +137,11 @@ promptSay: CALL textSame BRQ doRun + SETD.0 CommandLine + SETD.1 CdName + CALL textSame + BRQ doCd + SETD.0 CommandLine SETD.1 DeleteName CALL textSame @@ -196,10 +216,19 @@ promptUnknown: LDA.0 BRA promptSayUnknown ; No filesystem, so there is nothing to look through. + ; TWO PLACES, TRIED IN ORDER: where you are, and then the system's own place for + ; programs. The first is what makes a program you are working on the one that runs; the + ; second is what makes Snake work from anywhere without a copy of it in every directory. + ; Neither is stored anywhere, so there is nothing to configure and nothing to go stale. + RSTA + SETD.0 NamePrefix + STA.0 + +promptSearch: CALL nameProgram SETD.0 NameOk LDA.0 - BRA promptSayUnknown ; Too long to be a file name, so it is not the name of one. + BRA promptElsewhere ; Too long to be a path, so it is not the name of one. CALL loadProgram SETD.0 LoadStatus @@ -207,14 +236,35 @@ promptUnknown: 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. + ; not know, and it is the only answer worth looking somewhere else for. 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 +promptElsewhere: + ; Was that already the second place? + SETD.0 NamePrefix + LDA.0 + BNA promptSayUnknown + + ; A word beginning with a separator has said where to look, and looking somewhere else + ; would be answering a different question from the one asked. + SETD.0 CommandLine + LDA.0 + INIB 0x2F + CCF + SUB + BRQ promptSayUnknown + + INIA 0x01 + SETD.0 NamePrefix + STA.0 + BRI promptSearch + promptSayUnknown: ; Saying which word was not understood is worth the four instructions: it tells somebody ; who mistyped what they actually typed. @@ -441,9 +491,27 @@ nameProgram: SETD.0 NameOk STA.0 ; Not a name until it turns out to be one. - SETD.0 CommandLine SETD.1 ProgramName INIB 0d59 ; What is left of the buffer, less the four for the extension. + + ; The system's own place for programs goes on the front, when that is what is being + ; tried. Putting it here rather than pasting it on afterwards is what keeps the ".sbx" + ; test below looking at the end of the whole thing. + SETD.0 NamePrefix + LDA.0 + BRA nameFromLine + SETD.0 AppsPrefix +namePrefixCopy: + LDA.0 + BRA nameFromLine + STA.1 + INCD.0 + INCD.1 + DECB + BRI namePrefixCopy + +nameFromLine: + SETD.0 CommandLine nameCopy: LDA.0 BRA nameCopied @@ -824,6 +892,224 @@ loadRefuse: STA.1 RET +; ---- osChangeDir ---- +; +; The service behind the shell's cd, and the reason the shell bothers to put the working +; directory back when a program stops: without something a program can call, that promise +; would have been about a thing that could not happen. +; +; DP0 names a directory. Q is zero if the machine is now in it. +handleChangeDir: + ; DP0 arrives holding the path, because an interrupt frame keeps the caller's registers + ; and this runs with them still in place. What it hands BACK has to be written into the + ; frame, since RETI puts every register back the way the caller had it - which is why Q + ; is stored at DP2 plus two below rather than simply being set. + SETD.2 DiskReady + LDA.2 + BRA changeDirNo + + CALL sbfsWalk + BNQ changeDirNo + + ; The root has no entry to ask about, and is always somewhere that can be stood in. + SETD.2 SbfsAt + LDA.2 + INCD.2 + LDB.2 + OR + BRQ changeDirTake + SETD.2 SbfsFoundFlags + LDA.2 + INIB 0x02 + AND + BRQ changeDirNo + +changeDirTake: + CALL fileForget ; A relative path means something else from here. + SETD.0 SbfsAt + SETD.1 SbfsCwd + CALL sbfsCopyWord + + MVSD.2 + DPUP.2 0d02 + RSTA + STA.2 ; Q is zero: the machine is there now. + RETI + +changeDirNo: + MVSD.2 + DPUP.2 0d02 + INIA 0d1 + STA.2 + RETI + +; ---- cd ---- +; +; Moves the machine. What changes is two bytes, because the working directory is an entry +; index and nothing else: no path is stored anywhere, and the one on the prompt is worked +; out again each time from the chain of parents. +; +; "cd" on its own goes to the root, which is the only place that is always there and the +; only sensible thing to mean by home on a machine with no idea who is using it. +doCd: + SETD.0 DiskReady + LDA.0 + BRA fileNoDisk + + SETD.1 TextRest + LDD.0.1 + LDA.0 + BRA cdRoot + + CALL sbfsWalk + BNQ cdNoSuch + + ; It has to be a directory to stand in. Ending at the root is fine and is the one case + ; with no entry to ask, since the root is not an entry. + SETD.0 SbfsAt + LDA.0 + INCD.0 + LDB.0 + OR + BRQ cdTake + SETD.0 SbfsFoundFlags + LDA.0 + INIB 0x02 + AND + BRQ cdNotDirectory + +cdTake: + ; WHAT A RELATIVE PATH MEANS HAS JUST CHANGED, so the remembered file goes. It is keyed + ; on the path as somebody typed it, and "notes.txt" is a different file from here than + ; it was a moment ago. Nothing about the entry it remembers has changed, which is what + ; makes this the kind of stale that is believed rather than noticed. + CALL fileForget + + SETD.0 SbfsAt + SETD.1 SbfsCwd + CALL sbfsCopyWord + BRI prompt + +cdRoot: + CALL fileForget + SETD.0 SbfsCwd + RSTA + STA.0 + INCD.0 + STA.0 + BRI prompt + +cdNoSuch: + SETD.0 NoSuchFile + BRI fileComplain +cdNotDirectory: + SETD.0 NotDirectory + BRI fileComplain + +; ---- Writing out where the machine is ---- +; +; Builds the working directory's path into CwdText and leaves CwdAt pointing at where it +; begins. Nothing stores a path, so this walks up the chain of parents. +; +; WRITTEN BACKWARDS, from the end of the buffer towards the front, because that is the +; order the names arrive in and reversing them afterwards would want somewhere to put them +; in the meantime. What comes back is a pointer into the middle of the buffer rather than +; to the front of it, which costs nothing to print from. +shellPath: + ; The end of the buffer, holding the zero that ends the string. + SETD.0 CwdText + DPUP.0 0d126 + RSTA + STA.0 + SETD.1 CwdAt + STD.0.1 + + ; Where the walk up starts. + SETD.0 SbfsCwd + SETD.1 CwdWalk + CALL sbfsCopyWord + +shellPathStep: + SETD.0 CwdWalk + LDA.0 + INCD.0 + LDB.0 + OR + BRQ shellPathDone ; The root, which is where every path begins. + + SETD.0 CwdWalk + SETD.1 SbfsTarget + CALL sbfsCopyWord + SETD.0 SbfsTarget + CALL sbfsBackWord + CALL sbfsAtIndex + BNQ shellPathDone ; The disk would not read, so say as much as is known. + + ; The name goes in front of what is there, and a separator in front of that. + SETD.0 SbfsName + CALL shellPathPrepend + + SETD.0 SbfsUpParent + SETD.1 CwdWalk + CALL sbfsCopyWord + BRI shellPathStep + +shellPathDone: + ; A machine at the root has written nothing at all, and the path to the root is the + ; separator on its own. + SETD.0 CwdAt + LDD.1.0 + LDA.1 + BNA shellPathReady + SETD.0 Separator + CALL shellPathPrepend +shellPathReady: + RET + +; DP0 names a string. Puts it in front of what CwdAt points at, with a separator before +; it, and moves CwdAt back over the lot. +; +; The string has to be measured before it can be written, since it is written from its +; last character backwards. Nothing here is long enough for that to be worth avoiding. +shellPathPrepend: + SETD.1 PathLength + RSTA + STA.1 +pathMeasure: + LDA.0 + BRA pathMeasured + SETD.1 PathLength + LDA.1 + INCA + STA.1 + INCD.0 + BRI pathMeasure +pathMeasured: + ; DP0 is on the zero at the end. Step back onto the last character, unless there is not + ; one, in which case only the separator goes in. + SETD.1 CwdAt + LDD.2.1 ; DP2 is where the string already begins. + +pathBack: + SETD.1 PathLength + LDA.1 + BRA pathSeparator + DECA + STA.1 + DECD.0 + DECD.2 + LDA.0 + STA.2 + BRI pathBack + +pathSeparator: + DECD.2 + INIA 0x2F + STA.2 + SETD.1 CwdAt + STD.2.1 + RET + ; ---- delete and rename ---- ; ; The two things a disk needs that reading and writing do not provide, and the two that @@ -943,6 +1229,11 @@ runLoaded: SETD.1 SystemStack STD.0.1 + ; And where the machine is, so that the exit handler has something to put back. + SETD.0 SbfsCwd + SETD.1 SavedCwd + CALL sbfsCopyWord + ; Whatever followed the word "run" is kept where the program can ask for it. Copied ; rather than pointed at, because what it is pointing at is the line the shell typed ; into, and a program is entitled to outlive the shell's opinion of that. @@ -1700,6 +1991,15 @@ handleExit: ; an interrupt at whatever those addresses hold next. CALL removeVectors + ; And where the machine was before the program had it, for the same reason and by the + ; same discipline as the Stack above and the vectors just now: a program is entitled to + ; move about, and the shell is entitled to find itself where it left off. What a relative + ; path means has changed back, so the remembered file goes with it. + SETD.0 SavedCwd + SETD.1 SbfsCwd + CALL sbfsCopyWord + CALL fileForget + ; The console goes back to how the shell wants it, whatever the program left it in: line ; mode, and not interrupting. A program that wanted either is expected to put it back ; itself, but one that stopped early, or forgot, would otherwise hand back a shell with @@ -2789,6 +3089,9 @@ doHelp: SETD.0 HelpText CALL printString CALL newLine + SETD.0 HelpCdText + CALL printString + CALL newLine SETD.0 HelpMoreText CALL printString CALL newLine @@ -2817,6 +3120,16 @@ Farewell: "halted" DirectoryText: "" +NotDirectory: +"that is not a directory" +Separator: +"/" + +; Where a program is looked for when it is not where you are. One fixed place rather than a +; list somebody sets, because a list would need somewhere to live between one boot and the +; next, and there is no such place yet. +AppsPrefix: +"/Apps/" IsDirectory: "that is a directory" AndText: @@ -2836,7 +3149,9 @@ 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 - [words] load and start that program off the disk" + [words] look where you are and then in /Apps, and start that" +HelpCdText: +"cd [path] go to a directory, or to the root with nothing after it" HelpMoreText: "delete take it off the disk rename call it something else @@ -2968,11 +3283,29 @@ DeleteName: "delete" RenameName: "rename" +CdName: +"cd" HelpName: "help" ExitName: "exit" +; Where the machine is, written out for the prompt, and where in the buffer it begins. +; Built from the end backwards, so it starts somewhere in the middle. +CwdText: + #Reserve 0d127 +CwdAt: + 0x00 0x00 +CwdWalk: + 0x00 0x00 +PathLength: + 0x00 + +; What the working directory was when a program was started, so that it can be put back +; when the program stops. +SavedCwd: + 0x00 0x00 + DirFolders: 0x00 DirTaken: @@ -3000,6 +3333,8 @@ ProgramName: #Reserve 0d64 NameOk: 0x00 +NamePrefix: + 0x00 NameLeft: 0x00 LoadedEntry: @@ -3224,6 +3559,7 @@ CommandLine: osFileRename handleFileRename osFileInfo handleFileInfo osFileBlock handleFileBlock + osChangeDir handleChangeDir osPrintNumber handlePrintNumber osBreak handleBreak Device 0x20 diskDone diff --git a/Programs/CosmOS/Source/sbfs.asm b/Programs/CosmOS/Source/sbfs.asm index 4a48b18..e4b9c66 100644 --- a/Programs/CosmOS/Source/sbfs.asm +++ b/Programs/CosmOS/Source/sbfs.asm @@ -104,35 +104,95 @@ sbfsGeometry: ; ---- Finding something by path ---- ; -; DP0 points at a path ending in a zero byte: names with '/' between them, walked from the -; root. Q is zero if it was found, and then SbfsFileStart, SbfsFileBlocks and SbfsFileTail +; DP0 points at a path ending in a zero byte: names with '/' between them. A path that +; begins with a separator is walked from the root, and anything else from SbfsCwd, which +; is where the machine currently is. +; +; Q is zero if it was found, and then SbfsFileStart, SbfsFileBlocks and SbfsFileTail ; describe it, SbfsFoundFlags says what kind of thing it is, and DP3 is left on the entry ; with SbfsBlock on the directory block it came out of - which is what deleting and ; renaming need in order to change it and put it back. ; ; A BARE NAME IS A PATH OF ONE NAME, so everything written before there were directories -; still works and still costs one walk of the directory. +; still works and still costs one walk of the directory. It now means a name in the +; working directory rather than a name in the root, which is the whole of what a working +; directory is, and no caller had to be told. ; ; Each name is looked for among the entries whose parent is where the walk has got to. ; There is no list of children anywhere: being a child is a fact written in the child, so ; finding them means looking at all of them. That is the same walk the flat version did ; with one more thing compared, which is why a flat disk costs what it always did - and on ; a version one disk every entry says "the root", so the comparison is free and true. +; +; sbfsFind is sbfsWalk with one thing added: the root is not a file. The two are separate +; because moving about wants the other answer - "cd /" and "cd .." both end at the root +; quite legitimately, and would have no way to say so through a routine that calls it a +; failure. sbfsFind: + CALL sbfsWalk + BNQ sbfsFindNoWalk + + ; Did the walk get anywhere at all? The root is a place, but it is not a file. + SETD.0 SbfsAt + LDA.0 + INCD.0 + LDB.0 + OR + BRQ sbfsFindRoot + RSTA + RSTB + CCF + ADD ; Q is zero: found. +sbfsFindNoWalk: + RET + +sbfsFindRoot: + RSTA + INIB 0d1 + CCF + ADD + RET + +; The walk itself. Q is zero if the whole path was walked, and SbfsAt says where it ended - +; which may be the root, and that is an answer rather than a failure. +sbfsWalk: SETD.1 SbfsPathAt STD.0.1 - ; The walk starts at the root, which is zero: a parent is an entry index PLUS ONE, and - ; the root is not an entry. + ; Where it starts. A path beginning with a separator is measured from the root, which is + ; zero because a parent is an entry index PLUS ONE and the root is not an entry. + ; Anything else is measured from wherever the machine already is. + LDA.0 + INIB 0x2F + CCF + SUB + BRQ sbfsWalkFromRoot + + SETD.0 SbfsCwd + SETD.1 SbfsAt + CALL sbfsCopyWord + + ; Where the machine is, is a directory - it could not have been got to otherwise - but + ; nothing here has read its entry, so what describes it is whatever the last find left + ; behind. Say the one thing about it that the walk needs, and mark the rest as not to be + ; believed, exactly the way going up does. + INIA 0x02 + SETD.0 SbfsFoundFlags + STA.0 + RSTA + SETD.0 SbfsFieldsValid + STA.0 + BRI sbfsFindName + +sbfsWalkFromRoot: SETD.0 SbfsAt RSTA STA.0 INCD.0 STA.0 - ; Nothing described yet. An empty path comes back as missing, which is right: the root - ; is not a file. + ; Nothing described yet. An empty path comes back as the root, which is right. RSTA SETD.0 SbfsFieldsValid STA.0 @@ -224,13 +284,13 @@ sbfsFindUp: BRI sbfsFindName sbfsFindEnd: - ; Did the walk get anywhere at all? The root is not a file. + ; Ending at the root is a perfectly good answer here, and there is nothing to describe. SETD.0 SbfsAt LDA.0 INCD.0 LDB.0 OR - BRQ sbfsFindMissing + BRQ sbfsFindGood SETD.0 SbfsFieldsValid LDA.0 @@ -442,6 +502,21 @@ sbfsScanFound: ADD ; Q is zero: found. RET +; DP2 is on an entry. Q is zero if it lives in the directory the machine is in. The same +; comparison sbfsMatchParent makes, against the other of the two places a walk can be. +sbfsWalkHere: + PSHD.2 + POPD.0 + DPUP.0 0d28 + SETD.2 SbfsCwd + CALL sbfsSameByte + BNQ sbfsWalkHereDone + INCD.0 + INCD.2 + CALL sbfsSameByte +sbfsWalkHereDone: + RET + ; DP2 is on an entry. Q is zero if it lives in the directory the walk is looking in. ; ; On a version one disk these two bytes are zero in every entry, and the walk starts at @@ -534,6 +609,30 @@ sbfsAtFound: PSHD.2 POPD.3 CALL sbfsTakeEntry + + ; And the name, which only this way of arriving at an entry needs: walking up a chain of + ; parents to write out where the machine is wants the name at every step. Copied here + ; rather than in sbfsTakeEntry, because every find in the system goes through that one + ; and none of them wants twenty two bytes moved on its behalf. + PSHD.3 + POPD.0 + DPUP.0 0d06 + SETD.1 SbfsName + INIA 0d22 + SETD.2 SbfsCount + STA.2 +sbfsAtName: + LDA.0 + STA.1 + INCD.0 + INCD.1 + LDA.2 + DECA + STA.2 + BNA sbfsAtName + RSTA + STA.1 ; The twenty third byte, which makes it a string. + RSTA RSTB CCF @@ -697,6 +796,13 @@ sbfsWalkEntry: AND BRQ sbfsWalkScan + ; And is it in the directory the machine is in? The entries of every directory on the + ; disk are mixed together in one array - being a child is a fact written in the child, + ; not a list kept anywhere - so a walk that wants one directory has to say which, and + ; everything else is stepped over on the way past. + CALL sbfsWalkHere + BNQ sbfsWalkScan + CALL sbfsWalkTake RSTA RSTB @@ -1770,6 +1876,19 @@ SbfsMatchLeft: 0x00 SbfsLeft: 0x00 +; ---- Where the machine is ---- +; +; The working directory, written the way a parent is written: an entry index plus one, so +; zero is the root and a freshly booted machine is already there. A path that does not +; begin with a separator is measured from here. +; +; It lives here rather than in the shell because it is what a relative path MEANS, and the +; thing that resolves a path is here. Keeping it in the shell would mean either handing it +; down on every call or having the shell paste it onto the front of every name, and the +; second of those is how a name that is already absolute gets ruined. +SbfsCwd: + 0x00 0x00 + ; ---- What walking a path keeps ---- ; ; SbfsAt is where the walk has got to, written the way a parent is written: an entry index diff --git a/Programs/CosmOS/Source/services.asm b/Programs/CosmOS/Source/services.asm index 42e059f..3feb963 100644 --- a/Programs/CosmOS/Source/services.asm +++ b/Programs/CosmOS/Source/services.asm @@ -68,6 +68,19 @@ ; 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. + +; ---- Moving about ---- +; +; DP0 names a directory. Q is zero if the machine is now in it. +; +; WHAT A PROGRAM CHANGES HERE, THE SHELL PUTS BACK when the program stops - the same +; discipline the Stack and the vector table are held to, and for the same reason. A program +; is entitled to move about; the shell is entitled to find itself where it left off. +; +; This is what makes a bare name mean something to a program: everything a program opens is +; relative to here, so a program given a directory to work in can say "notes.txt" and mean +; the one in it. + osChangeDir 0d28 ; 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, diff --git a/Tests/expected/cosmos.out b/Tests/expected/cosmos.out index 6677c71..882fbcb 100644 --- a/Tests/expected/cosmos.out +++ b/Tests/expected/cosmos.out @@ -2,7 +2,8 @@ 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 + [words] look where you are and then in /Apps, and start that +cd [path] go to a directory, or to the root with nothing after it 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 a7de6f1..452b8d9 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 400E A 11 B 22 Q 00 status 00 -DP0 2030 DP1 0730 DP2 0000 DP3 4000 SP FFFF +DP0 2030 DP1 082E DP2 0000 DP3 4000 SP FFFF press a key break at 4023 A 44 B 55 Q 00 status 00 -DP0 2000 DP1 0730 DP2 0000 DP3 4000 SP FFF5 +DP0 2000 DP1 082E DP2 0000 DP3 4000 SP FFF5 press a key carried on to the end finished diff --git a/Tests/expected/cosmosCwd.out b/Tests/expected/cosmosCwd.out new file mode 100644 index 0000000..6616087 --- /dev/null +++ b/Tests/expected/cosmosCwd.out @@ -0,0 +1,36 @@ +CosmOS +> Apps +A +B +0 files, 3 directories +> /A> Say.sbx 52 +notes.txt 25 +2 files +/A> these are the notes in A +finished +/A> /B> and these are the very different notes in B +finished +/B> it says: reached the one in Apps +finished +/B> /A> Hello, World! +finished +/A> > Apps +A +B +0 files, 3 directories +> /B> these are the notes in A +finished +/B> moved, and reading a bare name from there: +these are the notes in A +finished +/B> and these are the very different notes in B +finished +/B> no such file +/B> that is not a directory +/B> > Apps +A +B +0 files, 3 directories +> halted +Execution halted. +[exit 0] diff --git a/Tests/expected/cosmosInvoke.out b/Tests/expected/cosmosInvoke.out index 523df27..242062c 100644 --- a/Tests/expected/cosmosInvoke.out +++ b/Tests/expected/cosmosInvoke.out @@ -19,7 +19,8 @@ notes.sbx 21 > 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 + [words] look where you are and then in /Apps, and start that +cd [path] go to a directory, or to the root with nothing after it 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/cosmosTree.out b/Tests/expected/cosmosTree.out index f5941af..ddbc56b 100644 --- a/Tests/expected/cosmosTree.out +++ b/Tests/expected/cosmosTree.out @@ -1,10 +1,7 @@ CosmOS > Apps -Deep -Say.sbx 155 -Say.sbx 52 rooted.txt 21 -3 files, 2 directories +1 file, 1 directory > loaded, starting at 4000 > it says: the shallow one finished @@ -26,10 +23,8 @@ finished > that is a directory > gone > Apps -Deep -Say.sbx 155 rooted.txt 21 -2 files, 2 directories +1 file, 1 directory > halted Execution halted. [exit 0] diff --git a/Tests/expected/cosmosTreeWrite.out b/Tests/expected/cosmosTreeWrite.out index 8863103..43c42ef 100644 --- a/Tests/expected/cosmosTreeWrite.out +++ b/Tests/expected/cosmosTreeWrite.out @@ -1,17 +1,15 @@ CosmOS > Folder -Inner Edit.sbx 1996 -1 file, 2 directories +1 file, 1 directory > loaded, starting at 4000 > note.txt, 0 lines > : : : > written, 67 bytes > finished > Folder -Inner Edit.sbx 1996 note.txt 67 -2 files, 2 directories +2 files, 1 directory > halted Execution halted. [exit 0] diff --git a/Tests/input/cosmosCwd.in b/Tests/input/cosmosCwd.in new file mode 100644 index 0000000..4aa061b --- /dev/null +++ b/Tests/input/cosmosCwd.in @@ -0,0 +1,20 @@ +dir +cd /A +dir +Type notes.txt +cd /B +Type notes.txt +Say reached the one in Apps +cd /A +Say reached the one in A +cd .. +dir +cd /B +Type ../A/notes.txt +Wander /A +Type notes.txt +cd nosuchplace +cd notes.txt +cd / +dir +exit diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index 61fe847..41ab092 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -219,6 +219,37 @@ awk 'BEGIN { for (i = 0; i < 30; i++) printf "line %02d: ABCDEFGHIJKLMNOPQRSTUVW printf 'this is not a program' > rooted.txt "$TOOL" put "$DISKS/tree.img" rooted.txt >/dev/null +# A disk for moving about on. Two directories hold a file of THE SAME NAME with different +# text in it, which is the fixture the working directory needs: "notes.txt" has to mean a +# different file from each of them, and the only way to see that it does is for the two to +# say different things. +# +# It is also what checks the remembered file is dropped when the machine moves. That cache +# is keyed on the path as somebody typed it, so "notes.txt" is the same key in both places +# and nothing about the entry it remembers looks wrong - it is the kind of stale that is +# believed rather than noticed. +# +# Type and Say go in /Apps, so that a program can be started from anywhere; hello goes in +# /A under the name Say.sbx, so that where you are is visibly tried before /Apps. +"$TOOL" format "$DISKS/cwd.img" 256 4 >/dev/null +"$TOOL" mkdir "$DISKS/cwd.img" /Apps >/dev/null +"$TOOL" mkdir "$DISKS/cwd.img" /A >/dev/null +"$TOOL" mkdir "$DISKS/cwd.img" /B >/dev/null +"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ + "$ROOT/Programs/CosmOS/Apps/Type.asm" -o "$WORK/Type.sbx" >/dev/null +"$TOOL" put "$DISKS/cwd.img" "$WORK/Type.sbx" /Apps/Type.sbx >/dev/null +"$TOOL" put "$DISKS/cwd.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null +"$TOOL" put "$DISKS/cwd.img" "$WORK/treeHello.sbx" /A/Say.sbx >/dev/null +printf 'these are the notes in A\n' > notesA.txt +printf 'and these are the very different notes in B\n' > notesB.txt +"$TOOL" put "$DISKS/cwd.img" notesA.txt /A/notes.txt >/dev/null +"$TOOL" put "$DISKS/cwd.img" notesB.txt /B/notes.txt >/dev/null +# Wander is the only thing that can move the machine from inside a program, which makes it +# the only thing that can check the shell puts the working directory back afterwards. +"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ + "$ROOT/Programs/CosmOS/Apps/Wander.asm" -o "$WORK/Wander.sbx" >/dev/null +"$TOOL" put "$DISKS/cwd.img" "$WORK/Wander.sbx" /Apps/Wander.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. # diff --git a/Tests/manifest b/Tests/manifest index 79f96da..4728193 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -343,6 +343,25 @@ cosmosTree | CosmOS/Source/cosmos.asm | run | cosmosTre # hold no blocks and must therefore be in nobody's way when a run of free ones is wanted. # The listing afterwards says where it landed and how big it is. cosmosTreeWrite | CosmOS/Source/cosmos.asm | run | cosmosTreeWrite.in | - | disks/treewrite.img +# The working directory. cd moves the machine, the prompt says where it is once that is +# not the root, and dir lists one directory rather than the whole disk. +# +# The two notes.txt say different things ON PURPOSE, and that is the whole test: reading +# "notes.txt" from /A and then from /B has to give two different files. If the remembered +# file were not dropped when the machine moves, the second read would hand back the first +# one - same key, same entry, nothing about it looking wrong. +# +# Then that a program is looked for where you are BEFORE /Apps: /A holds a Say.sbx that is +# really hello, so "Say" from /A prints the wrong thing on purpose, and "Say" from /B has +# to reach the real one in /Apps. +# +# And the two things only a program moving the machine itself can check. Wander calls +# osChangeDir, so after "Wander /A" from /B the prompt has to say /B again - the shell puts +# the working directory back the way it puts the Stack and the vector table back. Reading +# notes.txt straight afterwards has to give B's copy, which is the remembered file being +# dropped when the machine moved. Without Wander neither of those could be made to fail, +# because nothing else on the machine can move it. +cosmosCwd | CosmOS/Source/cosmos.asm | run | cosmosCwd.in | - | disks/cwd.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 @@ -381,6 +400,7 @@ app-Edit | CosmOS/Apps/Edit.asm | assemble | - app-Files | CosmOS/Apps/Files.asm | assemble | - | - app-Stream | CosmOS/Apps/Stream.asm | assemble | - | - app-Type | CosmOS/Apps/Type.asm | assemble | - | - +app-Wander | CosmOS/Apps/Wander.asm | assemble | - | - app-More | CosmOS/Apps/More.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.