diff --git a/Programs/CosmOS/Apps/More.asm b/Programs/CosmOS/Apps/More.asm new file mode 100644 index 0000000..0e8a1ab --- /dev/null +++ b/Programs/CosmOS/Apps/More.asm @@ -0,0 +1,151 @@ +; Read a text file one screen at a time. +; +; Twenty two lines are shown before a prompt. Space advances another screen, Return one +; line, and q gives the machine back to CosmOS. This is forward-only on purpose: the file +; stream holds one block and never asks the whole document to fit in memory. +; +; Written by ChatGPT for Anachronaut's SplitBit + +#Include services.asm +#Program + #Base 0x2000 + +start: + SETD.0 Name + INIB 0d29 + SWI osArgument + SETD.0 Name + LDA.0 + BRA noName + CALL fileStreamOpen + BNQ openFailed + CALL fullPage + +nextBlock: + CALL fileStreamNext + BNQ readFailed + PSHD.3 + POPB + POPA + SETD.2 Remaining + STA.2 + INCD.2 + STB.2 + OR + BRQ finished + SETD.1 FileStreamBlock + SETD.2 Remaining + +printLoop: + LDA.1 + OUTA 0x00 + INIB 0x0A + XOR + BNQ bytePrinted + SETD.3 LinesLeft + LDA.3 + DECA + STA.3 + BNA bytePrinted + CALL pause + BNQ finished +bytePrinted: + INCD.1 + CALL fileStreamTakeRemaining + BRQ nextBlock + BRI printLoop + +; Ask for one key without asking for Return. The console is put back immediately, +; including on q and end of redirected input. +pause: + SETD.0 MorePrompt + SWI osPrintString + INIA 0x01 + OUTA 0x02 + INA 0x00 + PSHA ; Keep the key while A is used to restore line mode. + RSTA + OUTA 0x02 + POPA + SETD.0 ClearPrompt + SWI osPrintString + + INIB 0x71 ; q + XOR + BRQ pauseQuit + INIB 0xFF ; end of redirected input + XOR + BRQ pauseQuit + INIB 0x0A ; Return: one more line. + XOR + BRQ oneLine + INIB 0x20 ; Space: one more screen. + XOR + BNQ pause ; Ignore every other key. + CALL fullPage + RSTA + RSTB + OR + RET +oneLine: + SETD.3 LinesLeft + INIA 0x01 + STA.3 + RSTA + RSTB + OR + RET +pauseQuit: + RSTA + INIB 0x01 + OR + RET +fullPage: + SETD.3 LinesLeft + INIA 0d22 + STA.3 + RET + +noName: + SETD.0 Usage + SWI osPrintString + SWI osExit +openFailed: + SETD.0 OpenError + SWI osPrintString + BRI printError +readFailed: + SETD.0 ReadError + SWI osPrintString +printError: + RSTA + MVQB + SWI osPrintNumber + SETD.0 NewLine + SWI osPrintString +finished: + SWI osExit + +#Data + #Base 0x1000 +Usage: +"more: give me a file name +" +OpenError: +"more: cannot find the file, error " +ReadError: +"more: cannot read the file, error " +NewLine: + 0x0A 0x00 +MorePrompt: +"-- more --" +ClearPrompt: + 0x0A 0x00 +Name: + #Reserve 0d29 +Remaining: + 0x00 0x00 +LinesLeft: + 0x00 + +#Include fileStream.asm diff --git a/Programs/CosmOS/Apps/Type.asm b/Programs/CosmOS/Apps/Type.asm new file mode 100644 index 0000000..d484416 --- /dev/null +++ b/Programs/CosmOS/Apps/Type.asm @@ -0,0 +1,80 @@ +; Print a text file without asking it to fit in Data Memory. +; fileStream.asm owns finding the file and walking its blocks. Type owns only what makes +; it Type: send each byte to the console until the stream says there are no more. +; +; Written by ChatGPT for Anachronaut's SplitBit + +#Include services.asm +#Program + #Base 0x2000 + +start: + SETD.0 Name + INIB 0d29 + SWI osArgument + SETD.0 Name + LDA.0 + BRA noName + CALL fileStreamOpen + BNQ openFailed + +nextBlock: + CALL fileStreamNext + BNQ readFailed + PSHD.3 + POPB + POPA + SETD.2 Remaining + STA.2 + INCD.2 + STB.2 + OR + BRQ finished + SETD.1 FileStreamBlock + SETD.2 Remaining + +printLoop: + LDA.1 + OUTA 0x00 + INCD.1 + CALL fileStreamTakeRemaining + BRQ nextBlock + BRI printLoop + +noName: + SETD.0 Usage + SWI osPrintString + SWI osExit +openFailed: + SETD.0 OpenError + SWI osPrintString + BRI printError +readFailed: + SETD.0 ReadError + SWI osPrintString +printError: + RSTA + MVQB + SWI osPrintNumber + SETD.0 NewLine + SWI osPrintString +finished: + SWI osExit + +#Data + #Base 0x1000 +Usage: +"type: give me a file name +" +OpenError: +"type: cannot find the file, error " +ReadError: +"type: cannot read the file, error " +NewLine: + 0x0A 0x00 +Name: + #Reserve 0d29 +Remaining: + 0x00 0x00 + +#Include fileStream.asm diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index f243349..413c323 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -138,6 +138,8 @@ from every assembly file in it. Several are old programs written for the bare ma | Break | Stops itself twice with SWI osBreak, so that the registers can be seen changing between one stop and the next. | | Edit | A line editor. | | Stream | Reads an 84,000 byte file through a buffer of 256, which is what says a file bigger than Data Memory can be read at all. | +| Type | Prints a named text file a block at a time, including one too large to fit in Data Memory. | +| More | A forward-only pager. Space advances a screen, Return one line, and q stops. | ### The Monitor: diff --git a/Programs/CosmOS/Source/fileStream.asm b/Programs/CosmOS/Source/fileStream.asm new file mode 100644 index 0000000..c3c3b41 --- /dev/null +++ b/Programs/CosmOS/Source/fileStream.asm @@ -0,0 +1,141 @@ +; Read a named file from beginning to end, one block at a time. +; +; DP0 = filename +; CALL fileStreamOpen Q = 0, or the osFileInfo error +; CALL fileStreamNext Q = 0, or the osFileBlock error +; DP3 = bytes in FileStreamBlock; zero means EOF +; +; This is application-side machinery built on CosmOS services, not a filesystem and not +; yet an OS stream. Open remembers the address of the caller's filename, which must remain +; valid until the one stream is finished. +; +; Written by ChatGPT for Anachronaut's SplitBit + +#Program +fileStreamOpen: + SETD.3 FileStreamName + STD.0.3 + SWI osFileInfo + BNQ fileStreamReturn + PSHD.3 + POPB + POPA + SETD.3 FileStreamBlocks + STA.3 + INCD.3 + STB.3 + SETD.3 FileStreamIndex + RSTA + STA.3 + INCD.3 + STA.3 +fileStreamReturn: + RET + +fileStreamNext: + SETD.3 FileStreamBlocks + LDA.3 + INCD.3 + LDB.3 + OR + BRQ fileStreamEnd + SETD.3 FileStreamName + LDD.0.3 + SETD.1 FileStreamBlock + SETD.3 FileStreamIndex + LDA.3 + INCD.3 + LDB.3 + SWI osFileBlock + BNQ fileStreamReturn + + ; DP3 is the service's return value and also the pointer this routine uses for all of + ; its bookkeeping. Keep the value before touching the pointer, then restore it at the + ; one successful return below. + PSHD.3 + POPB + POPA + SETD.3 FileStreamCount + STA.3 + INCD.3 + STB.3 + + ; Index++. + SETD.3 FileStreamIndex + INCD.3 + LDA.3 + INCA + STA.3 + BNC fileStreamTakeBlock + DECD.3 + LDA.3 + INCA + STA.3 +fileStreamTakeBlock: + ; Blocks--. + SETD.3 FileStreamBlocks + INCD.3 + LDA.3 + BRA fileStreamBlocksBorrow + DECA + STA.3 + BRI fileStreamReadReturn +fileStreamBlocksBorrow: + INIA 0xFF + STA.3 + DECD.3 + LDA.3 + DECA + STA.3 +fileStreamReadReturn: + SETD.3 FileStreamCount + LDD.3.3 + RET +fileStreamEnd: + ; There is no reset-pointer instruction. Two zero bytes through the Stack are the + ; literal construction of a zero Data Pointer. + RSTA + PSHA + PSHA + POPD.3 + RET + +; DP2 names a big-endian sixteen-bit byte count. Decrement it, returning Q = 0 when it +; reached zero and nonzero while bytes remain. This is shared because correctly counting +; a full 0x0100-byte block is the least interesting part of both applications to duplicate. +fileStreamTakeRemaining: + INCD.2 + LDA.2 + BRA fileStreamRemainingBorrow + DECA + STA.2 + DECD.2 + LDB.2 + INCD.2 + LDA.2 + OR + RET +fileStreamRemainingBorrow: + INIA 0xFF + STA.2 + DECD.2 + LDA.2 + DECA + STA.2 + LDB.2 + INCD.2 + LDA.2 + OR + RET + +#Data +FileStreamName: + 0x00 0x00 +FileStreamBlocks: + 0x00 0x00 +FileStreamIndex: + 0x00 0x00 +FileStreamCount: + 0x00 0x00 +FileStreamBlock: + #Reserve 0d256 diff --git a/Tests/expected/cosmosMore.out b/Tests/expected/cosmosMore.out new file mode 100644 index 0000000..8a16597 --- /dev/null +++ b/Tests/expected/cosmosMore.out @@ -0,0 +1,89 @@ +CosmOS +> loaded, starting at 2000 +> first line +second line +line 00: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 01: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 02: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 03: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 04: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 05: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 06: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 07: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 08: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 09: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 10: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 11: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 12: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 13: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 14: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 15: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 16: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 17: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 18: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 19: ABCDEFGHIJKLMNOPQRSTUVWXYZ +-- more -- +finished +> first line +second line +line 00: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 01: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 02: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 03: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 04: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 05: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 06: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 07: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 08: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 09: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 10: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 11: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 12: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 13: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 14: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 15: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 16: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 17: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 18: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 19: ABCDEFGHIJKLMNOPQRSTUVWXYZ +-- more -- +line 20: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 21: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 22: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 23: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 24: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 26: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 27: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 28: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 29: ABCDEFGHIJKLMNOPQRSTUVWXYZ +finished +> first line +second line +line 00: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 01: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 02: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 03: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 04: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 05: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 06: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 07: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 08: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 09: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 10: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 11: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 12: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 13: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 14: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 15: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 16: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 17: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 18: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 19: ABCDEFGHIJKLMNOPQRSTUVWXYZ +-- more -- +line 20: ABCDEFGHIJKLMNOPQRSTUVWXYZ +-- more -- +finished +> halted +Execution halted. +[exit 0] diff --git a/Tests/expected/cosmosType.out b/Tests/expected/cosmosType.out new file mode 100644 index 0000000..9bd2c70 --- /dev/null +++ b/Tests/expected/cosmosType.out @@ -0,0 +1,43 @@ +CosmOS +> loaded, starting at 2000 +> first line +second line +line 00: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 01: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 02: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 03: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 04: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 05: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 06: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 07: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 08: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 09: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 10: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 11: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 12: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 13: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 14: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 15: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 16: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 17: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 18: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 19: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 20: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 21: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 22: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 23: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 24: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 26: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 27: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 28: ABCDEFGHIJKLMNOPQRSTUVWXYZ +line 29: ABCDEFGHIJKLMNOPQRSTUVWXYZ +finished +> finished +> type: cannot find the file, error 2 +finished +> type: give me a file name +finished +> halted +Execution halted. +[exit 0] diff --git a/Tests/input/cosmosMore.in b/Tests/input/cosmosMore.in new file mode 100644 index 0000000..552f4b8 --- /dev/null +++ b/Tests/input/cosmosMore.in @@ -0,0 +1,6 @@ +load More.sbx +run readable.txt +qrun readable.txt + run readable.txt + +qexit diff --git a/Tests/input/cosmosType.in b/Tests/input/cosmosType.in new file mode 100644 index 0000000..cd48f81 --- /dev/null +++ b/Tests/input/cosmosType.in @@ -0,0 +1,6 @@ +load Type.sbx +run readable.txt +run empty.txt +run missing.txt +run +exit diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index 81b2f9b..0429c85 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -161,6 +161,22 @@ awk 'BEGIN { for (i = 0; i < 50; i++) printf "small %05d\n", i }' > small.txt "$ROOT/Programs/CosmOS/Apps/Stream.asm" -o "$WORK/Stream.sbx" >/dev/null "$TOOL" put "$DISKS/stream.img" "$WORK/Stream.sbx" >/dev/null +# A disk for Type. The first file crosses several blocks and deliberately has no zero byte +# for the application to mistake for an end marker. The second proves that zero blocks is +# a valid empty file rather than an error. +"$TOOL" format "$DISKS/type.img" 64 2 >/dev/null +printf 'first line\nsecond line\n' > readable.txt +awk 'BEGIN { for (i = 0; i < 30; i++) printf "line %02d: ABCDEFGHIJKLMNOPQRSTUVWXYZ\n", i }' >> readable.txt +: > empty.txt +"$TOOL" put "$DISKS/type.img" readable.txt >/dev/null +"$TOOL" put "$DISKS/type.img" empty.txt >/dev/null +"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ + "$ROOT/Programs/CosmOS/Apps/Type.asm" -o "$WORK/Type.sbx" >/dev/null +"$TOOL" put "$DISKS/type.img" "$WORK/Type.sbx" >/dev/null +"$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" \ + "$ROOT/Programs/CosmOS/Apps/More.asm" -o "$WORK/More.sbx" >/dev/null +"$TOOL" put "$DISKS/type.img" "$WORK/More.sbx" >/dev/null + # A disk for the assembler that runs on the machine. It holds a source file and the two # programs that check the front end - the reader on its own and the tokenizer on its own - # because a fault in either of those would otherwise turn up much later as a mysterious diff --git a/Tests/manifest b/Tests/manifest index 7e6ddeb..892c8bc 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -316,6 +316,14 @@ cosmosServices | CosmOS/Source/cosmos.asm | run | cosmosFil # notice the file moved - and that one would otherwise pass, since the blocks are still # there and still hold the same bytes. cosmosStream | CosmOS/Source/cosmos.asm | run | cosmosStream.in | - | disks/stream.img +# A useful application made out of the streaming primitive: print a named text file without +# requiring it to fit in Data Memory. The fixture crosses block boundaries, includes a full +# 256-byte block, and also checks empty, missing, and absent filename cases. +cosmosType | CosmOS/Source/cosmos.asm | run | cosmosType.in | - | disks/type.img +# The same stream with interaction layered on it. Three runs exercise q, a whole screen +# with Space, and one line with Return. The input is intentionally packed so that the one +# byte the pager consumes leaves the next shell command immediately behind it. +cosmosMore | CosmOS/Source/cosmos.asm | run | cosmosMore.in | - | disks/type.img # The assembler's front end, checked in two pieces before anything is built on it. # # cosmosSource reads a source file and prints it back. The file crosses a block boundary, @@ -340,6 +348,8 @@ app-Break | CosmOS/Apps/Break.asm | assemble | - app-Edit | CosmOS/Apps/Edit.asm | assemble | - | - app-Files | CosmOS/Apps/Files.asm | assemble | - | - app-Stream | CosmOS/Apps/Stream.asm | assemble | - | - +app-Type | CosmOS/Apps/Type.asm | assemble | - | - +app-More | CosmOS/Apps/More.asm | assemble | - | - # The assembler that runs on the machine, and its parts. Checked on their own so that a # failure reads as "it does not assemble" rather than as a broken disk image. asm-Asm | CosmOS/Assembler/Asm.asm | assemble | - | -