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