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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user