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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
d9ebc76cf5
commit
2defbb49e2
@@ -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 <file>` 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.
|
||||
|
||||
Reference in New Issue
Block a user