Tab reaches the disk

Paths and programs, which is the half that makes it worth having. The first word
of a line is a command or a PROGRAM, offered under the name somebody would type
- the extension taken off - and anything after it is a file, offered as it
really is. A separator anywhere in the word says which directory to look in.

A directory answers with a separator on the end instead of a space, which says
what it is and lets the next part be typed straight away. The answer ending in
one is also what stops a space being added, so that is one test rather than a
flag.

PROGRAMS ARE LOOKED FOR WHERE THE SHELL WOULD LOOK to run one: where you are,
/Apps on the disk you are on, and /Apps on drive 0. Offering something the shell
would not find would be finishing a word into a thing that then does not work.
Drive 0's is skipped when that is already the drive, or every program in it
would be offered twice and nothing would ever be the only match.

Walking somebody else's directory means standing in it, which is the only way to
walk one here, so where the person was and which drive they were on are put down
first and restored whatever happens.

Three bugs, all found by running it:

THE DIRECTORY TEST WAS INVERTED. dir asks the same question the same way round
four hundred lines further up, which is what made it obvious once looked at.

THE /Apps WALK OVERWROTE THE TYPED PATH. The whole search runs a second time to
list the matches, and by then TabDir said "/Apps" - so a word that had named
nowhere went looking in the wrong place and listed nothing at all. Two ways into
the walk now, and the typed path is never written over.

AND LISTING ONLY KNEW ABOUT COMMANDS, because it was a second copy of the walk.
It is the same walk with a flag now: finding the answer and showing the matches
are the same question asked twice.

Also cosmosMonitor, which had been RE-BLESSED INTO MEANINGLESSNESS by the wall
move. It disassembles a loaded program, at an address the input names - and that
address moved a page while the recording was simply re-recorded to whatever came
out, which was a page of zeroes. It is pointed at 5000 again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-09-01 18:10:40 -04:00
co-authored by Claude Opus 5
parent bb065fe221
commit 3c76934a9a
9 changed files with 551 additions and 132 deletions
+463 -84
View File
@@ -1538,7 +1538,7 @@ tabComplete:
tabBack:
SETD.1 TabWordAt
LDA.1
BRA tabIsFirst ; The start of the line is the start of the word, and of the line.
BRA tabAtLineStart ; The start of the line is the start of the word, and of the line.
DECA
CALL tabCharAt
MVQA
@@ -1551,10 +1551,19 @@ tabBack:
STA.1
BRI tabBack
tabAtLineStart:
INIA 0x01
SETD.1 TabFirst
STA.1
BRI tabIsFirst
tabFoundStart:
; There is a space behind the word, so something may be in front of that space. Only a run
; of spaces means this is still the first word; anything else is an argument, and finishing
; one of those is not built yet.
; of spaces means this is still the first word - and which word it is decides what the word
; could BE: a command or a program at the front of a line, a file anywhere after it.
INIA 0x01
SETD.1 TabFirst
STA.1
SETD.1 TabWordAt
LDA.1
SETD.1 TabLeft
@@ -1569,8 +1578,10 @@ tabFirstCheck:
MVQA
INIB 0x20
XOR
BNQ tabDone ; Not a space, so a word came before this one.
BRI tabFirstCheck
BRQ tabFirstCheck
RSTA
SETD.1 TabFirst
STA.1 ; A word came before this one, so this is an argument.
tabIsFirst:
; How much of it is typed. Nothing at all is not a question anybody asked - it would offer
@@ -1592,7 +1603,22 @@ tabIsFirst:
SETD.1 TabBestLen
STA.1
CALL tabTryNames
; A path in the word says which directory to look in, and narrows what is being matched to
; the part after the last separator.
CALL tabSplit
; ---- What this word could be ----
;
; The shell's own commands, only at the front of a line and only when no directory has been
; named: nothing with a separator in it was ever going to be "dir".
;
; And on the disk: programs at the front of a line, since that is all a first word can
; usefully be, and anything at all after it.
SETD.1 TabFirst
LDA.1
SETD.1 TabPrograms
STA.1
CALL tabRunSources
SETD.1 TabCount
LDA.1
@@ -1641,11 +1667,24 @@ tabPutDone:
XOR
BNQ tabAmbiguous
; Only one thing it can be, so it is finished and a space says so.
; Only one thing it can be, so it is finished and a space says so - unless it is a
; directory, which answered with a separator on the end and is somewhere to carry on typing
; rather than something to have finished.
SETD.3 TabBest
SETD.1 TabBestLen
LDA.1
DECA
DPUA.3
LDA.3
INIB 0x2F
XOR
BRQ tabSaidDirectory
INIA 0x20
SETD.1 EditChar
STA.1
CALL editPut
tabSaidDirectory:
CALL editRedraw
BRI tabDone
@@ -1663,6 +1702,20 @@ tabAmbiguous:
tabDone:
BRI editKey
; Everywhere a candidate can come from, in the order they are tried. Run once to find the
; answer and again to show the matches, which is why it is a routine and not a run of code.
tabRunSources:
SETD.1 TabFirst
LDA.1
BRA tabNoNames
SETD.1 TabDirLen
LDA.1
BNA tabNoNames
CALL tabTryNames
tabNoNames:
CALL tabTryFiles
RET
; A holds an offset into the line. Q is the character there.
tabCharAt:
SETD.1 EditBase
@@ -1725,6 +1778,15 @@ tabOfferMatch:
BRI tabOfferMatch
tabOfferTake:
; ---- Folded in, or shown ----
;
; The same walk answers both questions, because they are the same question asked twice:
; what matches. Listing used to be a second copy of the walk that knew only about the
; shell's own words, so the files it should have been showing were never there.
SETD.1 TabShowing
LDA.1
BNA tabOfferPrint
; The first match is the answer. Every one after it cuts the answer back to where the two
; stop agreeing, which is all that can be said without guessing which was meant.
SETD.1 TabCount
@@ -1738,6 +1800,15 @@ tabOfferTake:
BRI tabOfferSkip
tabOfferFirst:
CALL tabTake
BRI tabOfferSkip
tabOfferPrint:
SETD.1 TabStart
LDD.0.1
CALL printString
INIA 0x20
OUTA 0x00
OUTA 0x00 ; Two spaces between them, and A already holds one.
tabOfferSkip:
; Past the end of this candidate, from wherever the comparison stopped.
@@ -1813,6 +1884,355 @@ tabNarrowCut:
tabNarrowDone:
RET
; ---- Splitting the word at its last separator ----
;
; "Apps/Sn" is a directory to look in and a name to match, and everything downstream depends
; on getting that split right. What comes back is TabDir holding the directory part - empty
; when there is no separator, meaning where the machine already is - and TabWordAt and
; TabWordLen narrowed to the name part, so that matching and inserting go on working on the
; part of the word they were always about.
tabSplit:
RSTA
SETD.1 TabDirLen
STA.1
; Backwards, so the LAST separator is the one found.
SETD.1 TabWordLen
LDA.1
SETD.1 TabAt
STA.1
tabSplitBack:
SETD.1 TabAt
LDA.1
BRA tabSplitNone ; No separator anywhere in it.
DECA
STA.1
SETD.1 TabWordAt
LDB.1
CCF
ADD
MVQA
CALL tabCharAt
MVQA
INIB 0x2F
XOR
BNQ tabSplitBack
; Found one. The directory part is everything up to and including it, which is what makes
; "/Source/" and "Source/" both say what they mean.
SETD.1 TabAt
LDA.1
INCA
SETD.1 TabDirLen
STA.1
SETD.2 TabDir
RSTA
SETD.1 TabCopyAt
STA.1
tabSplitCopy:
SETD.1 TabCopyAt
LDA.1
SETD.1 TabDirLen
LDB.1
CCF
SUB
BNC tabSplitCopied
SETD.1 TabWordAt
LDA.1
SETD.1 TabCopyAt
LDB.1
CCF
ADD
MVQA
CALL tabCharAt
MVQA
STA.2
INCD.2
SETD.1 TabCopyAt
LDA.1
INCA
STA.1
BRI tabSplitCopy
tabSplitCopied:
RSTA
STA.2 ; The zero that ends it.
; And the word being matched is what comes after the separator.
SETD.1 TabWordAt
LDA.1
SETD.1 TabDirLen
LDB.1
CCF
ADD
MVQA
SETD.1 TabWordAt
STA.1
SETD.1 TabWordLen
LDA.1
SETD.1 TabDirLen
LDB.1
CCF
SUB
MVQA
SETD.1 TabWordLen
STA.1
RET
tabSplitNone:
SETD.0 TabDir
RSTA
STA.0
RET
; ---- What is on the disk ----
;
; Every entry of the directory the word names that begins with the rest of the word. A
; directory is offered with a separator after it, which says what it is and lets the next
; part be typed straight away - and, because the answer then ends in one, is also what stops
; a space being put after it.
;
; WHERE THE MACHINE IS COMES BACK. This walks somebody else's directory by standing in it,
; which is the only way to walk one here, so where they were and which drive they were on are
; put down first and restored whatever happens.
tabTryFiles:
SETD.0 DiskReady
LDA.0
BRA tabFilesNone
INA 0x24
SETD.1 TabWasDrive
STA.1
SETD.0 SbfsCwd
SETD.1 TabWasCwd
CALL sbfsCopyWord
CALL tabWalkNamed
; ---- And the places a program is looked for ----
;
; The first word of a line is a command or a program, and the shell looks for a program in
; three places: where you are, the system's own place for them on the disk you are on, and
; that same place on drive 0. Tab offers what the shell would find, or it would finish a
; word into something that then would not run.
;
; ONLY WHEN NO DIRECTORY WAS NAMED. "Apps/Sn" means that directory and nowhere else.
SETD.1 TabFirst
LDA.1
BRA tabFilesBack
SETD.1 TabDirLen
LDA.1
BNA tabFilesBack
CALL tabWalkApps
; And drive 0's, unless that is the drive we are already on - in which case it is the same
; directory, and offering everything in it twice would mean nothing was ever the only
; match.
SETD.1 TabWasDrive
LDA.1
BRA tabFilesBack
RSTA
CALL sbfsUse
CALL tabWalkApps
BRI tabFilesBack
; ---- Everything in a directory, offered ----
;
; Two ways in, because there are two kinds of place to look: the one the word named, and the
; system's own place for programs. THE FIRST MUST NOT BE OVERWRITTEN BY THE SECOND - an
; earlier version wrote "/Apps" into TabDir, and since the whole walk runs a second time to
; list the matches, that second run looked in /Apps for a word which had named nowhere, and
; showed nothing at all.
;
; Where the machine stands is moved and put back by the caller, which is what makes calling
; either of these more than once safe.
tabWalkApps:
SETD.0 TabWasCwd
SETD.1 SbfsCwd
CALL sbfsCopyWord
SETD.0 AppsPath
BRI tabWalkTo
tabWalkNamed:
SETD.0 TabWasCwd
SETD.1 SbfsCwd
CALL sbfsCopyWord
SETD.1 TabDirLen
LDA.1
BRA tabFilesHere ; No directory named, so it is this one.
SETD.0 TabDir
tabWalkTo:
CALL sbfsWalk
BNQ tabWalkNothing ; No such path, and nothing to offer out of it.
; The root is not an entry, so it has no flags to ask - and it is the one place that can be
; walked to without there being anything to have asked.
SETD.0 SbfsAt
LDA.0
INCD.0
LDB.0
OR
BRQ tabFilesGo
SETD.0 SbfsFoundFlags
LDA.0
INIB 0x02
AND
BRQ tabWalkNothing ; Not a directory, so there is nothing in it.
tabFilesGo:
SETD.0 SbfsAt
SETD.1 SbfsCwd
CALL sbfsCopyWord
tabFilesHere:
CALL sbfsFirst
BRI tabFilesCheck
tabFilesStep:
CALL sbfsNext
tabFilesCheck:
BNQ tabWalkNothing
CALL tabEntry
BRI tabFilesStep
tabWalkNothing:
RET
tabFilesBack:
SETD.0 TabWasCwd
SETD.1 SbfsCwd
CALL sbfsCopyWord
SETD.1 TabWasDrive
LDA.1
CALL sbfsUse
tabFilesNone:
RET
; The entry the walk is standing on, offered under the name somebody would type for it.
tabEntry:
SETD.0 SbfsName
SETD.1 TabCandidate
CALL tabCopyName
SETD.0 SbfsFoundFlags
LDA.0
INIB 0x02
AND
BRQ tabEntryFile ; The bit SET is what says directory - see dir, which asks the
; same question the same way round.
; A directory, and the separator goes on the end of it.
SETD.3 TabCandidate
CALL tabEnd
INIA 0x2F
STA.3
INCD.3
RSTA
STA.3
BRI tabEntryOffer
tabEntryFile:
; A file. When the first word of a line is being finished it can only usefully be a
; program, and the name to type for one is the name without its extension - so anything
; else is not offered at all rather than offered and useless.
SETD.1 TabPrograms
LDA.1
BRA tabEntryOffer
CALL tabStripSbx
BNQ tabEntryDone
tabEntryOffer:
SETD.3 TabCandidate
CALL tabOffer
tabEntryDone:
RET
; DP0 names a string and DP1 where to put it. Copies up to what a name can be.
tabCopyName:
RSTB
tabCopyNext:
LDA.0
BRA tabCopyDone
STA.1
INCD.0
INCD.1
INCB
INIA 0d40
CCF
SUB
BRQ tabCopyDone
BRI tabCopyNext
tabCopyDone:
RSTA
STA.1
RET
; DP3 names a string. Leaves DP3 on its zero.
tabEnd:
LDA.3
BRA tabEndDone
INCD.3
BRI tabEnd
tabEndDone:
RET
; Takes ".sbx" off the end of TabCandidate. Q is zero if it was there, and one if it was not
; - which is how a file that is not a program is passed over.
tabStripSbx:
; How long the name is, counted rather than measured from a pointer, because what is
; wanted next is a NUMBER: four characters back from the end is only a place if there are
; four of them to go back over.
SETD.0 TabCandidate
RSTB
tabStripCount:
LDA.0
BRA tabStripCounted
INCD.0
INCB
BRI tabStripCount
tabStripCounted:
; The count is in B and wanted in A, and there is no instruction that moves one to the
; other: adding it to nothing is how a value crosses from B to A on this machine.
RSTA
CCF
ADD
MVQA
INIB 0d4
CCF
SUB
BRC tabStripNo ; Shorter than the extension, so it cannot end in it.
MVQA
SETD.3 TabCandidate
DPUA.3 ; Four from the end, which is where the extension would begin.
SETD.0 SbxText
tabStripMatch:
LDA.0
BRA tabStripYes
LDB.3
XOR
BNQ tabStripNo
INCD.0
INCD.3
BRI tabStripMatch
tabStripYes:
; It ended in the extension, so the name to type is what comes before it. DP3 was left on
; the zero by the comparison, so four back is where the dot was.
INIA 0d4
DPDA.3
RSTA
STA.3
RSTB
CCF
ADD ; Q is zero: it is a program.
RET
tabStripNo:
INIA 0x01
RSTB
CCF
ADD ; Q is one: it is not.
RET
; ---- The shell's own words ----
;
; Fifteen strings packed end to end, each ending in the zero that says where the next one
@@ -1842,7 +2262,13 @@ tabNamesDone:
; editor's idea of where things are hangs off those two registers.
tabList:
CALL newLine
CALL tabListNames
INIA 0x01
SETD.1 TabShowing
STA.1
CALL tabRunSources
RSTA
SETD.1 TabShowing
STA.1
CALL newLine
CALL sayPrompt
@@ -1858,82 +2284,6 @@ tabList:
CALL editRedraw
BRI editKey
tabListNames:
SETD.1 ShellNameCount
LDA.1
SETD.1 TabLeft
STA.1
SETD.3 ShellNames
tabListNext:
SETD.1 TabLeft
LDA.1
BRA tabListDone
DECA
STA.1
CALL tabShow
BRI tabListNext
tabListDone:
RET
; DP3 names a candidate. Prints it if it begins with what was typed, and leaves DP3 past its
; zero either way, the same bargain tabOffer makes.
tabShow:
SETD.1 TabStart
STD.3.1
SETD.1 TabWordLen
LDA.1
SETD.1 TabSeen
STA.1
RSTA
SETD.1 TabAt
STA.1
tabShowMatch:
SETD.1 TabSeen
LDA.1
BRA tabShowIt
DECA
STA.1
LDA.3
BRA tabShowSkip
SETD.1 TabHold
STA.1
SETD.1 TabWordAt
LDA.1
SETD.1 TabAt
LDB.1
CCF
ADD
MVQA
CALL tabCharAt
MVQA
SETD.1 TabHold
LDB.1
XOR
BNQ tabShowSkip
INCD.3
SETD.1 TabAt
LDA.1
INCA
STA.1
BRI tabShowMatch
tabShowIt:
SETD.1 TabStart
LDD.0.1
CALL printString
INIA 0x20
OUTA 0x00
OUTA 0x00 ; Two spaces between them, and A already holds one.
tabShowSkip:
LDA.3
BRA tabShowPast
INCD.3
BRI tabShowSkip
tabShowPast:
INCD.3
RET
; ---- A command that did not work ----
;
; The one place a failure is recorded, so that the thing reading lines out of a file can
@@ -6104,6 +6454,11 @@ MonitorName:
ShellNameCount:
0d15
SbxText:
".sbx"
AppsPath:
"/Apps"
; 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:
@@ -6457,6 +6812,30 @@ TabStart:
; The answer so far: the first match, cut back by every one after it to where they agree.
TabBest:
#Reserve 0d64
; The word being matched, as somebody would type it: a name with its extension taken off, or
; a directory with a separator put on.
TabCandidate:
#Reserve 0d48
; The part of the word up to and including its last separator, which is the directory to look
; in. Empty means where the machine already is.
TabDir:
#Reserve 0d64
TabDirLen:
0x00
TabCopyAt:
0x00
TabFirst:
0x00
TabPrograms:
0x00
; Whether the walk is looking for the answer or showing what the answers were.
TabShowing:
0x00
; Where the machine was standing before a directory was walked on its behalf.
TabWasCwd:
0x00 0x00
TabWasDrive:
0x00
; ---- What the fault screen is saying ----
;