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
195 lines
4.7 KiB
NASM
195 lines
4.7 KiB
NASM
; 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
|