diff --git a/Programs/CosmOS/Assembler/scratch.asm b/Programs/CosmOS/Assembler/scratch.asm
index 48d4171..5daffbd 100644
--- a/Programs/CosmOS/Assembler/scratch.asm
+++ b/Programs/CosmOS/Assembler/scratch.asm
@@ -19,12 +19,17 @@
; 0x8C00 8192 the label names, packed end to end
; 0xAC00 13312 the output file being built
; 0xE000 1792 the vector names, 64 entries of twenty eight
-; 0xE700 1758 the reader's stack, six levels of 293
-; 0xEE00 368 which files have been included, sixteen names of 23
+; 0xE700 2048 the reader's stack, six levels of 301
+; 0xEF00 368 which files have been included, sixteen names of 23
;
-; That ends at 0xEF70, with the Stack coming down from 0xFFFF above it - about four
+; 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.
;
+; The reader's levels went from 293 to 301 when an include gained somewhere to be looked
+; for: the name in each level is a PATH now, and "/Lib/" is five characters of it. Six
+; levels of 301 is 1806, so the room here has to stay above that - which is why the include
+; 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
@@ -41,4 +46,4 @@ ScratchVecNames:
ScratchSrcStack:
0xE7 0x00
ScratchIncNames:
- 0xEE 0x00
+ 0xEF 0x00
diff --git a/Programs/CosmOS/Assembler/source.asm b/Programs/CosmOS/Assembler/source.asm
index ca4a556..bc64c54 100644
--- a/Programs/CosmOS/Assembler/source.asm
+++ b/Programs/CosmOS/Assembler/source.asm
@@ -189,13 +189,53 @@ srcInclude:
CALL srcRemember
CALL srcPush
+
+ ; TWO PLACES, TRIED IN ORDER: where you are, and then /Lib. The same rule the shell uses
+ ; for a program it does not recognise, which is where it came from - a name means the
+ ; one beside you if there is one, and the system's otherwise.
+ ;
+ ; It is what a search path is for, and the host assembler has had one since before there
+ ; was a machine to run this on. Without it every source that calls a service has to sit
+ ; in the same directory as services.asm, and the disk cannot be organised at all.
SETD.0 IncWanted
SETD.1 SrcName
CALL srcKeepName
CALL srcRewind
+ BRQ srcIncludeIn
+
+ CALL srcInLibrary
+ CALL srcRewind
BNQ srcIncludeGone
+srcIncludeIn:
RET ; Q is zero, out of srcRewind.
+; SrcName becomes the same name inside the library directory. Built here rather than kept
+; as a second buffer, because what has to survive is the name the file was ASKED for -
+; that is what the include-once list holds, and a file found in the library on one line
+; and beside you on another is still the same include.
+srcInLibrary:
+ SETD.0 SrcLibrary
+ SETD.1 SrcName
+srcLibraryPrefix:
+ LDA.0
+ BRA srcLibraryName
+ STA.1
+ INCD.0
+ INCD.1
+ BRI srcLibraryPrefix
+
+srcLibraryName:
+ SETD.0 IncWanted
+srcLibraryCopy:
+ LDA.0
+ STA.1
+ BRA srcLibraryDone
+ INCD.0
+ INCD.1
+ BRI srcLibraryCopy
+srcLibraryDone:
+ RET
+
srcIncludeSkip:
RSTA
RSTB
@@ -483,8 +523,12 @@ srcKeepEnd:
; ---- The current file, as one block so that it can be put aside in one piece ----
;
SrcState:
+; THIRTY TWO RATHER THAN THE TWENTY THREE A NAME NEEDS, because what goes here is a PATH:
+; an include not found beside you is looked for in the library, and "/Lib/" plus a name of
+; twenty two plus the zero that ends it is twenty eight. Every block of the file is asked
+; for by this name, so it has to be the one that resolves, not the one that was typed.
SrcName:
- #Reserve 0d23
+ #Reserve 0d32
SrcBlocks:
0x00 0x00
SrcIndex:
@@ -504,12 +548,19 @@ SrcEnded:
SrcBuffer:
#Reserve 0d256
-; 292 bytes: a name of 23, six numbers of two, one single byte, and the buffer. NOTHING MAY
-; BE ADDED IN THE MIDDLE OF THE BLOCK ABOVE without changing this to match.
+; 301 bytes: a name of 32, six numbers of two, one single byte, and the buffer. NOTHING MAY
+; BE ADDED IN THE MIDDLE OF THE BLOCK ABOVE without changing this to match, and the room
+; set aside for six of them in scratch.asm has to be at least six times it.
SrcStateBytes:
- 0x01 0x24
+ 0x01 0x2D
SrcDepthLimit:
0d6
+
+; Where an include is looked for when it is not beside you. One fixed place rather than a
+; list somebody sets, for the same reason the shell has one fixed place for programs: a
+; list would need somewhere to live between one boot and the next.
+SrcLibrary:
+"/Lib/"
SrcOne:
0x00 0x01
diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md
index 6e0431b..cf7d460 100644
--- a/Programs/CosmOS/README.md
+++ b/Programs/CosmOS/README.md
@@ -281,6 +281,35 @@ CosmOS also boots without a disk. It reports that no filesystem was found, leave
shell and memory monitor available, and refuses commands that require a mounted disk
without stopping the machine.
+## What Is On The Disk:
+
+`make -C Programs cosmos-disk` builds the disk this system is meant to be met on, and it is
+laid out in three directories:
+
+| Where | What |
+| -- | -- |
+| `/Apps` | The programs. The second place the shell looks for a word it does not recognise, so anything here starts by name from anywhere on the disk. |
+| `/Source` | The things you name to the assembler: CosmOS itself, the assembler itself, and small programs to read. |
+| `/Lib` | The things those include. Everything here is named by an `#Include` somewhere and by nothing else, which is what makes it a library rather than a source. |
+
+The split is by role rather than by which directory the host keeps a file in, and it only
+works because **an include is looked for where you are and then in `/Lib`** - the same rule
+the shell uses for programs, applied to the assembler. Without that search every source
+that calls a service would have to sit beside `services.asm`, and there would be nothing to
+organise.
+
+So the machine rebuilds itself from its own disk:
+
+```text
+> cd /Source
+/Source> Asm cosmos.asm
+wrote cosmos.bin: program 9778, data 3346, labels 648
+/Source> Asm Asm.asm
+wrote Asm.sbx: program 7570, data 4114, labels 562
+```
+
+Both come out byte for byte what the host assembler makes from the same source.
+
## Included Applications:
`Programs/CosmOS/Apps` holds what the shell can load, and the application disk is built
diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm
index 9a5d58c..f86ee11 100644
--- a/Programs/CosmOS/Source/cosmos.asm
+++ b/Programs/CosmOS/Source/cosmos.asm
@@ -363,6 +363,21 @@ dirCheck:
; A file's length is its block count times 256 plus its tail, which is the block count
; in the high byte and the tail in the low one. Nothing has to multiply anything.
+ ;
+ ; THAT ONLY WORKS WHILE THE BLOCK COUNT FITS IN A BYTE. Two hundred and fifty six blocks
+ ; is sixty five thousand five hundred and thirty six bytes, and the number that comes out
+ ; of the shift is sixteen bits wide - so a file of that size or more came out as itself
+ ; less 65536, which is a plausible number and a wrong one. cosmos.asm is 82,996 bytes and
+ ; this called it 17,460.
+ ;
+ ; Such a file says its size in BLOCKS instead. Printing the true figure would want
+ ; decimal printing twenty four bits wide, which is a page of console.asm to say something
+ ; nobody reads more precisely than "big"; changing the unit says it exactly and can never
+ ; be wrong.
+ SETD.0 SbfsFileBlocks
+ LDA.0
+ BNA dirInBlocks
+
SETD.0 SbfsFileBlocks
INCD.0
LDA.0
@@ -379,6 +394,14 @@ dirCheck:
CALL newLine
BRI dirStep
+dirInBlocks:
+ SETD.0 SbfsFileBlocks
+ CALL printWordDecimal
+ SETD.0 BlocksText
+ CALL printString
+ CALL newLine
+ BRI dirStep
+
dirIsDirectory:
SETD.0 DirFolders
LDA.0
@@ -3185,6 +3208,8 @@ Farewell:
"halted"
DirectoryText:
"
"
+BlocksText:
+" blocks"
NotDirectory:
"that is not a directory"
MadeText:
diff --git a/Programs/makefile b/Programs/makefile
index dfcc213..b9dadec 100644
--- a/Programs/makefile
+++ b/Programs/makefile
@@ -112,44 +112,62 @@ cosmos: $(COSMOS) $(APPS) $(NATIVE_ASM)
# and twenty eight more names. The superblock has carried this number per disk since the
# format was written, so nothing but this line knows what it is.
$(COSMOS_DISK): $(APPS) $(NATIVE_ASM) testPrograms/stringKeyword.asm \
- CosmOS/Apps/hello.asm CosmOS/Apps/Say.asm CosmOS/Apps/Keys.asm \
- CosmOS/Source/services.asm CosmOS/Source/console.asm \
+ $(wildcard CosmOS/Apps/*.asm) \
$(wildcard CosmOS/Source/*.asm) $(wildcard CosmOS/Assembler/*.asm)
@mkdir -p $(@D)
rm -f $@
$(DISKTOOL) format $@ 4096 24
- @for app in $(APPS); do $(DISKTOOL) put $@ $$app; done
- $(DISKTOOL) put $@ $(NATIVE_ASM)
- @# SOURCE goes on as well, because an assembler with nothing to assemble is a
- @# demonstration of nothing.
+ @# THREE DIRECTORIES, WHICH IS WHAT A CLEAN INSTALL LOOKS LIKE: what you run, what you
+ @# assemble, and what those include. It was thirty nine files in one list with
+ @# cosmos.asm sitting between fileStream.asm and sbfs.asm.
@#
- @# hello.asm and Say.asm are the APPLICATION versions, so each assembles to a .sbx
- @# written straight over the one the host tool put there - which means the next
- @# thing loaded is a program the machine built itself, in the same breath.
+ @# The split is by ROLE rather than by which directory the host keeps them in. /Source
+ @# holds the things you name to the assembler and /Lib the things they pull in, which is
+ @# a distinction the host makes with -I and the machine now makes with a search path of
+ @# its own: an include is looked for beside you and then in /Lib. Without that, every
+ @# source that calls a service would have to sit in the same directory as services.asm
+ @# and there would be nothing to organise.
+ $(DISKTOOL) mkdir $@ /Apps
+ $(DISKTOOL) mkdir $@ /Source
+ $(DISKTOOL) mkdir $@ /Lib
+ @# What you run. /Apps is the second place the shell looks when a word it does not know
+ @# turns out to be a program, so anything in here starts by name from anywhere.
+ @for app in $(APPS); do \
+ $(DISKTOOL) put $@ $$app /Apps/`basename $$app` >/dev/null || exit 1; done
+ $(DISKTOOL) put $@ $(NATIVE_ASM) /Apps/Asm.sbx
+ @# What you assemble. All of it, because an assembler with nothing to assemble is a
+ @# demonstration of nothing:
@#
- @# Keys.asm is the one that brings a vector of its own, so assembling it exercises the
- @# version two header and the Vector Segment: the loader installs its handler, the
- @# console interrupts into it, and the shell takes the vector back at exit.
- @#
- @# strings.asm is the odd one out on purpose: it has no #Include and no #Base, so it
- @# comes out as a boot image rather than a loadable program, and the difference
- @# between the two is visible on one disk.
- $(DISKTOOL) put $@ CosmOS/Apps/hello.asm
- $(DISKTOOL) put $@ CosmOS/Apps/Say.asm
- $(DISKTOOL) put $@ CosmOS/Apps/Keys.asm
- $(DISKTOOL) put $@ CosmOS/Source/services.asm
- $(DISKTOOL) put $@ CosmOS/Source/console.asm
- $(DISKTOOL) put $@ testPrograms/stringKeyword.asm strings.asm
- @# And the whole of CosmOS, and the whole of the assembler, so that the machine can
- @# build the system it is running on and then build the thing that built it:
- @#
- @# > load Asm.sbx
- @# > run cosmos.asm
- @# > run Asm.asm
+ @# > cd /Source
+ @# /Source> Asm cosmos.asm the system it is running on
+ @# /Source> Asm Asm.asm and the thing that built it
@#
@# Both come out byte for byte what the host tool makes from the same source.
- @for f in CosmOS/Source/*.asm CosmOS/Assembler/*.asm; do \
- $(DISKTOOL) put $@ $$f >/dev/null; done
+ @#
+ @# Keys.asm brings a vector of its own, so assembling it exercises the version two
+ @# header and the Vector Segment: the loader installs its handler, the console
+ @# interrupts into it, and the shell takes the vector back at exit.
+ @#
+ @# strings.asm is the odd one out on purpose. It has no #Include and no #Base, so it
+ @# comes out as a boot image rather than a loadable program, and the difference between
+ @# the two is visible on one disk.
+ $(DISKTOOL) put $@ CosmOS/Source/cosmos.asm /Source/cosmos.asm
+ $(DISKTOOL) put $@ CosmOS/Assembler/Asm.asm /Source/Asm.asm
+ $(DISKTOOL) put $@ CosmOS/Assembler/readTest.asm /Source/readTest.asm
+ $(DISKTOOL) put $@ CosmOS/Assembler/tokenTest.asm /Source/tokenTest.asm
+ $(DISKTOOL) put $@ CosmOS/Apps/hello.asm /Source/hello.asm
+ $(DISKTOOL) put $@ CosmOS/Apps/Say.asm /Source/Say.asm
+ $(DISKTOOL) put $@ CosmOS/Apps/Keys.asm /Source/Keys.asm
+ $(DISKTOOL) put $@ testPrograms/stringKeyword.asm /Source/strings.asm
+ @# And what those include. Everything here is named by an #Include somewhere and by
+ @# nothing else, which is exactly what makes it a library rather than a source.
+ @for f in CosmOS/Source/console.asm CosmOS/Source/fileStream.asm \
+ CosmOS/Source/sbfs.asm CosmOS/Source/services.asm CosmOS/Source/text.asm \
+ CosmOS/Assembler/classify.asm CosmOS/Assembler/labels.asm \
+ CosmOS/Assembler/numbers.asm CosmOS/Assembler/scratch.asm \
+ CosmOS/Assembler/source.asm CosmOS/Assembler/table.asm \
+ CosmOS/Assembler/token.asm CosmOS/Assembler/vectors.asm; do \
+ $(DISKTOOL) put $@ $$f /Lib/`basename $$f` >/dev/null || exit 1; done
# The system as well as the disk. Building only the image leaves whatever cosmos.bin was
# there before, or none at all, and then the disk is booted with a system that does not
diff --git a/SplitBit Assembler Manual.md b/SplitBit Assembler Manual.md
index d51a3ac..a5641d9 100644
--- a/SplitBit Assembler Manual.md
+++ b/SplitBit Assembler Manual.md
@@ -597,7 +597,9 @@ The host assembler reads every token of every file into one array and works on t
Two passes are enough because **every length is known without resolving anything**. How many bytes a token comes to falls out of what the token is - an instruction's from its shape, a value's is one, a string's is its characters and a zero - and never from the value of anything named. So the first pass works out exactly where every label lands and the second never needs a fixup list. A forward reference stops being a special case and becomes the reason there are two passes at all.
-One thing is genuinely easier here than on a host. The host assembler searches a list of include directories, because a host has directories; **SBFS is flat**, so an include is a file name and there is nowhere else to look.
+Both assemblers search for an include, and they search differently because they run on different machines. The host one walks the list given with `-I`, in order, after looking beside the file that asked. The native one looks in **the working directory and then in `/Lib`** - two places, both fixed, because a list somebody could set would need somewhere to live between one boot and the next and CosmOS has no such place yet.
+
+That search is what lets a disk be organised at all. Without it every source that calls a service would have to sit in the same directory as `services.asm`, and a disk would be one long list of names whatever the filesystem could do. It was one long list until SBFS grew directories: an include used to be a bare name with nowhere else to look, because there was nowhere else.
### Building Applications:
diff --git a/Tests/docs.sh b/Tests/docs.sh
index 38985ff..2a70113 100755
--- a/Tests/docs.sh
+++ b/Tests/docs.sh
@@ -430,6 +430,24 @@ for heading in ["## An Example SplitBit Assembly Program:",
problems.append("the example under \"%s\" no longer assembles"
% heading.strip("# :"))
+# ---- No manual still says the filesystem is flat ----
+#
+# It was flat, and every manual said so in passing, and those sentences went on being true
+# for as long as nobody looked. SBFS grew directories over five separate pieces of work and
+# the last thing left claiming otherwise was one line in the Assembler Manual explaining
+# why an include had nowhere else to look.
+#
+# A phrase rather than a section, because that is the shape this kind of staleness takes:
+# not a chapter that is wrong, one clause inside a paragraph that is otherwise right.
+for name, text in (("the Programming Manual", pm), ("the Assembler Manual", am),
+ ("the README", open("README.md").read()),
+ ("the CosmOS README", open("Programs/CosmOS/README.md").read())):
+ for claim in ("SBFS is flat", "the filesystem is flat", "flat filesystem",
+ "SBFS has no directories"):
+ if claim.lower() in text.lower():
+ problems.append("%s still says \"%s\", and it has not been true since"
+ " directories arrived" % (name, claim))
+
# ---- CosmOS fits in the half of the machine it says it does ----
#
# The memory map in the CosmOS README is a CONVENTION. Nothing in the assembler, the
diff --git a/Tests/expected/cosmosBreak.out b/Tests/expected/cosmosBreak.out
index 0cc49e3..ed66c6c 100644
--- a/Tests/expected/cosmosBreak.out
+++ b/Tests/expected/cosmosBreak.out
@@ -3,11 +3,11 @@ CosmOS
> two stops, and what the registers were at each
break at 400E
A 11 B 22 Q 00 status 00
-DP0 2030 DP1 09BC DP2 0000 DP3 4000 SP FFFF
+DP0 2030 DP1 09C4 DP2 0000 DP3 4000 SP FFFF
press a key
break at 4023
A 44 B 55 Q 00 status 00
-DP0 2000 DP1 09BC DP2 0000 DP3 4000 SP FFF5
+DP0 2000 DP1 09C4 DP2 0000 DP3 4000 SP FFF5
press a key
carried on to the end
finished