Let a script run a script, four deep
A build script calling a setup script is the first thing anybody tries. What is saved when one script starts another is A POSITION AND NOT A BUFFER: the name, which block comes next, how many are left, and where in the block it had got to. Seventy bytes, and they sit next to each other in the data segment on purpose so that saving them is one copy. The block itself is read again on the way back, which costs one disk read per return and saves 257 bytes a level - the inner script reads its own block into the single buffer there is, so coming back means fetching the outer one's block again and landing on the byte it left. The slot is reached by stepping rather than by multiplying, because this machine has no multiply and the depth is never more than three steps. Four levels. Deep enough for a script calling a script that calls a helper, shallow enough that a script running itself says so rather than filling memory. A line that fails now stops every level and not just the innermost, because a build whose helper failed should not carry on in its caller. The caller's place is saved BEFORE the new file is looked at, and put back on every way out that is not success. Opening writes the name into the live state in order to ask the disk about it, so by the time "there is no such file" is known, the caller's place has already been overwritten - a failed 'do' inside a script would otherwise leave the script that ran it reading from a name it never chose. The test resumes in the outer script's SECOND block, which is the case the whole design turns on and the one an ordinary nesting test would miss. Breaking the re-read, the save, or the limit each fails it. 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
a12d61fb80
commit
c28826df77
@@ -4,6 +4,7 @@
|
||||
; CALL scriptOpen Q = 0 and a script is running, or Q says what was wrong:
|
||||
; 1 there is no such file
|
||||
; 2 it is not a script - no #! on the front
|
||||
; 3 too many scripts inside each other
|
||||
; DP0 = a buffer, B = how much room
|
||||
; CALL scriptLine Q = 0 and there is a line in the buffer, or nonzero at the end
|
||||
;
|
||||
@@ -31,6 +32,87 @@
|
||||
|
||||
#Program
|
||||
|
||||
; ---- One script inside another ----
|
||||
;
|
||||
; A build script calling a setup script is the first thing anybody tries, so what is saved
|
||||
; when one script starts another is a POSITION AND NOT A BUFFER. The whole state of a
|
||||
; running script is its name, which block comes next, how many are left, and where in the
|
||||
; block it is - seventy bytes, laid out next to each other below so that saving it is one
|
||||
; copy. The block itself is read again on the way back, which costs one disk read per return
|
||||
; and saves 257 bytes a level.
|
||||
;
|
||||
; Four levels. Deep enough for a script calling a script that calls a helper, and shallow
|
||||
; enough that a script which runs itself says so instead of filling memory.
|
||||
scriptPush:
|
||||
CALL scriptSlotAt
|
||||
SETD.0 ScriptName
|
||||
PSHD.3
|
||||
POPD.1
|
||||
CALL scriptCopyState
|
||||
RET
|
||||
|
||||
scriptPop:
|
||||
CALL scriptSlotAt
|
||||
PSHD.3
|
||||
POPD.0
|
||||
SETD.1 ScriptName
|
||||
CALL scriptCopyState
|
||||
; ScriptAt points into the block buffer, which now holds somebody else's block. Reading
|
||||
; it back is what makes the saved pointer mean what it meant.
|
||||
CALL scriptReread
|
||||
RET
|
||||
|
||||
; DP3 = where the script one level up is remembered. Reached by stepping rather than by
|
||||
; multiplying, because this machine cannot multiply and the depth is never more than three
|
||||
; steps. DP3 because RET puts the others back.
|
||||
scriptSlotAt:
|
||||
SETD.3 ScriptSaved
|
||||
SETD.2 ScriptDepth
|
||||
LDA.2
|
||||
DECA
|
||||
BRA scriptSlotDone
|
||||
scriptSlotStep:
|
||||
DPUP.3 0d70
|
||||
DECA
|
||||
BNA scriptSlotStep
|
||||
scriptSlotDone:
|
||||
RET
|
||||
|
||||
; Seventy bytes, DP0 to DP1.
|
||||
scriptCopyState:
|
||||
INIB 0d70
|
||||
scriptCopyByte:
|
||||
LDA.0
|
||||
STA.1
|
||||
INCD.0
|
||||
INCD.1
|
||||
DECB
|
||||
BNB scriptCopyByte
|
||||
RET
|
||||
|
||||
; The block that is meant to be in the buffer, back in the buffer. ScriptIndex is the NEXT
|
||||
; one, so the one being read from is the one before it.
|
||||
scriptReread:
|
||||
SETD.1 ScriptIndex
|
||||
LDA.1
|
||||
INCD.1
|
||||
LDB.1
|
||||
DECB
|
||||
BNC scriptRereadGo
|
||||
DECA
|
||||
scriptRereadGo:
|
||||
SETD.0 ScriptName
|
||||
SETD.1 ScriptBlock
|
||||
SWI osFileBlock
|
||||
SETD.1 ScriptBlock
|
||||
PSHD.3
|
||||
POPB
|
||||
POPA
|
||||
DPUW.1
|
||||
RSTA
|
||||
STA.1
|
||||
RET
|
||||
|
||||
; ---- Opening ----
|
||||
;
|
||||
; The name is COPIED rather than remembered by address. osFileBlock is given the name again
|
||||
@@ -39,6 +121,25 @@
|
||||
; must stay valid; here it cannot, because the thing that reads the next line is the reason
|
||||
; the name is needed.
|
||||
scriptOpen:
|
||||
; ---- Four deep and no further ----
|
||||
SETD.1 ScriptDepth
|
||||
LDA.1
|
||||
INIB 0d4
|
||||
CCF
|
||||
SUB
|
||||
BRQ scriptOpenTooDeep
|
||||
|
||||
; ---- The one already running is put somewhere safe FIRST ----
|
||||
;
|
||||
; Before anything below overwrites it, and put back again on every way out of here that is
|
||||
; not success. Opening writes the name into the live state to ask the disk about it, so by
|
||||
; the time the answer is known the caller's place is already gone.
|
||||
;
|
||||
; A still holds the depth from the check above: SUB writes Q and leaves it alone.
|
||||
BRA scriptOpenFirst
|
||||
CALL scriptPush
|
||||
scriptOpenFirst:
|
||||
|
||||
SETD.1 ScriptName
|
||||
INIB 0d63
|
||||
CALL copyText
|
||||
@@ -92,15 +193,29 @@ scriptOpenThere:
|
||||
; Past the shebang line, wherever it ends.
|
||||
CALL scriptSkipLine
|
||||
|
||||
INIA 0x01
|
||||
SETD.1 ScriptRunning
|
||||
SETD.1 ScriptDepth
|
||||
LDA.1
|
||||
INCA
|
||||
STA.1
|
||||
RSTA
|
||||
BRI scriptOpenFailed ; A is nought, which is the answer for "it opened".
|
||||
BRI scriptOpenAnswer ; A is nought, which is the answer for "it opened".
|
||||
|
||||
scriptOpenTooDeep:
|
||||
INIA 0x03
|
||||
BRI scriptOpenAnswer ; Nothing was pushed, so there is nothing to put back.
|
||||
|
||||
scriptOpenNotOne:
|
||||
INIA 0x02
|
||||
scriptOpenFailed:
|
||||
; Whatever was running is still running, and its place is in the slot rather than in the
|
||||
; live state. A is the answer and must survive being put back.
|
||||
SETD.1 ScriptDepth
|
||||
LDB.1
|
||||
BRB scriptOpenAnswer
|
||||
PSHA
|
||||
CALL scriptPop
|
||||
POPA
|
||||
scriptOpenAnswer:
|
||||
; Q is the answer, and A holds it. Adding nought is how a register becomes Q.
|
||||
RSTB
|
||||
CCF
|
||||
@@ -322,16 +437,35 @@ scriptSkipLine:
|
||||
scriptSkipDone:
|
||||
RET
|
||||
|
||||
; One script ending. Whatever asked for it carries on, if anything did.
|
||||
scriptClose:
|
||||
SETD.1 ScriptDepth
|
||||
LDA.1
|
||||
BRA scriptCloseNone
|
||||
DECA
|
||||
STA.1
|
||||
BRA scriptCloseNone
|
||||
CALL scriptPop
|
||||
scriptCloseNone:
|
||||
RET
|
||||
|
||||
; Every script ending at once, which is what a line that did not work means. A build whose
|
||||
; helper failed should not carry on in the script that called the helper either.
|
||||
scriptAbandon:
|
||||
RSTA
|
||||
SETD.1 ScriptRunning
|
||||
SETD.1 ScriptDepth
|
||||
STA.1
|
||||
RET
|
||||
|
||||
#Data
|
||||
|
||||
ScriptRunning:
|
||||
ScriptDepth:
|
||||
0x00
|
||||
|
||||
; ---- Seventy bytes, and they are next to each other on purpose ----
|
||||
;
|
||||
; Name, blocks left, next block, where in the block: the whole of where a script has got to.
|
||||
; Saving it is one copy because of this order, and nothing else may be put between them.
|
||||
ScriptName:
|
||||
#Reserve 0d64
|
||||
ScriptBlocks:
|
||||
@@ -340,6 +474,11 @@ ScriptIndex:
|
||||
0x00 0x00
|
||||
ScriptAt:
|
||||
0x00 0x00
|
||||
|
||||
; Three would do - a save happens on the second script and not the first - but four costs
|
||||
; seventy bytes and removes an off-by-one from the only place it could hide.
|
||||
ScriptSaved:
|
||||
#Reserve 0d280
|
||||
ScriptInto:
|
||||
0x00 0x00
|
||||
ScriptRoom:
|
||||
|
||||
Reference in New Issue
Block a user