; I/Os in zasm ; ; As a general rule, I/O in zasm is pretty straightforward. We receive, as a ; parameter, two blockdevs: One that we can read and seek and one that we can ; write to (we never seek into it). ; ; This unit also has the responsibility of counting the number of written bytes, ; maintaining IO_PC and of properly disabling output on first pass. ; ; On top of that, this unit has the responsibility of keeping track of the ; current lineno. Whenever GetB is called, we check if the fetched byte is a ; newline. If it is, we increase our lineno. This unit is the best place to ; keep track of this because we have to handle ioRecallPos. ; ; zasm doesn't buffers its reads during tokenization, which simplifies its ; process. However, it also means that it needs, in certain cases, a "putback" ; mechanism, that is, a way to say "you see that character I've just read? that ; was out of my bounds. Could you make it as if I had never read it?". That ; buffer is one character big and is made with the expectation that ioPutBack ; is always called right after a ioGetB (when it's called). ; ; ioPutBack will mess up seek and tell offsets, so thath "put back" should be ; consumed before having to seek and tell. ; ; That's for the general rules. ; ; Now, let's enter includes. To simplify processing, we make include mostly ; transparent to all other units. They always read from ioGetB and a include ; directive should have the exact same effect as copy/pasting the contents of ; the included file in the caller. ; ; By the way: we don't support multiple level of inclusion. Only top level files ; can include. ; ; When we include, all we do here is open the file with fsOpen and set a flag ; indicating that we're inside an include. When that flag is on, GetB, Seek and ; Tell are transparently redirected to their fs* counterpart. ; ; When we reach EOF in an included file, we transparently unset the "in include" ; flag and continue on the general IN stream. ; *** Variables *** .equ IO_IN_BLK IO_RAMSTART .equ IO_OUT_BLK IO_IN_BLK+BLOCKDEV_SIZE ; Save pos for ioSavePos and ioRecallPos .equ IO_SAVED_POS IO_OUT_BLK+BLOCKDEV_SIZE ; File handle for included source .equ IO_INCLUDE_HDL IO_SAVED_POS+2 ; blkdev for include file .equ IO_INCLUDE_BLK IO_INCLUDE_HDL+FS_HANDLE_SIZE ; see ioPutBack below .equ IO_PUTBACK_BUF IO_INCLUDE_BLK+BLOCKDEV_SIZE .equ IO_IN_INCLUDE IO_PUTBACK_BUF+1 .equ IO_PC IO_IN_INCLUDE+1 ; Current lineno in top-level file .equ IO_LINENO IO_PC+2 ; Current lineno in include file .equ IO_INC_LINENO IO_LINENO+2 ; Line number (can be top-level or include) when ioSavePos was last called. .equ IO_SAVED_LINENO IO_INC_LINENO+2 ; Handle for the ioSpitBin .equ IO_BIN_HDL IO_SAVED_LINENO+2 .equ IO_RAMEND IO_BIN_HDL+FS_HANDLE_SIZE ; *** Code *** ioInit: xor a ld (IO_PUTBACK_BUF), a ld (IO_IN_INCLUDE), a ld de, IO_INCLUDE_BLK ld hl, _ioIncBlk call blkSet jp ioResetCounters ioGetB: ld a, (IO_PUTBACK_BUF) or a ; cp 0 jr nz, .getback call ioInInclude jr z, .normalmode ; We're in "include mode", read from FS push ix ; --> lvl 1 ld ix, IO_INCLUDE_BLK call _blkGetB pop ix ; <-- lvl 1 jr nz, .includeEOF cp 0x0a ; newline ret nz ; not newline? nothing to do ; We have newline. Increase lineno and return (the rest of the ; processing below isn't needed. push hl ld hl, (IO_INC_LINENO) inc hl ld (IO_INC_LINENO), hl pop hl ret .includeEOF: ; We reached EOF. What we do depends on whether we're in Local Pass ; mode. Yes, I know, a bit hackish. Normally, we *should* be ; transparently getting of include mode and avoid meddling with global ; states, but here, we need to tell main.asm that the local scope if ; over *before* we get off include mode, otherwise, our IO_SAVED_POS ; will be wrong (an include IO_SAVED_POS used in global IN stream). call zasmIsLocalPass ld a, 0 ; doesn't affect Z flag ret z ; local pass? return EOF ; regular pass (first or second)? transparently get off include mode. ld (IO_IN_INCLUDE), a ; A already 0 ld (IO_INC_LINENO), a ld (IO_INC_LINENO+1), a ; continue on to "normal" reading. We don't want to return our zero .normalmode: ; normal mode, read from IN stream push ix ; --> lvl 1 ld ix, IO_IN_BLK call _blkGetB pop ix ; <-- lvl 1 cp LF ; newline ret nz ; not newline? return ; inc current lineno push hl ld hl, IO_LINENO inc (hl) pop hl cp a ; ensure Z ret .getback: push af xor a ld (IO_PUTBACK_BUF), a pop af ret ; Put back non-zero character A into the "ioGetB stack". The next ioGetB call, ; instead of reading from IO_IN_BLK, will return that character. That's the ; easiest way I found to handle the readWord/gotoNextLine problem. ioPutBack: ld (IO_PUTBACK_BUF), a ret ioPutB: push hl ; --> lvl 1 ld hl, (IO_PC) inc hl ld (IO_PC), hl pop hl ; <-- lvl 1 push af ; --> lvl 1 call zasmIsFirstPass jr z, .skip pop af ; <-- lvl 1 push ix ; --> lvl 1 ld ix, IO_OUT_BLK call _blkPutB pop ix ; <-- lvl 1 ret .skip: pop af ; <-- lvl 1 cp a ; ensure Z ret ioSavePos: ld hl, (IO_LINENO) call ioInInclude jr z, .skip ld hl, (IO_INC_LINENO) .skip: ld (IO_SAVED_LINENO), hl call _ioTell ld (IO_SAVED_POS), hl ret ioRecallPos: ld hl, (IO_SAVED_LINENO) call ioInInclude jr nz, .include ld (IO_LINENO), hl jr .recallpos .include: ld (IO_INC_LINENO), hl .recallpos: ld hl, (IO_SAVED_POS) jr _ioSeek ioRewind: call ioResetCounters ; sets HL to 0 jr _ioSeek ioResetCounters: ld hl, 0 ld (IO_PC), hl ld (IO_LINENO), hl ld (IO_SAVED_LINENO), hl ret ; always in absolute mode (A = 0) _ioSeek: call ioInInclude ld a, 0 ; don't alter flags jr nz, .include ; normal mode, seek in IN stream ld ix, IO_IN_BLK jp _blkSeek .include: ; We're in "include mode", seek in FS ld ix, IO_INCLUDE_BLK jp _blkSeek ; returns _ioTell: call ioInInclude jp nz, .include ; normal mode, seek in IN stream ld ix, IO_IN_BLK jp _blkTell .include: ; We're in "include mode", tell from FS ld ix, IO_INCLUDE_BLK jp _blkTell ; returns ; Sets Z according to whether we're inside an include ; Z is set when we're *not* in includes. A bit weird, I know... ioInInclude: ld a, (IO_IN_INCLUDE) or a ; cp 0 ret ; Open include file name specified in (HL). ; Sets Z on success, unset on error. ioOpenInclude: call ioPrintLN call fsFindFN ret nz ld ix, IO_INCLUDE_HDL call fsOpen ld a, 1 ld (IO_IN_INCLUDE), a ld hl, 0 ld (IO_INC_LINENO), hl xor a ld ix, IO_INCLUDE_BLK call _blkSeek cp a ; ensure Z ret ; Open file specified in (HL) and spit its contents through ioPutB ; Sets Z on success. ioSpitBin: call fsFindFN ret nz push hl ; --> lvl 1 ld ix, IO_BIN_HDL call fsOpen ld hl, 0 .loop: ld ix, IO_BIN_HDL call fsGetB jr nz, .loopend call ioPutB inc hl jr .loop .loopend: pop hl ; <-- lvl 1 cp a ; ensure Z ret ; Return current lineno in HL and, if in an include, its lineno in DE. ; If not in an include, DE is set to 0 ioLineNo: push af ld hl, (IO_LINENO) ld de, 0 call ioInInclude jr z, .end ld de, (IO_INC_LINENO) .end: pop af ret _ioIncGetB: ld ix, IO_INCLUDE_HDL jp fsGetB _ioIncBlk: .dw _ioIncGetB, unsetZ ; call printstr followed by newline ioPrintLN: push hl call printstr ld hl, .sCRLF call printstr pop hl ret .sCRLF: .db 0x0a, 0x0d, 0