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
245 lines
3.9 KiB
NASM
245 lines
3.9 KiB
NASM
; Writes a file one block at a time, without ever holding the whole of it.
|
|
;
|
|
; This is the write side of what Stream demonstrates for reading: a file bigger than the
|
|
; memory building it. It writes as many blocks as it is asked for, each one filled with a
|
|
; pattern that says which block it is, so that what comes off the disk afterwards can be
|
|
; checked against what should have gone on rather than merely being the right length.
|
|
;
|
|
; The argument is how many blocks, in decimal. The last one is deliberately a part block,
|
|
; because a tail is the case every off-by-one in a filesystem hides in.
|
|
;
|
|
; Written by Anachronaut
|
|
|
|
#Include services.asm
|
|
#Program
|
|
#Base 0x4000
|
|
|
|
start:
|
|
SETD.0 Argument
|
|
INIB 0d8
|
|
SWI osArgument
|
|
SETD.0 Argument
|
|
LDA.0
|
|
BRA useDefault
|
|
CALL readCount
|
|
BRI counted
|
|
useDefault:
|
|
INIA 0d4
|
|
SETD.0 Blocks
|
|
STA.0
|
|
counted:
|
|
|
|
; Nothing may be zero blocks: the tail below would then be the whole file.
|
|
SETD.0 Blocks
|
|
LDA.0
|
|
BNA haveCount
|
|
INIA 0d1
|
|
SETD.0 Blocks
|
|
STA.0
|
|
haveCount:
|
|
|
|
; Whole blocks, and a tail of forty bytes on the end of them.
|
|
SETD.0 Name
|
|
SETD.3 0x00 0x00
|
|
SETD.0 Blocks
|
|
LDA.0
|
|
PSHA
|
|
POPB
|
|
RSTA
|
|
PSHA
|
|
PSHB
|
|
POPD.3
|
|
SETD.0 Name
|
|
INIA 0d40
|
|
SWI osFileStart
|
|
BNQ startFailed
|
|
|
|
; Each whole block, filled with its own number.
|
|
RSTA
|
|
SETD.0 Which
|
|
STA.0
|
|
nextBlock:
|
|
SETD.0 Which
|
|
LDA.0
|
|
SETD.2 Blocks
|
|
LDB.2
|
|
CCF
|
|
SUB
|
|
BRQ theTail
|
|
|
|
CALL fillBlock
|
|
SETD.1 Block
|
|
SETD.0 Which
|
|
LDA.0
|
|
RSTB
|
|
PSHA
|
|
POPB
|
|
RSTA
|
|
SWI osFileWrite
|
|
BNQ writeFailed
|
|
|
|
SETD.0 Which
|
|
LDA.0
|
|
INCA
|
|
STA.0
|
|
BRI nextBlock
|
|
|
|
theTail:
|
|
; And the part block at the end, which is the same fill cut short by the size given at
|
|
; the start. Only the first forty bytes of it will belong to the file.
|
|
CALL fillBlock
|
|
SETD.1 Block
|
|
SETD.0 Which
|
|
LDA.0
|
|
RSTB
|
|
PSHA
|
|
POPB
|
|
RSTA
|
|
SWI osFileWrite
|
|
BNQ writeFailed
|
|
|
|
; And how big it turned out to be, which here is what was asked for: this one knows its
|
|
; size from the start. Something that did not - an assembler, say - would ask for more
|
|
; than it needed and say the truth here.
|
|
RSTA
|
|
PSHA
|
|
SETD.0 Blocks
|
|
LDA.0
|
|
PSHA
|
|
POPD.3
|
|
INIA 0d40
|
|
SWI osFileDone
|
|
BNQ doneFailed
|
|
|
|
SETD.0 Wrote
|
|
SWI osPrintString
|
|
SETD.0 Blocks
|
|
LDB.0
|
|
RSTA ; A and B together are the number, so the count is the low half.
|
|
SWI osPrintNumber
|
|
SETD.0 AndTail
|
|
SWI osPrintString
|
|
SWI osExit
|
|
|
|
; The block becomes 256 copies of the block number plus a fixed byte, so that a block
|
|
; written into the wrong place is visible rather than merely being bytes.
|
|
fillBlock:
|
|
SETD.0 Block
|
|
SETD.1 Which
|
|
LDA.1
|
|
INIB 0x41
|
|
CCF
|
|
ADD
|
|
MVQA
|
|
RSTB
|
|
fillLoop:
|
|
STA.0
|
|
INCD.0
|
|
DECB
|
|
BNB fillLoop
|
|
RET
|
|
|
|
; The argument, in decimal, into Blocks. Anything that is not a digit ends it.
|
|
readCount:
|
|
RSTA
|
|
SETD.1 Blocks
|
|
STA.1
|
|
SETD.0 Argument
|
|
countLoop:
|
|
LDA.0
|
|
BRA countDone
|
|
INIB 0x30
|
|
CCF
|
|
SUB
|
|
MVQA
|
|
INIB 0d10
|
|
CCF
|
|
SUB
|
|
BNC countDone ; Not a digit, so the number ended.
|
|
SETD.1 Blocks
|
|
LDB.1
|
|
PSHA
|
|
RSTA
|
|
INIA 0d10
|
|
CALL timesTen
|
|
POPA
|
|
SETD.1 Scratch
|
|
LDB.1
|
|
CCF
|
|
ADD
|
|
MVQA
|
|
SETD.1 Blocks
|
|
STA.1
|
|
INCD.0
|
|
BRI countLoop
|
|
countDone:
|
|
RET
|
|
|
|
; B times ten into Scratch, by adding it up. Nothing here is bigger than a byte.
|
|
timesTen:
|
|
RSTA
|
|
SETD.0 Scratch
|
|
STA.0
|
|
INIA 0d10
|
|
tenLoop:
|
|
PSHA
|
|
SETD.0 Scratch
|
|
LDA.0
|
|
SETD.2 TenHold
|
|
STB.2
|
|
LDB.2
|
|
CCF
|
|
ADD
|
|
MVQA
|
|
SETD.0 Scratch
|
|
STA.0
|
|
POPA
|
|
DECA
|
|
BNA tenLoop
|
|
RET
|
|
|
|
startFailed:
|
|
SETD.0 NoStart
|
|
SWI osPrintString
|
|
SWI osExit
|
|
writeFailed:
|
|
SETD.0 NoWrite
|
|
SWI osPrintString
|
|
SWI osExit
|
|
doneFailed:
|
|
SETD.0 NoDone
|
|
SWI osPrintString
|
|
SWI osExit
|
|
|
|
#Data
|
|
#Base 0x2000
|
|
|
|
Name:
|
|
"poured.dat"
|
|
Argument:
|
|
#Reserve 0d9
|
|
Blocks:
|
|
0x00
|
|
Which:
|
|
0x00
|
|
Scratch:
|
|
0x00
|
|
TenHold:
|
|
0x00
|
|
Block:
|
|
#Reserve 0d256
|
|
Wrote:
|
|
"poured "
|
|
AndTail:
|
|
" blocks and a tail of 40
|
|
"
|
|
NoStart:
|
|
"could not start it
|
|
"
|
|
NoWrite:
|
|
"could not write a block
|
|
"
|
|
NoDone:
|
|
"could not finish it
|
|
"
|