diff --git a/Programs/CosmOS/Assembler/labels.asm b/Programs/CosmOS/Assembler/labels.asm index 6b9ba35..66cba7c 100644 --- a/Programs/CosmOS/Assembler/labels.asm +++ b/Programs/CosmOS/Assembler/labels.asm @@ -297,18 +297,21 @@ LabEnd: ; How many labels there may be, and how many bytes of name between them. ; ; SIZED FOR THE ASSEMBLER ITSELF, which turns out to be the largest thing it is asked to -; build: 555 labels and about 6,800 bytes of name, against CosmOS's 650 and 8,081 - CosmOS is -; the bigger of the two now, and was the smaller when this was written. Running into either +; build: the assembler is about 555 labels, against CosmOS's 1,341 and 16,758 bytes of name +; measured 2026-09-06 - CosmOS is much the bigger of the two now, and was the smaller when +; this was written. Running into either ; limit says so rather than writing past the end of the table, and that is what it did. The buffers live ; above the program rather than inside it - see the scratch map in Asm.asm. -; Fifteen hundred and thirty six names, and sixteen kilobytes to hold them in. Both were -; half that, and the names ran out first: 8,081 bytes of 8,192, which is a hundred and -; eleven - and the thing that ran into it was one ordinary piece of work adding eighteen -; labels. THESE TWO MUST AGREE WITH THE SCRATCH MAP, which says where the room actually is. +; Two thousand and forty eight names, and twenty six kilobytes to hold them in. Both have +; now been doubled twice, and BOTH TIMES THE NAMES RAN OUT FIRST with the index a couple of +; hundred behind them - 8,081 of 8,192 the first time, 16,758 of 16,384 the second. A name +; is thirteen bytes on average and an index entry is four, so the arena will always be the +; one that speaks; raising it alone would buy a few hundred labels and then the other wall. +; THESE TWO MUST AGREE WITH THE SCRATCH MAP, which says where the room actually is. LabLimit: - 0x06 0x00 + 0x08 0x00 LabRoom: - 0x40 0x00 + 0x68 0x00 LabNamed: ": " diff --git a/Programs/CosmOS/Assembler/scratch.asm b/Programs/CosmOS/Assembler/scratch.asm index 4744635..d3c934a 100644 --- a/Programs/CosmOS/Assembler/scratch.asm +++ b/Programs/CosmOS/Assembler/scratch.asm @@ -20,10 +20,10 @@ ; bad enough; a stale number sitting next to the correction is worse, because whichever ; one a reader takes is a coin toss. ; -; 0x5000 6144 the label index, 1536 entries of four -; 0x6800 16384 the label names, packed end to end -; 0xA800 256 one block of the output file, on its way to the disk -; 0xA900 17152 free +; 0x5000 8192 the label index, 2048 entries of four +; 0x7000 26624 the label names, packed end to end +; 0xD800 256 one block of the output file, on its way to the disk +; 0xD900 1792 free ; 0xE000 1792 the vector names, 64 entries of twenty eight ; 0xE700 2048 the reader's stack, six levels of 301 ; 0xEF00 368 which files have been included, sixteen names of 23 @@ -69,6 +69,17 @@ ; among buffers that do not need it, because an unclaimed page is available to whichever ; one turns out to want it. ; +; ---- And one turned out to want it ---- +; +; Sixteen kilobytes of names was half full when that was written and ran out on 2026-09-06, +; on an ordinary day's work on the shell: 16,758 bytes of name against 16,384. The index was +; 1,341 of 1,536 in the same breath, which is the paragraph above this one happening again - +; two ceilings, and lifting only the one that spoke would have bought two hundred labels. +; +; So both, out of the free page, and both left at about a third clear rather than at half: +; the names to 26,624 and the index to 2,048. What is left over is 1,792 bytes, kept +; unclaimed for the same reason the seventeen kilobytes were. +; ; That ends at 0xF070, with the Stack coming down from 0xFFFF above it - nearly four ; kilobytes, against the tens of bytes of CALL frames this ever nests. ; @@ -78,16 +89,17 @@ ; list moved up rather than the stack simply being asked to fit. ; ; THE TWO THINGS THAT DECIDE THESE SIZES are the largest program it will be asked to build -; and the largest one it will be asked to read. CosmOS is 475 labels and 9,564 bytes of -; output; the assembler itself is 555 labels, about 6,800 bytes of name and 11,648 of output. The -; second is bigger than the first, which is worth knowing: the hardest thing this assembles -; is not the operating system, it is itself. +; and the largest one it will be asked to read. Measured 2026-09-06: CosmOS is 1,341 labels +; and 16,758 bytes of name, and the assembler itself is far smaller. It was the other way +; round when this file was written - "the hardest thing this assembles is not the operating +; system, it is itself" - and the system has grown past it since. The numbers here are worth +; measuring again rather than believing, which is why they carry a date. ScratchLabIndex: 0x50 0x00 ScratchLabArena: - 0x68 0x00 + 0x70 0x00 ScratchWindow: - 0xA8 0x00 + 0xD8 0x00 ScratchVecNames: 0xE0 0x00 ScratchSrcStack: diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index 282605d..3052b22 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -481,6 +481,23 @@ program; anything after it is a file. 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. +**And when there is more than one, they are listed in columns**, sized to the longest of them, +the same way `ls` lays a directory out. The width comes from the screen and the longest name +is counted on the pass that finds the answer - which already visits every match, so the +listing costs no walk of its own to know how wide a column should be. + +**Directories are blue and the shell's own commands are green.** Both are known for nothing: +Tab appends the separator itself, and a built-in is not a file at all - which is also why no +amount of reading the disk could have told you it will run. + +**A file is not coloured, and that is a decision rather than an omission.** Whether a file +will run is a read of its first block, which is what `ls` does and what makes `ls` cost twice +what it otherwise would. Here it would be worse than slow: candidates are offered from inside +a directory walk, and looking a file up would overwrite the very fields holding the walk's own +position. Doing it safely means holding every candidate name in memory first, which is a +buffer the shell would carry whether anybody pressed Tab or not - and `ls` is one keystroke +away. + **Files are looked for in the three places 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. A file that is in two of diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index f5f84e7..86607a3 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -1739,6 +1739,8 @@ tabIsFirst: STA.1 SETD.1 TabBestLen STA.1 + SETD.1 TabLongest + STA.1 ; A path in the word says which directory to look in, and narrows what is being matched to ; the part after the last separator. @@ -1929,6 +1931,16 @@ tabOfferTake: LDA.1 INCA STA.1 + + ; ---- How wide the widest of them is, counted on the pass that is already here ---- + ; + ; Listing wants it and listing happens on the SECOND pass, by which time this one is over. + ; Working it out there would mean a third walk of everything; working it out here costs a + ; walk of one name, and only of names that matched. + CALL tabWidest + + SETD.1 TabCount + LDA.1 INIB 0d1 XOR BRQ tabOfferFirst @@ -1939,12 +1951,62 @@ tabOfferFirst: BRI tabOfferSkip tabOfferPrint: + ; ---- The cell the last one started, finished now ---- + ; + ; Padding before rather than after, so a line ends on the last character of a name instead + ; of on a run of spaces. The same reason ls does it that way. + SETD.1 TabAcross + LDA.1 + BRA tabOfferInk + CALL tabPad + +tabOfferInk: + ; A directory in blue and a built-in command in green, which are the two things that can be + ; known for nothing. Whether a FILE will run cannot: that is a read of its first block, and + ; this is a keystroke. + SETD.1 TabKind + LDA.1 + BRA tabOfferSay + DECA + BRA tabOfferBlue + INIA 0d2 + BRI tabOfferInked +tabOfferBlue: + INIA 0d4 + BRI tabOfferInked +tabOfferSay: + RSTA +tabOfferInked: + OUTA 0x06 + SETD.1 TabStart LDD.0.1 CALL printString - INIA 0x20 - OUTA 0x00 - OUTA 0x00 ; Two spaces between them, and A already holds one. + RSTA + OUTA 0x06 + + ; How much of the cell it took, for whoever finishes it, and one more across the line. + SETD.1 TabStart + LDD.0.1 + CALL tabTextLength + MVQA + SETD.1 TabUsed + STA.1 + + SETD.1 TabAcross + LDA.1 + INCA + STA.1 + SETD.1 TabColumns + LDB.1 + CCF + SUB + BNQ tabOfferSkip + + RSTA + SETD.1 TabAcross + STA.1 + CALL newLine tabOfferSkip: ; Past the end of this candidate, from wherever the comparison stopped. @@ -2258,6 +2320,9 @@ tabEntry: ; same question the same way round. ; A directory, and the separator goes on the end of it. + INIA 0d1 + SETD.1 TabKind + STA.1 SETD.3 TabCandidate CALL tabEnd INIA 0x2F @@ -2267,6 +2332,10 @@ tabEntry: STA.3 tabEntryFile: + RSTA + SETD.1 TabKind + STA.1 + ; A file, offered under the name it actually has - so nothing happens to it here at all. ; A first word used to be offered with ".sbx" taken off, and anything without that ending ; not offered, which was right while the extension was what made a file reachable by name. @@ -2323,11 +2392,73 @@ tabNameNext: BRA tabNamesDone DECA STA.1 + INIA 0d2 + SETD.1 TabKind + STA.1 ; A command of the shell's own, which no disk read could say. CALL tabOffer BRI tabNameNext tabNamesDone: RET +; DP0 names a string. Q is how long it is. Q rather than A, because a RET puts A back - which +; is the oldest trap on this machine and cost an afternoon in ls. +tabTextLength: + RSTA +tabTextNext: + LDB.0 + BRB tabTextDone + INCA + INCD.0 + BRI tabTextNext +tabTextDone: + RSTB + CCF + ADD + RET + +; The candidate at TabStart against the longest seen so far, called on the pass that finds the +; answer - so listing costs no walk of its own to know how wide a column should be. +tabWidest: + SETD.1 TabStart + LDD.0.1 + CALL tabTextLength + MVQA + SETD.1 TabLongest + LDB.1 + CCF + SUB + BRC tabWidestKept ; Borrowed, so this one is not the longest. + STA.1 +tabWidestKept: + RET + +; Fills out the cell the last name started. +tabPad: + SETD.1 TabCell + LDA.1 + SETD.1 TabUsed + LDB.1 + CCF + SUB + BRC tabPadOne ; Wider than its cell, so one space keeps them apart. + MVQA + BRA tabPadOne + SETD.1 TabPadLeft + STA.1 +tabPadNext: + INIA 0x20 + OUTA 0x00 + SETD.1 TabPadLeft + LDA.1 + DECA + STA.1 + BNA tabPadNext + RET +tabPadOne: + INIA 0x20 + OUTA 0x00 + RET + ; ---- Showing them when they cannot be narrowed further ---- ; ; The line is reprinted underneath afterwards, and where it now BEGINS is asked rather than @@ -2335,6 +2466,52 @@ tabNamesDone: ; editor's idea of where things are hangs off those two registers. tabList: CALL newLine + + ; ---- How many of them fit across ---- + ; + ; The longest was counted on the pass that found the answer, so this is arithmetic and not + ; another walk. Two spaces between columns, and the screen says how wide it is - the same + ; sum ls does, for the same reason and with the same answer. + SETD.1 TabLongest + LDA.1 + INCA + INCA + SETD.1 TabCell + STA.1 + INA 0x32 + SETD.1 TabRoom + STA.1 + RSTA + SETD.1 TabColumns + STA.1 +tabFitting: + SETD.1 TabRoom + LDA.1 + SETD.1 TabCell + LDB.1 + CCF + SUB + BRC tabFitted + MVQA + SETD.1 TabRoom + STA.1 + SETD.1 TabColumns + LDA.1 + INCA + STA.1 + BRI tabFitting +tabFitted: + ; A candidate wider than the screen still gets a column to itself. + SETD.1 TabColumns + LDA.1 + BNA tabListing + INIA 0d1 + STA.1 + +tabListing: + RSTA + SETD.1 TabAcross + STA.1 INIA 0x01 SETD.1 TabShowing STA.1 @@ -2342,7 +2519,14 @@ tabList: RSTA SETD.1 TabShowing STA.1 + + ; A row with something in it needs ending. One that filled its last column does not, and a + ; blank line under a listing looks like a listing with a gap in it. + SETD.1 TabAcross + LDA.1 + BRA tabListed CALL newLine +tabListed: CALL sayPrompt INA 0x03 @@ -8728,6 +8912,27 @@ TabCopyAt: 0x00 TabFirst: 0x00 +; ---- Laying the matches out in columns ---- +; +; What kind of thing the candidate being offered is, how wide the widest of them was, and how +; far along a row the listing has got. The kind is set where the candidate comes FROM, since +; that is the only place that knows: 0 a file, 1 a directory, 2 one of the shell's own words. +TabKind: + 0x00 +TabLongest: + 0x00 +TabCell: + 0x00 +TabColumns: + 0x00 +TabRoom: + 0x00 +TabAcross: + 0x00 +TabUsed: + 0x00 +TabPadLeft: + 0x00 ; Whether the walk is looking for the answer or showing what the answers were. TabShowing: 0x00 diff --git a/Tests/expected/cfgFallback.out b/Tests/expected/cfgFallback.out index 863f47d..4d3c5be 100644 --- a/Tests/expected/cfgFallback.out +++ b/Tests/expected/cfgFallback.out @@ -14,7 +14,7 @@ made > cd Notes /Notes> dir 0 files -8 of 32 entries, 309 blocks free +8 of 32 entries, 307 blocks free /Notes> exit halted Execution halted. diff --git a/Tests/expected/cosmosTab.out b/Tests/expected/cosmosTab.out index 4221db8..7511a6a 100644 --- a/Tests/expected/cosmosTab.out +++ b/Tests/expected/cosmosTab.out @@ -5,7 +5,7 @@ one > mk[5;3Hmkdir [5;9H/made-by-tab made > d -dir do drive delete +[0;32mdir[0m [0;32mdo[0m [0;32mdrive[0m [0;32mdelete[0m > [9;3Hd[9;4H I do not know: d > zzzx diff --git a/Tests/expected/onceAsked.out b/Tests/expected/onceAsked.out index d62dd05..f1a7fe4 100644 --- a/Tests/expected/onceAsked.out +++ b/Tests/expected/onceAsked.out @@ -5,7 +5,7 @@ next start: /System/Boot/bare.bin, once System