S2: the assembler writes the file as it makes it
The output image is gone. It was eighteen kilobytes and it is now one block of window, because the file was always produced in order and only ever needed to be written that way. Everything works in FILE OFFSETS now. A cursor is a two byte number counting from the front of the file, and since a block is two hundred and fifty six bytes, the block it lands in is the offset's high byte and the place within that block is its low one - so there is no division anywhere, and ImgWalk, ProgPut and DataPut needed no change but where they start. ONE WINDOW RATHER THAN THREE. The plan said three: one per segment, and a third for the block where the program ends and the data begins, which belongs to both. Fetching a block back instead makes all of that one case. The header is patched after every byte is out, the boundary block is written by both cursors, and both are simply revisits - a revisit is what fetching handles. osFileFetch is the service that allows it, and is the read side of the write. A run of bytes in one segment costs nothing extra; a switch between segments costs two block operations, and a source file has a few dozen switches and several thousand bytes. Two bugs, both a pointer meaning two things: putAt took the cursor to advance in DP2 and then wanted DP2 for the window's address. A call puts DP2 back the way it was AT THE CALL, so the step at the end moved whatever the last call had left there - the window walked off across memory while the cursor stood still. It goes in memory now, like the block did in S1, and for the same reason. The size the file is created at could not be right. How many vectors are actually installed is not known until the second pass has resolved their handlers, and by then the file must already exist to be written into - so Keys, which brings one vector, came out four bytes short. Teaching the first pass to count them meant teaching it about devices, and about a Boot line in a loadable program not being installed at all, which is two ways to disagree with the second pass about what a file contains. So osFileDone is told the size instead. A writer asks for as much as the file could possibly come to - the whole of it plus four bytes for every vector DECLARED, which no file can exceed - and says what it really came to at the end. The blocks it did not use go back to the free count. Asking for too much costs a moment; asking for too little writes off the end of a file. That is a better service for it, not a workaround. A writer that cannot know its size until the last byte is the ordinary case, and it is exactly the case this whole rung exists for. 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
9f7dffdeca
commit
fb7b224bbb
@@ -987,9 +987,36 @@ handleFileWrite:
|
||||
STA.2
|
||||
RETI
|
||||
|
||||
; Nothing to be told. The old file goes and the temporary takes its name, which is the only
|
||||
; step that can lose anything and the last one.
|
||||
; DP1 is where the block goes, and A and B together say which one, the same way writing is
|
||||
; told. Reads back a block of the file being written.
|
||||
handleFileFetch:
|
||||
SETD.2 SbfsIndex
|
||||
STA.2
|
||||
INCD.2
|
||||
STB.2
|
||||
CALL sbfsStreamFetch
|
||||
MVQA
|
||||
MVSD.2
|
||||
DPUP.2 0d02
|
||||
STA.2
|
||||
RETI
|
||||
|
||||
; DP3 is how many whole blocks it came to and A is what is left over, told the same way
|
||||
; osFileStart is told. It need not be what was asked for: a writer that cannot know its
|
||||
; size until the last byte asks for enough at the start, where running out costs nothing,
|
||||
; and says the truth here. The blocks it did not use go back.
|
||||
;
|
||||
; This is the only step that can lose anything, and the last one.
|
||||
handleFileDone:
|
||||
SETD.2 SbfsFileTail
|
||||
STA.2
|
||||
PSHD.3
|
||||
POPB
|
||||
POPA
|
||||
SETD.2 SbfsFileBlocks
|
||||
STA.2
|
||||
INCD.2
|
||||
STB.2
|
||||
CALL fileForget
|
||||
CALL sbfsStreamDone
|
||||
MVQA
|
||||
@@ -3744,6 +3771,7 @@ CommandLine:
|
||||
osFileStart handleFileStart
|
||||
osFileWrite handleFileWrite
|
||||
osFileDone handleFileDone
|
||||
osFileFetch handleFileFetch
|
||||
osPrintNumber handlePrintNumber
|
||||
osBreak handleBreak
|
||||
Device 0x20 diskDone
|
||||
|
||||
@@ -2420,11 +2420,60 @@ sbfsStreamWrite:
|
||||
CALL sbfsWriteOne
|
||||
RET
|
||||
|
||||
; DP1 is where the block should go and SbfsIndex says which one. The other direction of
|
||||
; sbfsStreamWrite, and the thing that lets a writer keep only ONE block of a file in hand:
|
||||
; anything writing two parts of a file at once has to be able to put a block down, go
|
||||
; somewhere else, and pick it up again where it left off.
|
||||
sbfsStreamFetch:
|
||||
SETD.0 SbfsStreamOpen
|
||||
LDA.0
|
||||
BRA sbfsStreamNo
|
||||
|
||||
SETD.0 SbfsStreamTo
|
||||
STD.1.0
|
||||
|
||||
SETD.0 SbfsFileStart
|
||||
SETD.2 SbfsStreamAt
|
||||
CALL sbfsSetWord
|
||||
SETD.0 SbfsFileBlocks
|
||||
SETD.2 SbfsStreamBlocks
|
||||
CALL sbfsSetWord
|
||||
SETD.0 SbfsStreamTail
|
||||
LDA.0
|
||||
SETD.2 SbfsFileTail
|
||||
STA.2
|
||||
|
||||
SETD.0 SbfsStreamTo
|
||||
LDD.1.0
|
||||
CALL sbfsReadOne
|
||||
RET
|
||||
|
||||
; SbfsFileBlocks and SbfsFileTail say how big it turned out to be, which need not be how
|
||||
; big it was started at.
|
||||
;
|
||||
; A WRITER MAY ASK FOR MORE ROOM THAN IT ENDS UP USING, and that is not laziness on its
|
||||
; part. Some sizes are not knowable until the last byte is out: the assembler cannot say
|
||||
; how many vectors a program installs until it has resolved them, and by then the file it
|
||||
; is writing into must already exist. So the room is taken generously at the start, where
|
||||
; running out costs nothing, and the size is told the truth here.
|
||||
;
|
||||
; The blocks that were asked for and not used go back to the free count. It is a note
|
||||
; rather than the authority - the directory is - but a note worth keeping right, and two
|
||||
; disks holding the same files must hold the same bytes.
|
||||
sbfsStreamDone:
|
||||
SETD.0 SbfsStreamOpen
|
||||
LDA.0
|
||||
BRA sbfsStreamNo
|
||||
|
||||
; What it really came to, put aside before anything walks the disk.
|
||||
SETD.0 SbfsStreamNewBlocks
|
||||
SETD.2 SbfsFileBlocks
|
||||
CALL sbfsSetWord
|
||||
SETD.0 SbfsFileTail
|
||||
LDA.0
|
||||
SETD.1 SbfsStreamNewTail
|
||||
STA.1
|
||||
|
||||
; Now, and not before, the old one goes. It may not be there at all, which is what
|
||||
; writing something for the first time looks like from here.
|
||||
CALL sbfsStreamWhere
|
||||
@@ -2433,15 +2482,67 @@ sbfsStreamDone:
|
||||
CALL sbfsWipeFound
|
||||
sbfsStreamNoOld:
|
||||
|
||||
; And the temporary takes its name, which is the whole of what committing is.
|
||||
; And the temporary takes its name and its true size, which together are the whole of
|
||||
; what committing is.
|
||||
CALL sbfsStreamTemp
|
||||
CALL sbfsScanFor
|
||||
BNQ sbfsStreamNo
|
||||
|
||||
; How much room it was given, before the entry that says so is changed.
|
||||
CALL sbfsFileExtent
|
||||
SETD.0 SbfsStreamSpare
|
||||
SETD.2 SbfsWantBlocks
|
||||
CALL sbfsSetWord
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d06
|
||||
SETD.0 SbfsStreamLeaf
|
||||
CALL sbfsCopyName
|
||||
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d03
|
||||
SETD.0 SbfsStreamNewBlocks
|
||||
CALL sbfsCopyWord
|
||||
PSHD.3
|
||||
POPD.1
|
||||
DPUP.1 0d05
|
||||
SETD.0 SbfsStreamNewTail
|
||||
LDA.0
|
||||
STA.1
|
||||
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
BNQ sbfsStreamNo
|
||||
|
||||
; And the difference goes back, which is what it was given less what it kept.
|
||||
SETD.0 SbfsFileBlocks
|
||||
SETD.2 SbfsStreamNewBlocks
|
||||
CALL sbfsSetWord
|
||||
SETD.0 SbfsStreamNewTail
|
||||
LDA.0
|
||||
SETD.1 SbfsFileTail
|
||||
STA.1
|
||||
CALL sbfsFileExtent
|
||||
SETD.0 SbfsStreamSpare
|
||||
SETD.2 SbfsWantBlocks
|
||||
CALL sbfsSubWord
|
||||
|
||||
RSTA
|
||||
SETD.0 SbfsBlock
|
||||
STA.0
|
||||
INCD.0
|
||||
STA.0
|
||||
CALL sbfsReadBlock
|
||||
BNQ sbfsStreamNo
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferOut
|
||||
SETD.0 SbfsBuffer
|
||||
DPUP.0 0d12
|
||||
SETD.2 SbfsStreamSpare
|
||||
CALL sbfsAddWord
|
||||
SETD.1 SbfsBuffer
|
||||
CALL sbfsBufferIn
|
||||
CALL sbfsWriteBlock
|
||||
@@ -2771,6 +2872,14 @@ SbfsStreamPath:
|
||||
0x00 0x00
|
||||
SbfsStreamFrom:
|
||||
0x00 0x00
|
||||
SbfsStreamTo:
|
||||
0x00 0x00
|
||||
SbfsStreamNewBlocks:
|
||||
0x00 0x00
|
||||
SbfsStreamNewTail:
|
||||
0x00
|
||||
SbfsStreamSpare:
|
||||
0x00 0x00
|
||||
SbfsStreamAt:
|
||||
0x00 0x00
|
||||
SbfsStreamParent:
|
||||
|
||||
@@ -102,7 +102,13 @@
|
||||
; registers and cannot write more than 65,535.
|
||||
osFileStart 0d29 ; DP0 names it, DP3 is whole blocks, A is bytes in the tail.
|
||||
osFileWrite 0d30 ; DP1 is the block, A and B together are which one, from zero.
|
||||
osFileDone 0d31 ; No arguments. The temporary takes the name.
|
||||
osFileDone 0d31 ; DP3 is whole blocks and A the tail: how big it turned out to be.
|
||||
osFileFetch 0d32 ; DP1 is where it goes, A and B are which block. Reads one back.
|
||||
|
||||
; osFileFetch is what lets a program keep only ONE block of a file in hand while writing
|
||||
; it. Anything producing two parts of a file at once - an assembler, whose source says
|
||||
; #Program and #Data in whatever order it likes - has to be able to put a block down, go
|
||||
; and write somewhere else, and pick it up again where it left off.
|
||||
; Q is zero if it read, DP3 is how many of its bytes are the file's:
|
||||
; a whole 0d256 except in a last block that is short. That count is
|
||||
; why DP3 answers and not a register - 0d256 does not fit in a byte,
|
||||
|
||||
Reference in New Issue
Block a user