From 2defbb49e211414b7ab3c04d6e30a8ceed84fe4f Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Sun, 6 Sep 2026 15:27:57 -0400 Subject: [PATCH] A program can ask where it came from SWI osWhereAmI hands back the path the program was loaded from, on the same terms as osArgument, and Libraries/path.asm joins a name to the place another thing is in. Between them an application can find its own assets: ask where you are, then pathBeside that and the file's name. The answer FOLLOWS THE PROGRAM AND NOT THE PERSON, which is the whole point and the reason the working directory could not serve. A program's assets are relative to the program and its arguments are relative to whoever ran it, and cwd can only be one of them - setting it to the program's own would mean "Play mytune.tune", typed by somebody in their own directory, looked in Play's. It is made absolute before the program starts, because the path the search settled on may be a bare name: a program found where somebody was standing is named by the word that was typed, and a bare name means the working directory - which a program is entitled to move out of. Worked out once, at the start, since where a program came from is a fact about its start and cannot change afterwards. Joining is a LIBRARY and not a service. A service that opened a file relative to the program would need a twin for every file operation there is - read, save, info, block, start, write, done, delete, rename - while one service handing back a path composes with all of them. ---- And the root's own path was "//" ---- Found by the first caller that asks. shellPath prepends a separator in front of whatever string it is given, so being handed the separator itself wrote two of them. Nothing saw it while the only caller was the prompt, which asks where it is only when that is not the root. It is handed an empty string now, and cosmosWhere runs a program from the root. Where.sbx exists to be run rather than read, and is on the test disk twice: at the root, where it is found by the bare word typed, and in /Apps, where it is found by a path that already says where it is. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW --- Programs/CosmOS/Apps/Where.asm | 100 ++++++++++++++ Programs/CosmOS/README.md | 47 ++++++- Programs/CosmOS/Source/cosmos.asm | 124 +++++++++++++++++- Programs/CosmOS/Source/services.asm | 23 ++++ Programs/Libraries/path.asm | 194 ++++++++++++++++++++++++++++ SplitBit Test Manual.md | 2 +- Tests/expected/cosmosCrossDisk.out | 5 +- Tests/expected/cosmosDrives.out | 5 +- Tests/expected/cosmosFault.out | 5 +- Tests/expected/cosmosFlip.out | 5 +- Tests/expected/cosmosGrid.out | 5 +- Tests/expected/cosmosMonitor.out | 5 +- Tests/expected/cosmosMonitorRun.out | 5 +- Tests/expected/cosmosRun.out | 5 +- Tests/expected/cosmosSlowDisk.out | 5 +- Tests/expected/cosmosSprite.out | 5 +- Tests/expected/cosmosWhere.out | 29 +++++ Tests/input/cosmosWhere.in | 10 ++ Tests/makedisks.sh | 10 ++ Tests/manifest | 17 +++ 20 files changed, 583 insertions(+), 23 deletions(-) create mode 100644 Programs/CosmOS/Apps/Where.asm create mode 100644 Programs/Libraries/path.asm create mode 100644 Tests/expected/cosmosWhere.out create mode 100644 Tests/input/cosmosWhere.in diff --git a/Programs/CosmOS/Apps/Where.asm b/Programs/CosmOS/Apps/Where.asm new file mode 100644 index 0000000..1223ce6 --- /dev/null +++ b/Programs/CosmOS/Apps/Where.asm @@ -0,0 +1,100 @@ +; A program that says where it came from. +; +; The dullest thing on the disk, and that is the point of it: osWhereAmI and pathBeside are +; what an application with assets stands on, and both are much easier to check now, with +; nothing depending on them, than later inside a game that has gone quiet. +; +; Where says the path it was loaded from +; Where splash.tune says that, and the path of splash.tune beside it +; +; ---- The thing worth checking ---- +; +; The answer follows the PROGRAM and not the person. Run this from the root and it says one +; thing; walk somewhere else and run the same copy and it says the same thing, because the +; system worked it out when the program started and a program's own place cannot move under +; it. A working directory is where the person is standing, and it is what the argument means: +; "Where notes.txt" asks about a notes.txt in the program's place, not in yours, which is +; exactly the distinction an application needs and the reason cwd could not serve both. + +#Include services.asm + +#Program + + #Base 0x5000 + +start: + SETD.0 MyPath + INIB 0d192 + SWI osWhereAmI + + SETD.0 FromText + SWI osPrintString + SETD.0 MyPath + SWI osPrintString + CALL newLine + + ; With nothing after it, that is the whole of what it has to say. + SETD.0 Given + INIB 0d64 + SWI osArgument + SETD.0 Given + LDA.0 + BRA whereDone + + SETD.0 MyPath + SETD.1 Given + SETD.2 Beside + INIB 0d192 + CALL pathBeside + BNQ whereTooLong + + SETD.0 BesideText + SWI osPrintString + SETD.0 Beside + SWI osPrintString + CALL newLine + +whereDone: + RSTA + SWI osExit + +whereTooLong: + ; A path that did not fit is a wrong answer, and saying it would be worse than saying so. + SETD.0 TooLongText + SWI osPrintString + CALL newLine + INIA 0x01 + SWI osExit + +newLine: + SETD.0 NewLine + SWI osPrintString + RET + +#Data + + #Base 0x3000 + +FromText: +"loaded from " +BesideText: +"beside it: " +TooLongText: +"that path would not fit" +NewLine: + 0x0A 0x00 + +MyPath: + #Reserve 0d192 +Beside: + #Reserve 0d192 +Given: + #Reserve 0d64 + +; ---- Included last, after both segments have been based ---- +; +; A #Base says where a whole segment begins and so has to be the first thing in it, and a +; library brings code and data of its own. Put in front, its #Data would be the first thing +; in the Data Segment and this program's own #Base would be too late. This is where Lander +; keeps its seven, for the same reason. +#Include path.asm diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index 7ec8cd6..ead634f 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -758,7 +758,7 @@ laid out in three directories: | -- | -- | | `/Apps` | What you run. The second place the shell looks for a word it does not recognise, so anything here starts by name from anywhere on the disk - programs, and scripts too. | | `/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. | +| `/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. `path.asm` is the newest of them: joining a name to a place, which is arithmetic on two strings and so wants no help from the system. | 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 @@ -790,6 +790,7 @@ from every assembly file in it. Several are old programs written for the bare ma | Keys | The console interrupting rather than being asked. The only one that brings a vector of its own, which is what the version two format exists for. | | Play | Four voices on one clock, which is what music is and one channel cannot be. The timer keeps a tick and every voice keeps its own place in its own track and its own count of how much longer the note it is holding lasts, so the parts move at four different rates and share nothing but the beat. A track is pairs of bytes, what to play and how many ticks it lasts: 1 to 127 is a MIDI note, zero is a rest, and 255 ends it - MIDI stops at 127, so neither of those had to be invented. A note's duration is its whole life and the gate goes down when the count runs out, which means a gap between two notes is written as a rest rather than invented by the player out of some fraction it decided on. Each voice loads an instrument of its own before a note is played - an oboe for the melody, strings under it, a square wave for the bass and a kalimba for the arpeggio - out of the format SoundPatch writes, which the player reads as a count and that many parameter and value pairs and understands nothing else about. Four patches can be up at once because a patch belongs to its channel; Kalimba has an LFO switched off and the other three have one on, and under a device where the LFOs belonged to the whole machine the last patch loaded would have imposed its setting on every part. A voice does not play one long track: it walks an ORDER LIST of its own, a table of sequence addresses, and takes the next one when a sequence runs out. That is where repetition comes from and it costs no notation - the bass plays the same sequence in the first bar and the last, written once. The four columns are how it reads and how a tracker would show it; per voice is how it is stored, because a voice's order cursor is then a pointer it advances by itself. Sequence and patch names are INDICES through two tables, which are the only places an address lives - so a tune read from a file will need its base added to two arrays and nothing else, rather than a loader that walks every sequence looking for addresses to correct. A sequence can also carry commands, which take no time at all: 0x80 plays the rest of that voice on a different patch, which is how the melody's last bar becomes a swell rather than a reed. Nothing keeps the voices together except that their sequences add up to the same length, which is the first thing a compiler should check. The patch each voice starts on is declared rather than assumed, because a voice given no instrument would play on whatever the device woke up with. `Play ` reads a tune and plays that; `Play` on its own plays the one built into it. A tune file is "SBTU", a version, the tick in cycles, and offsets to a patch table, a sequence table and four order lists - everything in it an OFFSET from wherever it was put, so loading one is adding the base to two tables and pointing four voices at their order lists. No sequence is walked and nothing inside one is an address, which is what makes a malformed tune something that plays wrongly rather than something that takes the loader with it; the magic is checked first, because the loader follows what the offsets name. The patch each voice starts on is in the header, because a starting instrument is state. The engine that plays it is Libraries/player.asm rather than this program. It spends over ninety nine per cent of its time asleep, because a beat is something to be woken by rather than counted up to. | | Say | Prints whatever it was told, which is the shortest thing that shows osArgument working. | +| Where | Says the path it was loaded from, and with a name after it, the path of that name beside it. The shortest thing that shows osWhereAmI and path.asm working, and the answer follows the program rather than whoever ran it. | | Reboot | Starts the machine again, in 45 bytes. Writes a port rather than asking the system, because a reset has to work when the system does not. | | Once | Asks the loader to start something else on the next start, and only that one, in 569 bytes. | | Status | Says what the last program made of what it was asked to do, in 222 bytes. The shell keeps the number and does not print it; this is how a person looks. | @@ -1346,6 +1347,7 @@ Those numbers are written down once, in `Programs/CosmOS/Source/services.asm`, w | osBreak | Stops the program, shows every register as it had them, waits for a key, and carries on. | | osLastStatus | Q answers what the last program exited with: 0 it did what it was asked, 1 it did not, 2 it was asked wrongly. A program may give its own meanings if it says so. | | osTakeScreen | Says this program is about to use the whole screen and would like what is on it put back when it exits. Q is zero if that was arranged; anything else means it was not, which is the ordinary answer on a machine with no volatile drive. See Giving The Screen Back. | +| osWhereAmI | DP0 names somewhere to put the path the program was loaded from, B says how much room there is. See Where A Program Came From. | | osBootState | Q answers how the last start went: 0 settled, 1 trying, 2 fell back. A machine with no disk answers settled, because there is nothing there to be unsettled about. | | osBootSettle | Puts it back to settled, which is how a machine that fell back is told the situation has changed. Q is zero if the disk took it. **Settling is the only write a program gets** - marking a start as trying or fallen back is the loader's business, and a service that let a program claim either would let it lie about something the loader cannot check. | @@ -1443,6 +1445,49 @@ Running off the end is how a reader finds out it has finished, so it gets an ans A handler is entered with the caller's registers exactly as they were, because an interrupt frame is pushed rather than cleared. That is why a service can be given a pointer in DP0 and a count in B without any of it being copied anywhere first. +## Where A Program Came From: + +`SWI osWhereAmI` hands back the path the program was loaded from, on the same terms as +`osArgument`: DP0 says where to put it and B how much room there is. + +```text +/work> Where +loaded from /Apps/Where.sbx +``` + +**The answer follows the program and not the person.** It is worked out once, before the +program is given the machine, and a program's own place cannot move under it afterwards. +Standing in `/work` and running the copy in `/Apps` is told `/Apps`, which is the whole point. + +**Made absolute, because the path the search settled on may be a bare name.** A program found +where somebody was standing is named by the word that was typed, and a bare name means the +working directory - which a program is entitled to move out of. So a program asked later +where it lives would be told "beside wherever you are now", which is the one answer that is +certainly wrong. + +**Why this and not a service that opens a file beside the program.** That service would need a +twin for every file operation there is: read, save, info, block, start, write, done, delete, +rename. One service handing back a path composes with all of them, and joining a name to a +place becomes a library instead - `/Lib/path.asm`, whose `pathBeside` takes a path and a name +and answers with the name in the same place. + +``` + SETD.0 MyPath + SETD.1 Given + SETD.2 Beside + INIB 0d192 + CALL pathBeside +``` + +**And why not simply put the program in its own directory.** Because a program's *assets* are +relative to the program and its *arguments* are relative to the person, and the working +directory can only be one of them. Setting it to the program's own would mean `Play +mytune.tune`, typed by somebody in their own directory, looked in `Play`'s. The working +directory stays the person's; this is how a program finds its own things. + +`Programs/CosmOS/Apps/Where.asm` is the whole of it in one program, and exists to be run +rather than read. + ## The Filesystem Library: The disk knows blocks and nothing else, so a filesystem is software. Programs/CosmOS/Source/sbfs.asm is one. diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index c06850f..0c29f7a 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -4763,11 +4763,16 @@ shellPathStep: shellPathDone: ; A machine at the root has written nothing at all, and the path to the root is the ; separator on its own. + ; + ; AN EMPTY STRING, because prepending puts a separator in FRONT of whatever it is given - + ; so being handed the separator itself wrote two of them and the root came out as "//". + ; Nothing saw it for as long as the only caller was the prompt, which asks where it is + ; only when that is not the root; the first caller that always asks found it at once. SETD.0 CwdAt LDD.1.0 LDA.1 BNA shellPathReady - SETD.0 Separator + SETD.0 NoText CALL shellPathPrepend shellPathReady: RET @@ -5649,6 +5654,9 @@ runLoaded: INIB 0d64 CALL copyText + ; And where the program itself came from, so it can be asked later. + CALL runWhere + CALL installVectors ; The entry address is a number until BRD makes it a place. DP3 is the one to build it @@ -5657,6 +5665,98 @@ runLoaded: LDD.3.1 BRD.3 +; ---- Where the program itself came from ---- +; +; ProgramName holds the path the search settled on, and that path may be a bare name: a +; program found where somebody was standing is named by the word that was typed. A BARE NAME +; MEANS THE WORKING DIRECTORY, and a program is entitled to move out of it - so a program +; asked later where it lives would be told "beside wherever you are now", which is the one +; answer that is certainly wrong. +; +; So it is made absolute HERE, once. Where a program came from is a fact about its start and +; cannot change afterwards; where the person is can change on the program's own next line. +runWhere: + ; A path that begins with a separator has already said where it is. + SETD.0 ProgramName + LDA.0 + INIB 0x2F + CCF + SUB + BRQ runWhereAsFound + + ; Or a drive in front of it, which says so twice over. A digit and then a colon, both, the + ; same test the path walker makes - a name may begin with a digit, and the colon is the + ; whole of what tells the two apart. + LDA.0 + INIB 0x30 + CCF + SUB + BRC runWhereRelative ; Below a digit. + LDA.0 + INIB 0x3A + CCF + SUB + BNC runWhereRelative ; Above one. + INCD.0 + LDA.0 + INIB 0x3A ; splitlint[redundant-assignment]: 0x3A above is one past '9' and here it is ':' + CCF + SUB + BRQ runWhereAsFound + +runWhereRelative: + ; Where the machine is, written out. shellPath builds it backwards from the end of its + ; buffer and hands back a pointer into the middle, which is a string like any other. + CALL shellPath + SETD.0 CwdAt + LDD.0.0 + SETD.1 RunPath + INIB 0d127 + CALL copyText + + ; The end of it, and the last character on the way past. At the root there is no last + ; character at all, which is an answer rather than a special case: nought is not a + ; separator, so a separator gets written and the path begins with one. + SETD.0 RunPath + RSTA + SETD.1 RunPathLast + STA.1 +runWhereEnd: + LDA.0 + BRA runWhereJoin + SETD.1 RunPathLast + STA.1 + INCD.0 + BRI runWhereEnd + +runWhereJoin: + SETD.1 RunPathLast + LDA.1 + INIB 0x2F + CCF + SUB + BRQ runWhereName ; It ends in a separator already, so a second would be a lie. + INIA 0x2F + STA.0 + INCD.0 + +runWhereName: + ; And the program's own name under it. DP0 is where it goes, which is at most 127 bytes + ; along a buffer of 192 - so the 64 a name can be always fits. + PSHD.0 + POPD.1 + SETD.0 ProgramName + INIB 0d64 + CALL copyText + RET + +runWhereAsFound: + SETD.0 ProgramName + SETD.1 RunPath + INIB 0d192 + CALL copyText + RET + runNothing: SETD.0 NothingLoaded CALL printString @@ -5836,6 +5936,16 @@ handleArgument: CALL copyText RETI +; The same bargain for the other thing a program is entitled to know about its own start: +; where it came from. Worked out in runWhere before the program was given the machine, so +; this only has to hand it over. +handleWhereAmI: + PSHD.0 + POPD.1 + SETD.0 RunPath + CALL copyText + RETI + ; ---- The disk, on a program's behalf ---- ; ; A loaded program that wanted a file used to include the whole filesystem, so it carried a @@ -8110,6 +8220,17 @@ SearchDrive: 0x00 RunDrive: 0x00 + +; ---- Where the program came from, made absolute ---- +; +; A hundred and ninety two, which is a working directory of up to 127, a separator, and a +; name of up to 64. None of those three can be made longer without the others being counted +; again. +RunPath: + #Reserve 0d192 +RunPathLast: + 0x00 + ScreenSaved: 0x00 ScreenWasDrive: @@ -8530,6 +8651,7 @@ CommandLine: osBreak handleBreak osLastStatus handleLastStatus osTakeScreen handleTakeScreen + osWhereAmI handleWhereAmI osBootState handleBootState osBootSettle handleBootSettle Device 0x20 diskDone diff --git a/Programs/CosmOS/Source/services.asm b/Programs/CosmOS/Source/services.asm index d21d313..b4e7a8f 100644 --- a/Programs/CosmOS/Source/services.asm +++ b/Programs/CosmOS/Source/services.asm @@ -185,3 +185,26 @@ ; NOT AUTOMATIC, and that is the point: dir and Say print and stop, and their output is the ; reason you ran them. Only a program that says it took the screen gets the screen put back. osTakeScreen 0d36 + +; ---- Where the program itself came from ---- +; +; DP0 says where to put it and B is how much room there is counting the zero, the same +; bargain osArgument offers. What comes back is the whole path the program was loaded from, +; made absolute: a program found where somebody was standing is named by the bare word that +; was typed, and a bare name means the working directory - which a program is entitled to +; move out of. +; +; ---- Why this and not "open a file beside me" ---- +; +; A service that opened a file relative to the program would need a TWIN FOR EVERY FILE +; OPERATION there is: read, save, info, block, start, write, done, delete, rename. One +; service handing back a path composes with all of them, and joining a name to a place is +; then a library rather than a service - Libraries/path.asm, beside print.asm and math.asm. +; +; ---- And why not just move the program there ---- +; +; Because a program's ASSETS are relative to the program and its ARGUMENTS are relative to +; the person, and the working directory can only be one of them. Setting it to the program's +; own would mean "Play mytune.tune", typed by somebody in their own directory, looked in +; Play's. The working directory stays the person's; this is how a program finds its own. + osWhereAmI 0d37 diff --git a/Programs/Libraries/path.asm b/Programs/Libraries/path.asm new file mode 100644 index 0000000..0c8dd09 --- /dev/null +++ b/Programs/Libraries/path.asm @@ -0,0 +1,194 @@ +; path.asm +; Putting a name together with the place another thing is in. +; +; DP0 = a path, DP1 = a name, DP2 = where the answer goes, B = how much room, counting +; the zero on the end +; CALL pathBeside Q is zero if the whole of it fitted +; +; Written by Anachronaut +; +; ---- What this is for ---- +; +; A program asks the system where it came from - SWI osWhereAmI - and is handed the path it +; was loaded from. What it actually wants is the path of something NEXT TO that: its tune, +; its tiles, its saved state. This is that one step, and it is a library rather than a +; service because it is arithmetic on two strings and touches nothing the system owns. +; +; That split is the whole reason osWhereAmI hands back a path instead of opening files on a +; program's behalf. A service that opened a file relative to the program would need a twin +; for every file operation there is; a path composes with all of them, and the joining +; happens once, here, on a machine that has no string library to do it in each program. +; +; ---- Everything up to the last separator ---- +; +; The place a thing is in is everything up to and INCLUDING the last separator in its path, +; so joining is a copy and not a search backwards from the end. A path with no separator in +; it names something in the working directory, and the answer is then the name on its own - +; which means exactly the same thing, in the same place, and needs no special case. +; +; /Packages/app.Lander/Lander + splash.tune = /Packages/app.Lander/splash.tune +; /Lander.sbx + splash.tune = /splash.tune +; Lander.sbx + splash.tune = splash.tune +; +; ---- The room is counted once, in one place ---- +; +; Both halves go through pathPut, which counts the room down and always keeps a byte back +; for the zero. So an answer that did not fit is still a STRING, endable and printable, and +; a caller that ignores Q gets a short path rather than a walk off the end of its buffer. +; It gets a wrong answer, which is why Q exists - but not a broken machine. +; +; The answer is built through a stored pointer rather than DP2, because a CALL puts DP0 to +; DP2 back the way it found them and pathPut would otherwise write the same byte every time. + +#Program + +pathBeside: + SETD.3 PathPut + STD.2.3 + SETD.3 PathRoom + STB.3 + + ; ---- How much of the path is the place it is in ---- + ; + ; Counted rather than pointed at, because what is wanted is a NUMBER OF BYTES TO COPY and + ; a pointer would have to be turned into one. The count is set afresh at every separator, + ; so what it holds at the end is the last one - which is the one that matters. + PSHD.0 ; The front of the path, to come back to. + RSTA + SETD.3 PathCut + STA.3 + SETD.3 PathAt + STA.3 + +pathScan: + LDA.0 + BRA pathScanned + INIB 0x2F + CCF + SUB + BNQ pathScanStep + + ; A separator, so everything up to and including it is the place. + SETD.3 PathAt + LDA.3 + INCA + SETD.3 PathCut + STA.3 + +pathScanStep: + SETD.3 PathAt + LDA.3 + INCA + STA.3 + INCD.0 + BRI pathScan + +pathScanned: + POPD.0 + SETD.3 PathCut + LDA.3 + SETD.3 PathLeft + STA.3 + +pathPlace: + SETD.3 PathLeft + LDA.3 + BRA pathName ; All of the place is written, or there was none of it. + DECA + STA.3 + LDA.0 + CALL pathPut + BNQ pathNoRoom + INCD.0 + BRI pathPlace + +pathName: + PSHD.1 + POPD.0 +pathNameChar: + LDA.0 + BRA pathMade + CALL pathPut + BNQ pathNoRoom + INCD.0 + BRI pathNameChar + +pathMade: + CALL pathEnd + RSTA + RSTB + CCF + ADD ; Q is zero: the whole of it fitted. + RET + +pathNoRoom: + CALL pathEnd + INIA 0x01 + RSTB + CCF + ADD + RET + +; The zero that makes it a string. There is always room for it: pathPut refuses the byte +; that would have taken the last one. +pathEnd: + SETD.3 PathPut + LDD.2.3 + RSTA + STA.2 + RET + +; A holds a character. Puts it where the answer has got to and steps that on. Q is one if +; there is no room for it. +pathPut: + SETD.3 PathHold + STA.3 ; The character, across the pointer being fetched and put back. + + SETD.3 PathRoom + LDB.3 + DECB + BRB pathPutFull ; Only the zero's worth left, so this byte cannot be written. + STB.3 + + SETD.3 PathPut + LDD.2.3 + SETD.3 PathHold + LDA.3 + STA.2 + INCD.2 + SETD.3 PathPut + STD.2.3 + + RSTA + RSTB + CCF + ADD + RET + +pathPutFull: + INIA 0x01 + RSTB + CCF + ADD + RET + +#Data + +; Where the answer has got to, and how much room is left in it. +PathPut: + 0x00 0x00 +PathRoom: + 0x00 + +; How much of the path is the place it is in, how far the scan has got, and how much of the +; place is still to be written. +PathCut: + 0x00 +PathAt: + 0x00 +PathLeft: + 0x00 + +; One character, across the fetching and putting back of the pointer it is written through. +PathHold: + 0x00 diff --git a/SplitBit Test Manual.md b/SplitBit Test Manual.md index be807a4..33a0d8a 100644 --- a/SplitBit Test Manual.md +++ b/SplitBit Test Manual.md @@ -125,7 +125,7 @@ from `make`, not from here. ### 1. Recorded output `Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares -everything it printed against a file in `Tests/expected`. 214 tests, of which 152 run, 35 +everything it printed against a file in `Tests/expected`. 215 tests, of which 153 run, 35 only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image given at all. diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out index eb1784c..6ae7f0d 100644 --- a/Tests/expected/cosmosCrossDisk.out +++ b/Tests/expected/cosmosCrossDisk.out @@ -18,6 +18,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -31,7 +32,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -50,7 +51,7 @@ args.script 64 pass.script 20 holds.script 87 crossed.txt 560 -37 files, 1 directory +38 files, 1 directory > exit halted Execution halted. diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index bc72338..53c84a0 100644 --- a/Tests/expected/cosmosDrives.out +++ b/Tests/expected/cosmosDrives.out @@ -8,6 +8,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -21,7 +22,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -39,7 +40,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -36 files, 1 directory +37 files, 1 directory > drive 1 > dir other.txt 28 diff --git a/Tests/expected/cosmosFault.out b/Tests/expected/cosmosFault.out index 7b146c4..33cf4f4 100644 --- a/Tests/expected/cosmosFault.out +++ b/Tests/expected/cosmosFault.out @@ -25,6 +25,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -38,7 +39,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -56,7 +57,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -36 files, 1 directory +37 files, 1 directory > exit halted Execution halted. diff --git a/Tests/expected/cosmosFlip.out b/Tests/expected/cosmosFlip.out index 82cb848..7d4e805 100644 --- a/Tests/expected/cosmosFlip.out +++ b/Tests/expected/cosmosFlip.out @@ -15,6 +15,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -28,7 +29,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -46,7 +47,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -36 files, 1 directory +37 files, 1 directory > exit halted Execution halted. diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index a2480e5..e96e626 100644 --- a/Tests/expected/cosmosGrid.out +++ b/Tests/expected/cosmosGrid.out @@ -15,6 +15,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -28,7 +29,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -46,7 +47,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -36 files, 1 directory +37 files, 1 directory > exit halted Execution halted. diff --git a/Tests/expected/cosmosMonitor.out b/Tests/expected/cosmosMonitor.out index 5ecd81a..f672baa 100644 --- a/Tests/expected/cosmosMonitor.out +++ b/Tests/expected/cosmosMonitor.out @@ -101,6 +101,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -114,7 +115,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -132,7 +133,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -36 files, 1 directory +37 files, 1 directory > exit halted Execution halted. diff --git a/Tests/expected/cosmosMonitorRun.out b/Tests/expected/cosmosMonitorRun.out index 30a28a6..6ea896a 100644 --- a/Tests/expected/cosmosMonitorRun.out +++ b/Tests/expected/cosmosMonitorRun.out @@ -20,6 +20,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -33,7 +34,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -51,7 +52,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -36 files, 1 directory +37 files, 1 directory > exit halted Execution halted. diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index 286468f..b0ee8cf 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -8,6 +8,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -21,7 +22,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -39,7 +40,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -36 files, 1 directory +37 files, 1 directory > load load what? > load nosuch.sbx diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index 4ad8067..cb21626 100644 --- a/Tests/expected/cosmosSlowDisk.out +++ b/Tests/expected/cosmosSlowDisk.out @@ -6,6 +6,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -19,7 +20,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -37,7 +38,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -36 files, 1 directory +37 files, 1 directory > load Say.sbx loaded, starting at 5000 > run the disk took its time diff --git a/Tests/expected/cosmosSprite.out b/Tests/expected/cosmosSprite.out index 3c3f577..baa747a 100644 --- a/Tests/expected/cosmosSprite.out +++ b/Tests/expected/cosmosSprite.out @@ -15,6 +15,7 @@ Life.sbx 1396 Snake.sbx 2164 Keys.sbx 664 Say.sbx 156 +Where.sbx 827 Break.sbx 149 Grid.sbx 571 Press.sbx 872 @@ -28,7 +29,7 @@ Crash.sbx 632 vars.script 50 blocks.script 343 loops.script 272 -splash.tune 189 +splash.tune 210 tune.sbx 318 Play.sbx 2526 notes.txt 21 @@ -46,7 +47,7 @@ aloud.script 59 args.script 64 pass.script 20 holds.script 87 -36 files, 1 directory +37 files, 1 directory > exit halted Execution halted. diff --git a/Tests/expected/cosmosWhere.out b/Tests/expected/cosmosWhere.out new file mode 100644 index 0000000..8c3d659 --- /dev/null +++ b/Tests/expected/cosmosWhere.out @@ -0,0 +1,29 @@ +CosmOS +> Where +loaded from /Where.sbx +finished +> Where splash.tune +loaded from /Where.sbx +beside it: /splash.tune +finished +> mkdir /work +made +> cd /work +/work> Where +loaded from /Apps/Where.sbx +finished +/work> Where tiles.atlas +loaded from /Apps/Where.sbx +beside it: /Apps/tiles.atlas +finished +/work> cd / +> load /Apps/Where.sbx +loaded, starting at 5000 +> run notes.txt +loaded from /Apps/Where.sbx +beside it: /Apps/notes.txt +finished +> exit +halted +Execution halted. +[exit 0] diff --git a/Tests/input/cosmosWhere.in b/Tests/input/cosmosWhere.in new file mode 100644 index 0000000..bff9bb4 --- /dev/null +++ b/Tests/input/cosmosWhere.in @@ -0,0 +1,10 @@ +Where +Where splash.tune +mkdir /work +cd /work +Where +Where tiles.atlas +cd / +load /Apps/Where.sbx +run notes.txt +exit diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index d6165ab..cc62f6c 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -105,6 +105,15 @@ for i in 1 2 3 4 5 6 7 8; do "$TOOL" put "$DISKS/sbfs.img" "filler$i.txt" >/dev/ "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ "$ROOT/Programs/CosmOS/Apps/Say.asm" -o "$WORK/Say.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" >/dev/null +# Where.sbx says where it was loaded from, and where a name would be beside it. TWO COPIES +# on purpose, which is the whole test: the one at the root is found where you are standing +# and is named by the bare word that was typed, so the system has to work out what that word +# meant; the one in /Apps is found by a path that already says where it is. Both have to come +# out saying the same kind of thing, and the /Apps one has to go on saying it from a +# directory that has no copy of its own. +"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \ + "$ROOT/Programs/CosmOS/Apps/Where.asm" -o "$WORK/Where.sbx" >/dev/null +"$TOOL" put "$DISKS/cosmos.img" "$WORK/Where.sbx" >/dev/null # Break.sbx stops itself twice and shows what the registers were each time. It needs no disk # of its own: it only prints. "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ @@ -227,6 +236,7 @@ python3 -c "open('twoblocks.txt','w').write('the second disk, at length. ' * 20) "$ROOT/Programs/CosmOS/Apps/Copy.asm" -o "$WORK/Copy.sbx" >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Copy.sbx" /Apps/Copy.sbx >/dev/null "$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null +"$TOOL" put "$DISKS/cosmos.img" "$WORK/Where.sbx" /Apps/Where.sbx >/dev/null # A SCRIPT in the system's place, which is the one thing on this disk that is fetched from # drive 0 a block at a time WHILE it runs. Its own name carries the drive in front of it, # and resolving a name like that moves the machine to that drive - so a script started from diff --git a/Tests/manifest b/Tests/manifest index a132533..f81d0b3 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -1000,6 +1000,23 @@ cosmosScriptNest | CosmOS/Source/cosmos.asm | run | cosmosScr # which would let a command run with an argument missing. At the prompt they are ordinary # unset names, because there is no script to have been given anything. cosmosScriptArgs | CosmOS/Source/cosmos.asm | run | cosmosScriptArgs.in | 200000000 | disks/cosmos.img +# ---- Where a program came from ---- +# +# osWhereAmI and Libraries/path.asm, which are what an application with assets stands on. +# +# THE ANSWER FOLLOWS THE PROGRAM AND NOT THE PERSON, which is the claim and the reason the +# working directory could not serve: a program's assets are relative to the program and its +# arguments are relative to whoever ran it. Standing in /work, the copy in /Apps says /Apps +# and puts its asset there, while the argument still means what the person typed. +# +# Two copies of it on this disk reach the system by different routes. The one at the root is +# found where you are standing and named by the bare word typed, so the path has to be worked +# out from where the person was; the one in /Apps arrives already absolute. Running it from +# the root is also the check on the root's own path, which came out as "//" until the first +# caller that asks there rather than only when somebody has moved. +# +# And load then run, because that is the other way into the same place. +cosmosWhere | CosmOS/Source/cosmos.asm | run | cosmosWhere.in | 200000000 | disks/cosmos.img # ---- Starting itself ---- # # /System/Boot/startup.sh runs before anybody can type. This one is also the check on #quiet