> ls Copy.sbx Say.sbx Where.sbx Walk.sbx ls.sbx Lander where.sh dir says what is there one line each, with sizes and a tally of the disk. This says the names in columns and nothing else, which is what you want ninety times in a hundred. Two programs rather than one with a switch, because they answer different questions and neither answer is a worse version of the other. AND THIS IS A PROGRAM WHERE DIR IS BUILT IN, which is the difference that matters when the disk is what you are doubting: dir is already in memory and this has to be loaded off the disk it is about to list. The daily driver and the diagnostic. Two passes over the directory and no buffer at all. Columns need the longest name before the first line can be printed, which usually means holding every name - twenty four bytes each against a format that allows 1,024 entries. Walking twice costs a read of each directory block, into a buffer that is already there. Across and not down. Real listings go down the columns so that names next to each other alphabetically are next to each other on the screen, and that reason depends on sorting - which nothing here does. With the order arbitrary, down-and-across buys nothing and costs a division. Directories in blue and wearing a separator, unfinished saves in red, everything else plain: whether a file is runnable cannot be known without opening it, and opening every file in a directory to colour a listing is a price nobody agreed to pay. The colour reaches a terminal as well as the screen, so it is one mechanism and not two. ---- Three bugs, and one of them is this machine's oldest trap ---- nameLength answered in A, and a RET puts A back the way the caller had it. So it answered with nothing, and every gap between the columns came out 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 here comes home in it. The padding subtracted the other way round - the name from the cell - which borrowed on every name that was not the longest, so all of them took the "wider than its cell" path and the listing came out separated by one space. And padding after a name left trailing spaces on every line that did not fill its last column. It goes before the next name now, so spaces only ever fall between two things and a line ends on a name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
370 lines
7.9 KiB
NASM
370 lines
7.9 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, because
|
|
; whether a file is runnable cannot be known without opening it, and opening every file in a
|
|
; directory to colour a listing is a price nobody agreed to pay.
|
|
SETD.0 Kind
|
|
LDA.0
|
|
BRA lsPlain
|
|
DECA
|
|
BRA lsBlue
|
|
INIA 0d1 ; An unfinished save, which is worth looking at.
|
|
BRI lsInk
|
|
lsBlue:
|
|
INIA 0d4
|
|
BRI lsInk
|
|
lsPlain:
|
|
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
|