; Reading shell lines out of a file. ; ; DP0 = the file's name ; 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 ; DP0 = a buffer, B = how much room ; CALL scriptLine Q = 0 and there is a line in the buffer, or nonzero at the end ; ; Written by Anachronaut ; ; ---- Why the shell reads files and not the other way round ---- ; ; fileStream.asm does almost this and is deliberately not used. It is application machinery: ; More, Type and Wander each include it and each get their own copy in their own Data ; Segment. The shell reading a script through it would be a fourth copy, and the shell is the ; one place where that copy has to survive running a program - which is exactly the thing ; those programs are. Here the state belongs to the system and nothing a program does can ; reach it. ; ; ---- A block, and a nought on the end of it ---- ; ; The reader walks a Data Pointer along the block and stops at a nought. A block is 256 bytes ; and a count of them does not fit in a byte, so every other way of knowing where the block ; ends costs sixteen bit arithmetic on every character. Writing a nought after the last byte ; costs one store per block and turns the whole question into "is this byte zero". ; ; The buffer is 257 bytes for that reason: a full block leaves the nought at the end of it. ; A script cannot contain a nought, which is not a restriction anybody will notice - it is ; text, and the #! on the front is what stops a program being read as one. #Program ; ---- Opening ---- ; ; The name is COPIED rather than remembered by address. osFileBlock is given the name again ; for every block, and the caller's copy is CommandLine, which the next line typed will ; overwrite. fileStream remembers an address and says in its own comment that the address ; must stay valid; here it cannot, because the thing that reads the next line is the reason ; the name is needed. scriptOpen: SETD.1 ScriptName INIB 0d63 CALL copyText SETD.0 ScriptName SWI osFileInfo BRQ scriptOpenThere INIA 0x01 BRI scriptOpenFailed scriptOpenThere: ; DP3 is how many blocks. A file of none is not a script. PSHD.3 POPB POPA SETD.1 ScriptBlocks STA.1 INCD.1 STB.1 OR BRQ scriptOpenNotOne SETD.1 ScriptIndex RSTA STA.1 INCD.1 STA.1 ; The first block, so that the #! can be looked at before anything is promised. CALL scriptFill BNQ scriptOpenNotOne ; ---- What makes a file a script ---- ; ; Two bytes, and the rest of the line ignored. A directive rather than a comment, the way ; #Program is in assembly: the shell reads it and refuses the file without it, so calling ; it a comment would be a lie about what it does. What follows the #! is where the name of ; an interpreter goes when there is a second one; today there is one and it is this shell. SETD.1 ScriptBlock LDA.1 INIB 0d35 ; '#' CCF SUB BNQ scriptOpenNotOne INCD.1 LDA.1 INIB 0d33 ; '!' CCF SUB BNQ scriptOpenNotOne ; Past the shebang line, wherever it ends. CALL scriptSkipLine INIA 0x01 SETD.1 ScriptRunning STA.1 RSTA BRI scriptOpenFailed ; A is nought, which is the answer for "it opened". scriptOpenNotOne: INIA 0x02 scriptOpenFailed: ; Q is the answer, and A holds it. Adding nought is how a register becomes Q. RSTB CCF ADD RET ; ---- One line ---- ; ; Comments and blank lines never reach the shell. The reader drops them, so the echo does not ; print them and the dispatch never sees a line it would have to know to ignore. scriptLine: SETD.1 ScriptRoom STB.1 SETD.1 ScriptInto STD.0.1 scriptLineAgain: SETD.1 ScriptLength RSTA STA.1 SETD.0 ScriptInto LDD.0.0 scriptLineNext: CALL scriptByte BNQ scriptLineEnded SETD.1 ScriptChar LDA.1 INIB 0d10 CCF SUB BRQ scriptLineDone ; A still holds the character: SUB writes Q and leaves it alone. INIB 0d13 ; Carriage return, from a file written on a host that uses them. CCF SUB BRQ scriptLineNext ; Room? SETD.1 ScriptLength LDA.1 SETD.1 ScriptRoom LDB.1 CCF SUB BRQ scriptLineNext ; Full. Read on and drop what comes, the way readLine does. SETD.1 ScriptChar LDA.1 STA.0 INCD.0 SETD.1 ScriptLength LDA.1 INCA STA.1 BRI scriptLineNext scriptLineDone: ; Terminate it, then decide whether the shell wants to see it. RSTA STA.0 SETD.0 ScriptInto LDD.0.0 LDA.0 BRA scriptLineAgain ; Empty. INIB 0d59 ; ';' - a comment, the same as everywhere else on this machine. CCF SUB BRQ scriptLineAgain RSTA ; Q = 0: there is a line. RSTB CCF ADD RET scriptLineEnded: ; ---- A last line with no newline on it is still a line ---- ; ; Text files do not reliably end with one, and a script whose final command silently did ; not run because somebody's editor left the newline off is a bad way to find that out. ; If anything has been gathered, finish it the ordinary way; the next call comes back here ; with nothing gathered and ends for real. SETD.1 ScriptLength LDA.1 BRA scriptLineNoMore BRI scriptLineDone scriptLineNoMore: CALL scriptClose INIA 0x01 RSTB CCF ADD RET ; ---- One character, or the end ---- ; ; Q = 0 and the character is in ScriptChar, or Q is one and there are no more. ; ; IN MEMORY RATHER THAN IN A REGISTER, because RET puts A and B back the way the caller had ; them - only Q and Data Pointer 3 survive a CALL. Handing the character back in A looked ; right, assembled, and returned the caller's own A every time. scriptByte: SETD.1 ScriptAt LDD.1.1 LDA.1 BRA scriptByteRefill ; The nought at the end of the block. ; Step the saved pointer past it. SETD.1 ScriptChar STA.1 SETD.1 ScriptAt LDD.0.1 INCD.0 STD.0.1 RSTA RSTB CCF ADD ; Q = 0. RET scriptByteRefill: CALL scriptFill BNQ scriptByteNoMore BRI scriptByte scriptByteNoMore: INIA 0x01 RSTB CCF ADD RET ; ---- The next block, with a nought written after it ---- ; ; Q = 0 if there is one. scriptFill: SETD.1 ScriptBlocks LDA.1 INCD.1 LDB.1 OR BRQ scriptFillNoMore SETD.0 ScriptName SETD.1 ScriptBlock SETD.2 ScriptIndex LDA.2 INCD.2 LDB.2 SWI osFileBlock BNQ scriptFillNoMore ; DP3 is how many bytes came back. The nought goes after them. SETD.1 ScriptBlock PSHD.3 POPB POPA DPUW.1 RSTA STA.1 SETD.1 ScriptAt SETD.0 ScriptBlock STD.0.1 ; Index++, blocks--. SETD.1 ScriptIndex INCD.1 LDA.1 INCA STA.1 BNC scriptFillCount DECD.1 LDA.1 INCA STA.1 scriptFillCount: SETD.1 ScriptBlocks INCD.1 LDA.1 BRA scriptFillBorrow DECA STA.1 BRI scriptFillGot scriptFillBorrow: INIA 0xFF STA.1 DECD.1 LDA.1 DECA STA.1 scriptFillGot: RSTA RSTB CCF ADD RET scriptFillNoMore: INIA 0x01 RSTB CCF ADD RET ; Everything up to and including the next line feed, thrown away. Used for the shebang. scriptSkipLine: CALL scriptByte BNQ scriptSkipDone SETD.1 ScriptChar LDA.1 INIB 0d10 CCF SUB BNQ scriptSkipLine scriptSkipDone: RET scriptClose: RSTA SETD.1 ScriptRunning STA.1 RET #Data ScriptRunning: 0x00 ScriptName: #Reserve 0d64 ScriptBlocks: 0x00 0x00 ScriptIndex: 0x00 0x00 ScriptAt: 0x00 0x00 ScriptInto: 0x00 0x00 ScriptRoom: 0x00 ScriptLength: 0x00 ScriptChar: 0x00 ScriptBlock: #Reserve 0d257