From 2f0dd5d668ea927d2a9c12ee6165b6726c6f7ec4 Mon Sep 17 00:00:00 2001
From: Virgil Dupras <hsoft@hardcoded.net>
Date: Thu, 16 May 2019 21:15:00 -0400
Subject: [PATCH] zasm: iiiiiiiincluuuuuuudes!!1!

---
 apps/zasm/directive.asm      |  31 ++++++++++-
 apps/zasm/io.asm             | 124 +++++++++++++++++++++++++++++++++++++++++--
 apps/zasm/main.asm           |  20 +++----
 parts/z80/fs.asm             |   7 ++-
 tools/emul/Makefile          |   9 ++--
 tools/emul/zasm.c            |   5 +-
 tools/emul/zasm_glue.asm     |  13 +++--
 tools/emul/zasm_user.asm     |   6 +++
 tools/tests/zasm/runtests.sh |   6 +--
 tools/tests/zasm/test6.asm   |   3 ++
 10 files changed, 192 insertions(+), 32 deletions(-)
 create mode 100644 tools/tests/zasm/test6.asm

diff --git a/apps/zasm/directive.asm b/apps/zasm/directive.asm
index 5082ed0..1fae84a 100644
--- a/apps/zasm/directive.asm
+++ b/apps/zasm/directive.asm
@@ -3,6 +3,7 @@
 .equ	D_DB	0x00
 .equ	D_DW	0x01
 .equ	D_EQU	0x02
+.equ	D_INC	0x03
 .equ	D_BAD	0xff
 
 ; *** Variables ***
@@ -15,12 +16,14 @@ directiveNames:
 	.db	".DB", 0
 	.db	".DW", 0
 	.db	".EQU"
+	.db	"#inc"
 
 ; This is a list of handlers corresponding to indexes in directiveNames
 directiveHandlers:
 	.dw	handleDB
 	.dw	handleDW
 	.dw	handleEQU
+	.dw	handleINC
 
 handleDB:
 	push	hl
@@ -84,12 +87,38 @@ handleEQU:
 	pop	hl
 	ret
 
+handleINC:
+	call	readWord
+	jr	nz, .end
+	; HL points to scratchpad
+	; First, let's verify that our string is enquoted
+	ld	a, (hl)
+	cp	'"'
+	jr	nz, .end
+	; We have opening quote
+	inc	hl
+	xor	a
+	call	JUMP_FINDCHAR	; go to end of string
+	dec	hl
+	ld	a, (hl)
+	cp	'"'
+	jr	nz, .end
+	; we have ending quote, let's replace with null char
+	xor	a
+	ld	(hl), a
+	; Good, let's go back
+	ld	hl, scratchpad+1	; +1 because of the opening quote
+	call	ioOpenInclude
+.end:
+	xor	a		; zero bytes written
+	ret
+
 ; Reads string in (HL) and returns the corresponding ID (D_*) in A. Sets Z if
 ; there's a match.
 getDirectiveID:
 	push	bc
 	push	de
-	ld	b, D_EQU+1		; D_EQU is last
+	ld	b, D_INC+1		; D_INC is last
 	ld	c, 4
 	ld	de, directiveNames
 	call	findStringInList
diff --git a/apps/zasm/io.asm b/apps/zasm/io.asm
index eceb719..5a3c68c 100644
--- a/apps/zasm/io.asm
+++ b/apps/zasm/io.asm
@@ -1,3 +1,36 @@
+; I/Os in zasm
+;
+; As a general rule, I/O in zasm is pretty straightfoward. 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).
+;
+; 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 ioGetC (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 ioGetC 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, GetC, 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_GETC	IO_RAMSTART
 .equ	IO_IN_PUTC	IO_IN_GETC+2
@@ -7,21 +40,50 @@
 .equ	IO_OUT_PUTC	IO_OUT_GETC+2
 .equ	IO_OUT_SEEK	IO_OUT_PUTC+2
 .equ	IO_OUT_TELL	IO_OUT_SEEK+2
+; Save pos for ioSavePos and ioRecallPos
+.equ	IO_SAVED_POS	IO_OUT_TELL+2
+; File handle for included source
+.equ	IO_INCLUDE_HDL	IO_SAVED_POS+2
 ; see ioPutBack below
-.equ	IO_PUTBACK_BUF	IO_OUT_TELL+2
-.equ	IO_RAMEND	IO_PUTBACK_BUF+1
+.equ	IO_PUTBACK_BUF	IO_INCLUDE_HDL+FS_HANDLE_SIZE
+.equ	IO_IN_INCLUDE	IO_PUTBACK_BUF+1
+.equ	IO_RAMEND	IO_IN_INCLUDE+1
 
 ; *** Code ***
 
 ioInit:
 	xor	a
 	ld	(IO_PUTBACK_BUF), a
+	ld	(IO_IN_INCLUDE), a
 	ret
 
 ioGetC:
 	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	de
+	ld	de, IO_INCLUDE_HDL
+	call	JUMP_FSGETC
+	pop	de
+	or	a		; cp 0
+	ret	nz		; not zero, all good
+	; 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
+	; continue on to "normal" reading. We don't want to return our zero
+.normalmode:
+	; normal mode, read from IN stream
 	ld	ix, (IO_IN_GETC)
 	jp	(ix)
 .getback:
@@ -42,10 +104,64 @@ ioPutC:
 	ld	ix, (IO_OUT_PUTC)
 	jp	(ix)
 
-ioSeek:
+ioSavePos:
+	call	_ioTell
+	ld	(IO_SAVED_POS), hl
+	ret
+
+ioRecallPos:
+	ld	hl, (IO_SAVED_POS)
+	jr	_ioSeek
+
+ioRewind:
+	ld	hl, 0
+	jr	_ioSeek
+
+; 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_SEEK)
 	jp	(ix)
+.include:
+	; We're in "include mode", seek in FS
+	push	de
+	ld	de, IO_INCLUDE_HDL
+	call	JUMP_FSSEEK
+	pop	de
+	ret
 
-ioTell:
+_ioTell:
+	call	ioInInclude
+	jp	nz, .include
+	; normal mode, seek in IN stream
 	ld	ix, (IO_IN_TELL)
 	jp	(ix)
+.include:
+	; We're in "include mode", tell from FS
+	push	de
+	ld	de, IO_INCLUDE_HDL
+	call	JUMP_FSTELL
+	pop	de
+	ret
+
+; Sets Z according to whether we're inside an include
+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	JUMP_FSFINDFN
+	ret	nz
+	ld	hl, IO_INCLUDE_HDL
+	call	JUMP_FSOPEN
+	ld	a, 1
+	ld	(IO_IN_INCLUDE), a
+	cp	a		; ensure Z
+	ret
+
diff --git a/apps/zasm/main.asm b/apps/zasm/main.asm
index 5172f1c..16cd4ed 100644
--- a/apps/zasm/main.asm
+++ b/apps/zasm/main.asm
@@ -28,7 +28,13 @@
 ; JUMP_INTOHL
 ; JUMP_FINDCHAR
 ; JUMP_BLKSEL
+; JUMP_FSFINDFN
+; JUMP_FSOPEN
+; JUMP_FSGETC
+; JUMP_FSSEEK
+; JUMP_FSTELL
 ; RAMSTART	(where we put our variables in RAM)
+; FS_HANDLE_SIZE
 
 ; *** Variables ***
 
@@ -43,11 +49,8 @@
 ; this special pass, ZASM_FIRST_PASS will also be set so that the rest of the
 ; code behaves as is we were in the first pass.
 .equ	ZASM_LOCAL_PASS		ZASM_PC+2
-; I/O position (in terms of ioSeek/ioTell) of the current context. Used to
-; rewind to it after having parsed local labels.
-.equ	ZASM_CTX_POS		ZASM_LOCAL_PASS+1
 ; What ZASM_PC was when we started our context
-.equ	ZASM_CTX_PC		ZASM_CTX_POS+2
+.equ	ZASM_CTX_PC		ZASM_LOCAL_PASS+1
 .equ	ZASM_RAMEND		ZASM_CTX_PC+2
 
 ; *** Code ***
@@ -88,8 +91,7 @@ zasmMain:
 	call	zasmParseFile
 	ret	nz
 	; Second pass
-	ld	hl, 0
-	call	ioSeek
+	call	ioRewind
 	xor	a
 	ld	(ZASM_FIRST_PASS), a
 	call	zasmParseFile
@@ -244,8 +246,7 @@ _parseLabel:
 
 _beginLocalPass:
 	; remember were I/O was
-	call	ioTell
-	ld	(ZASM_CTX_POS), hl
+	call	ioSavePos
 	; Remember where PC was
 	ld	hl, (ZASM_PC)
 	ld	(ZASM_CTX_PC), hl
@@ -264,8 +265,7 @@ _beginLocalPass:
 _endLocalPass:
 	call	symSelectGlobalRegistry
 	; recall I/O pos
-	ld	hl, (ZASM_CTX_POS)
-	call	ioSeek
+	call	ioRecallPos
 	; recall PC
 	ld	hl, (ZASM_CTX_PC)
 	ld	(ZASM_PC), hl
diff --git a/parts/z80/fs.asm b/parts/z80/fs.asm
index 1147550..93a7cad 100644
--- a/parts/z80/fs.asm
+++ b/parts/z80/fs.asm
@@ -401,8 +401,8 @@ fsOpen:
 fsPlaceH:
 	push	af
 	push	hl
-	ld	ixh, d
-	ld	ixl, e
+	push	de
+	pop	ix
 	push	ix
 	ld	l, (ix)
 	ld	h, (ix+1)
@@ -417,8 +417,7 @@ fsPlaceH:
 fsAdvanceH:
 	push	af
 	inc	(ix)
-	ld	a, (ix)
-	jr	nc, .end
+	jr	nz, .end
 	inc	(ix+1)
 .end:
 	pop	af
diff --git a/tools/emul/Makefile b/tools/emul/Makefile
index d3ba997..d24d287 100644
--- a/tools/emul/Makefile
+++ b/tools/emul/Makefile
@@ -14,12 +14,15 @@ $(KERNEL_HEADERS):
 zasm-user.h: zasm_user.asm
 	scas -o - -I ../../apps/zasm $< | ./bin2c.sh USERSPACE | tee $@ > /dev/null
 
-zasm-includes.h: ../../parts/z80 $(CFSPACK)
+zasm-includes.cfs: ../../parts/z80 $(CFSPACK)
 	cp -rf $< zasm-includes
 	rm zasm-includes/README.md
-	$(CFSPACK) zasm-includes | ./bin2c.sh FSDEV | tee $@ > /dev/null
+	$(CFSPACK) zasm-includes > $@
 	rm -rf zasm-includes
 
+zasm-includes.h: zasm-includes.cfs
+	./bin2c.sh FSDEV < $< | tee $@ > /dev/null
+	
 shell: shell.c libz80/libz80.o shell-kernel.h $(CFSPACK)
 zasm: zasm.c libz80/libz80.o zasm-kernel.h zasm-user.h zasm-includes.h
 $(TARGETS):
@@ -34,4 +37,4 @@ $(CFSPACK):
 
 .PHONY: clean
 clean:
-	rm -f $(TARGETS) $(KERNEL_HEADERS) $(USER_HEADERS)
+	rm -f $(TARGETS) $(KERNEL_HEADERS) $(USER_HEADERS) zasm-includes.*
diff --git a/tools/emul/zasm.c b/tools/emul/zasm.c
index b651e2f..24be458 100644
--- a/tools/emul/zasm.c
+++ b/tools/emul/zasm.c
@@ -83,7 +83,7 @@ static uint8_t io_read(int unused, uint16_t addr)
             return fsdev_ptr & 0xff;
         } else {
 #ifdef DEBUG
-            fprintf(stderr, "tell %d\n", fsdev_ptr);
+            fprintf(stderr, "FS tell %d\n", fsdev_ptr);
 #endif
             fsdev_middle_of_seek_tell = 1;
             return fsdev_ptr >> 8;
@@ -122,7 +122,7 @@ static void io_write(int unused, uint16_t addr, uint8_t val)
             fsdev_ptr |= val;
             fsdev_middle_of_seek_tell = 0;
 #ifdef DEBUG
-            fprintf(stderr, "seek %d\n", fsdev_ptr);
+            fprintf(stderr, "FS seek %d\n", fsdev_ptr);
 #endif
         } else {
             fsdev_ptr = (val << 8) & 0xff00;
@@ -155,6 +155,7 @@ int main()
     for (int i=0; i<sizeof(FSDEV); i++) {
         fsdev[i] = FSDEV[i];
     }
+    fsdev_size = sizeof(FSDEV);
     // read stdin in buffer
     inpt_size = 0;
     inpt_ptr = 0;
diff --git a/tools/emul/zasm_glue.asm b/tools/emul/zasm_glue.asm
index aaac49e..851fd9e 100644
--- a/tools/emul/zasm_glue.asm
+++ b/tools/emul/zasm_glue.asm
@@ -18,6 +18,11 @@ jp	intoHL
 jp	findchar
 jp	parseHexPair
 jp	blkSel
+jp	fsFindFN
+jp	fsOpen
+jp	fsGetC
+jp	fsSeek
+jp	fsTell
 
 #include "core.asm"
 .equ	BLOCKDEV_RAMSTART	RAMSTART
@@ -89,19 +94,19 @@ fsdevPutC:
 
 fsdevSeek:
 	push	af
-	ld	a, l
-	out	(FS_SEEK_PORT), a
 	ld	a, h
 	out	(FS_SEEK_PORT), a
+	ld	a, l
+	out	(FS_SEEK_PORT), a
 	pop	af
 	ret
 
 fsdevTell:
 	push	af
 	in	a, (FS_SEEK_PORT)
-	ld	l, a
-	in	a, (FS_SEEK_PORT)
 	ld	h, a
+	in	a, (FS_SEEK_PORT)
+	ld	l, a
 	pop	af
 	ret
 
diff --git a/tools/emul/zasm_user.asm b/tools/emul/zasm_user.asm
index 1bd53be..157fbaf 100644
--- a/tools/emul/zasm_user.asm
+++ b/tools/emul/zasm_user.asm
@@ -9,7 +9,13 @@ JUMP_INTOHL	.equ    0x15
 JUMP_FINDCHAR	.equ    0x18
 JUMP_PARSEHEXPAIR .equ  0x1b
 JUMP_BLKSEL	.equ    0x1e
+JUMP_FSFINDFN	.equ    0x21
+JUMP_FSOPEN	.equ    0x24
+JUMP_FSGETC	.equ    0x27
+JUMP_FSSEEK	.equ    0x2a
+JUMP_FSTELL	.equ    0x2d
 
+.equ	FS_HANDLE_SIZE	8
 .equ	USER_CODE	0x4800
 .equ	RAMSTART	0x5800
 .org	USER_CODE
diff --git a/tools/tests/zasm/runtests.sh b/tools/tests/zasm/runtests.sh
index 0d54775..dd0b11e 100755
--- a/tools/tests/zasm/runtests.sh
+++ b/tools/tests/zasm/runtests.sh
@@ -4,11 +4,12 @@ set -e
 
 TMPFILE=$(mktemp)
 SCAS=scas
+PARTS=../../../parts/z80
 ZASM=../../emul/zasm
 ASMFILE=../../../apps/zasm/instr.asm
 
 cmpas() {
-    EXPECTED=$($SCAS -o - "$1" | xxd)
+    EXPECTED=$($SCAS -I ${PARTS} -o - "$1" | xxd)
     ACTUAL=$(cat $1 | $ZASM | xxd)
     if [ "$ACTUAL" == "$EXPECTED" ]; then
         echo ok
@@ -21,9 +22,6 @@ cmpas() {
     fi
 }
 
-echo "Comparing core.asm"
-cmpas ../../../parts/z80/core.asm
-
 for fn in *.asm; do
     echo "Comparing ${fn}"
     cmpas $fn
diff --git a/tools/tests/zasm/test6.asm b/tools/tests/zasm/test6.asm
new file mode 100644
index 0000000..648a642
--- /dev/null
+++ b/tools/tests/zasm/test6.asm
@@ -0,0 +1,3 @@
+add	a, b
+#include "core.asm"
+inc	b