The shell printed "finished" every time a program gave the machine back. That made sense when starting a program and getting the machine back was most of what the machine did and the prompt was the only other thing on the screen. Under a listing, between two commands, it is noise. And it was printed whatever the program made of it, so a program that had just explained what went wrong was answered with the word "finished" - "there is no such directory: nowhere" and then, immediately, "finished". The prompt coming back is what says a program is over. It is the same argument osLastStatus is made of: a program that failed has already said so in words, and anything the shell adds beside that is the shell talking over it. A program that FAULTED still says so, because the fault screen printed in red above and a program that is gone should not look like one that ended. A NEWLINE ONLY IF ONE IS WANTED. A program that stopped part way along a line would leave the prompt sitting in the middle of its last output, which the word used to prevent by accident. The console knows which column the cursor is in, so the shell asks - where a blank line printed every time would be right about half of the time, and an empty directory listed with ls would be followed by a blank line for no reason. Fifty nine recorded sessions lose the word. Two of them move a cursor up a row with it, which is the same fact seen from the screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
369 lines
7.7 KiB
NASM
369 lines
7.7 KiB
NASM
; A listing laid out to be read.
|
|
;
|
|
; > ls
|
|
; Copy.sbx Say.sbx Where.sbx
|
|
; Walk.sbx Lander where.sh
|
|
;
|
|
; > ls /Apps
|
|
;
|
|
; dir says what is there, one line each, with sizes and a tally of the disk. This says the
|
|
; names and nothing else, in columns, which is what you want ninety times in a hundred - and
|
|
; the two are separate programs rather than one with a switch because they answer different
|
|
; questions and neither answer is a worse version of the other.
|
|
;
|
|
; ---- Why this is a program and dir is not ----
|
|
;
|
|
; dir is built into the shell, and that is what makes it the one to trust when the disk is
|
|
; the thing being doubted: it is already in memory. This has to be loaded FROM the disk, so it
|
|
; cannot list a disk too broken to load it. The daily driver and the diagnostic, and it is
|
|
; worth having both.
|
|
;
|
|
; ---- Two passes, and no buffer at all ----
|
|
;
|
|
; Columns need the longest name before the first line can be printed, which usually means
|
|
; holding every name in memory - twenty four bytes each, and this format allows 1,024 entries
|
|
; on a disk. So instead the directory is walked TWICE: once to find the longest name and count
|
|
; them, once to print. A walk costs a read of each directory block and the disk buffer is
|
|
; already there, where a buffer for the names would be memory this program has to have whether
|
|
; the directory is large or not.
|
|
;
|
|
; ---- Across and then down, rather than down and then across ----
|
|
;
|
|
; Real listings go down the columns, so that names next to each other alphabetically are next
|
|
; to each other on the screen. THAT IS A REASON THAT DEPENDS ON SORTING, and nothing here
|
|
; sorts: entries come back in the order the directory holds them, which is the order they were
|
|
; made. With the order arbitrary, down-and-across buys nothing and costs a division to work
|
|
; out how many rows there are - on a machine that cannot divide.
|
|
;
|
|
; So this goes across. If sorting ever arrives, that is the moment to change it, and this
|
|
; comment is the reason why.
|
|
|
|
#Include services.asm
|
|
|
|
#Program
|
|
|
|
#Base 0x5000
|
|
|
|
start:
|
|
; A directory to list, if one was named. The shell puts the working directory back when a
|
|
; program exits, so this can walk off somewhere and not tidy up after itself.
|
|
SETD.0 Given
|
|
INIB 0d64
|
|
SWI osArgument
|
|
SETD.0 Given
|
|
LDA.0
|
|
BRA lsHere
|
|
SWI osChangeDir
|
|
BNQ lsNoSuchPlace
|
|
|
|
lsHere:
|
|
; ---- Pass one: how wide the widest of them is ----
|
|
RSTA
|
|
SETD.0 Longest
|
|
STA.0
|
|
SETD.0 Seen
|
|
STA.0
|
|
|
|
SETD.0 Name
|
|
INIB 0d24
|
|
SWI osDirFirst
|
|
BRI lsMeasure
|
|
|
|
lsMeasureNext:
|
|
SETD.0 Name
|
|
INIB 0d24
|
|
SWI osDirNext
|
|
|
|
lsMeasure:
|
|
MVQA
|
|
SETD.0 Kind
|
|
STA.0
|
|
INIB 0xFF
|
|
CCF
|
|
SUB
|
|
BRQ lsMeasured
|
|
|
|
SETD.0 Seen
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
|
|
CALL nameLength
|
|
MVQA
|
|
|
|
; A directory wears a separator, which is a character of width like any other.
|
|
SETD.0 Kind
|
|
LDB.0
|
|
DECB
|
|
BNB lsMeasureWidth
|
|
INCA
|
|
|
|
lsMeasureWidth:
|
|
SETD.0 Longest
|
|
LDB.0
|
|
CCF
|
|
SUB
|
|
BRC lsMeasureKeep ; Borrowed, so this one is not the longest.
|
|
STA.0
|
|
|
|
lsMeasureKeep:
|
|
BRI lsMeasureNext
|
|
|
|
lsMeasured:
|
|
; Nothing at all is said with nothing at all, which is what an empty directory looks like.
|
|
SETD.0 Seen
|
|
LDA.0
|
|
BRA lsDone
|
|
|
|
; ---- How many of them fit across ----
|
|
;
|
|
; Two spaces between columns, so that names never touch. Worked out by taking the cell width
|
|
; off the screen width until there is not room for another, because this machine cannot
|
|
; divide and the answer is never more than a handful.
|
|
SETD.0 Longest
|
|
LDA.0
|
|
INCA
|
|
INCA
|
|
SETD.0 Cell
|
|
STA.0
|
|
|
|
INA 0x32
|
|
SETD.1 Across
|
|
STA.1 ; What is left of the line, counted down.
|
|
RSTA
|
|
SETD.1 Columns
|
|
STA.1
|
|
|
|
lsFitting:
|
|
SETD.1 Across
|
|
LDA.1
|
|
SETD.1 Cell
|
|
LDB.1
|
|
CCF
|
|
SUB
|
|
BRC lsFitted ; Borrowed, so there is not room for another column.
|
|
MVQA
|
|
SETD.1 Across
|
|
STA.1
|
|
SETD.1 Columns
|
|
LDA.1
|
|
INCA
|
|
STA.1
|
|
BRI lsFitting
|
|
|
|
lsFitted:
|
|
; A name wider than the screen still gets a column of its own. One that runs over is better
|
|
; than one that is not shown.
|
|
SETD.0 Columns
|
|
LDA.0
|
|
BNA lsPrint
|
|
INIA 0d1
|
|
STA.0
|
|
|
|
lsPrint:
|
|
RSTA
|
|
SETD.0 At
|
|
STA.0
|
|
|
|
SETD.0 Name
|
|
INIB 0d24
|
|
SWI osDirFirst
|
|
BRI lsSay
|
|
|
|
lsSayNext:
|
|
SETD.0 Name
|
|
INIB 0d24
|
|
SWI osDirNext
|
|
|
|
lsSay:
|
|
MVQA
|
|
SETD.0 Kind
|
|
STA.0
|
|
INIB 0xFF
|
|
CCF
|
|
SUB
|
|
BRQ lsLastLine
|
|
|
|
; ---- The cell the last name started, finished now rather than then ----
|
|
;
|
|
; Padding AFTER a name puts spaces at the end of every line that does not fill its last
|
|
; column, and trailing whitespace on a line is the kind of thing that is invisible until
|
|
; something else reads the output. Padding before the next name instead means the spaces
|
|
; only ever go between two things, and a line ends on the last character of a name.
|
|
SETD.0 At
|
|
LDA.0
|
|
BRA lsColour ; First on its line, so there is nothing to finish.
|
|
CALL padOut
|
|
|
|
lsColour:
|
|
; ---- What it is, said in colour ----
|
|
;
|
|
; The attribute reaches a terminal as well as the screen now, so this is one mechanism and
|
|
; not two. A directory in blue and an unfinished save in red; everything else plain.
|
|
SETD.0 Kind
|
|
LDA.0
|
|
BRA lsFileInk
|
|
DECA
|
|
BRA lsBlue
|
|
INIA 0d1 ; An unfinished save, which is worth looking at.
|
|
BRI lsInk
|
|
lsBlue:
|
|
INIA 0d4
|
|
BRI lsInk
|
|
|
|
lsFileInk:
|
|
RSTA
|
|
lsInk:
|
|
OUTA 0x06
|
|
|
|
SETD.0 Name
|
|
SWI osPrintString
|
|
CALL nameLength
|
|
MVQA
|
|
|
|
SETD.0 Kind
|
|
LDB.0
|
|
DECB
|
|
BNB lsWidth
|
|
PSHA
|
|
SETD.0 Slash
|
|
SWI osPrintString
|
|
POPA
|
|
INCA
|
|
|
|
lsWidth:
|
|
; How much of the cell this name took, for whoever finishes it.
|
|
SETD.0 Used
|
|
STA.0
|
|
|
|
; Back to plain before anything else is written, so that a scheme with paper in it would
|
|
; not paint the gap between the columns.
|
|
RSTA
|
|
OUTA 0x06
|
|
|
|
SETD.0 At
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
SETD.0 Columns
|
|
LDB.0
|
|
CCF
|
|
SUB
|
|
BNQ lsSayNext ; Room for another on this line.
|
|
|
|
RSTA
|
|
SETD.0 At
|
|
STA.0
|
|
CALL newLine
|
|
BRI lsSayNext
|
|
|
|
lsLastLine:
|
|
; A line with something on it needs ending. One that ended on its last column does not, and
|
|
; a blank line under a listing looks like a listing with a gap in it.
|
|
SETD.0 At
|
|
LDA.0
|
|
BRA lsDone
|
|
CALL newLine
|
|
|
|
lsDone:
|
|
RSTA
|
|
SWI osExit
|
|
|
|
lsNoSuchPlace:
|
|
SETD.0 NoSuchPlace
|
|
SWI osPrintString
|
|
SETD.0 Given
|
|
SWI osPrintString
|
|
CALL newLine
|
|
INIA 0x01
|
|
SWI osExit
|
|
|
|
; ---- How wide the name in the buffer is ----
|
|
;
|
|
; Q COMES BACK THE COUNT, not A. A RET puts A back the way the caller had it, so a subroutine
|
|
; that answers in A answers with nothing - and this one did, which made every gap between the
|
|
; columns the same width because the padding was subtracting whatever A happened to hold.
|
|
; Q is the ALU's output and nothing puts it back, which is why every answer on this machine
|
|
; comes home in it.
|
|
nameLength:
|
|
SETD.0 Name
|
|
RSTA
|
|
lengthNext:
|
|
LDB.0
|
|
BRB lengthDone
|
|
INCA
|
|
INCD.0
|
|
BRI lengthNext
|
|
lengthDone:
|
|
RSTB
|
|
CCF
|
|
ADD
|
|
RET
|
|
|
|
; A is how much of the cell the name took. Fills the rest of it with spaces.
|
|
padOut:
|
|
; The cell less what the name took, and IN THAT ORDER. Written the other way round first,
|
|
; which borrowed on every name that was not the longest - so every one of them took the
|
|
; "wider than its cell" path below and the whole listing came out separated by one space.
|
|
SETD.0 Cell
|
|
LDA.0
|
|
SETD.0 Used
|
|
LDB.0
|
|
CCF
|
|
SUB
|
|
BRC padDone ; Wider than its cell, so one space keeps the names apart.
|
|
MVQA
|
|
BRA padOne
|
|
padLoop:
|
|
PSHA
|
|
SETD.0 Space
|
|
SWI osPrintString
|
|
POPA
|
|
DECA
|
|
BNA padLoop
|
|
RET
|
|
padOne:
|
|
padDone:
|
|
SETD.0 Space
|
|
SWI osPrintString
|
|
RET
|
|
|
|
newLine:
|
|
SETD.0 NewLine
|
|
SWI osPrintString
|
|
RET
|
|
|
|
#Data
|
|
|
|
#Base 0x3000
|
|
|
|
Slash:
|
|
"/"
|
|
Space:
|
|
" "
|
|
NoSuchPlace:
|
|
"there is no such directory: "
|
|
NewLine:
|
|
0x0A 0x00
|
|
|
|
Kind:
|
|
0x00
|
|
Longest:
|
|
0x00
|
|
Cell:
|
|
0x00
|
|
Columns:
|
|
0x00
|
|
Across:
|
|
0x00
|
|
At:
|
|
0x00
|
|
Seen:
|
|
0x00
|
|
Used:
|
|
0x00
|
|
Name:
|
|
#Reserve 0d24
|
|
Given:
|
|
#Reserve 0d64
|