D3: the machine knows where it is
cd moves it, dir lists the directory it is in, and the prompt says which one - but only when that is not the root, so a machine nobody has moved about on looks exactly as it always did and every recorded test that never says "cd" keeps its recorded prompt. A path beginning with a separator is measured from the root and anything else from where the machine is, so a bare name means a file in the current directory. NO PROGRAM HAD TO BE TOLD: the working directory lives in sbfs.asm beside the thing that resolves paths, because it is what a relative path MEANS. Keeping it in the shell would have meant either handing it down on every call or pasting it onto the front of every name, and the second of those is how a name that is already absolute gets ruined. Nothing stores the path. The working directory is an entry index and two bytes, and the text on the prompt is built each time by walking the chain of parents upward, writing names from the end of a buffer towards the front - which is the order they arrive in, and saves reversing them afterwards. sbfsFind splits into a walk and a check. "cd /" and "cd .." both end at the root quite legitimately, and had no way to say so through a routine whose only word for the root was "missing". Typing a program's name now tries two places in order: where you are, then /Apps. The first makes a program you are working on the one that runs; the second lets Snake work from anywhere. A word already beginning with a separator has said where to look, so only that place is tried. osChangeDir exists so that "a program may move about, and the shell puts the working directory back" is a thing that can happen rather than a promise about nothing. Both halves of that were unfalsifiable without it: with no way for a program to move, removing the restore changed no test. Wander is the program that moves - it goes where it is told and reads a file there by a bare name - and with it on the disk, removing the restore fails. The remembered file is dropped whenever what a relative path means changes: a cd, a program calling osChangeDir, a program exiting. Removing all of them fails the test and removing any one of them does not, because today every path into that cache belongs to a program that exits. It is kept in all three because the cost is a call and the failure is a file's blocks being handed out under another file's name. The cwd fixture holds two files called notes.txt saying different things, and a Say.sbx in /A that is really hello. Two copies of one program, or two copies of one file, would have passed with the whole of this deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
588e02aff5
commit
36ce9f6ccf
@@ -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
|
||||
@@ -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 <path>` | 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. |
|
||||
| `<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. |
|
||||
| `rename <file> <to>` | 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. |
|
||||
|
||||
|
||||
@@ -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:
|
||||
"<dir>"
|
||||
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 <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"
|
||||
<name> [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 <file> take it off the disk
|
||||
rename <file> <to> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user