diff --git a/apps/zasm/emul/Makefile b/apps/zasm/emul/Makefile index 171d023..4677893 100644 --- a/apps/zasm/emul/Makefile +++ b/apps/zasm/emul/Makefile @@ -8,5 +8,5 @@ libz80/libz80.o: libz80/z80.c kernel.h: glue.asm scas -o - -I ../../../parts/z80 $< | ./bin2c.sh KERNEL | tee $@ > /dev/null -zasm.h: ../main.asm ../instr.asm ../tok.asm +zasm.h: $(addprefix ../, main.asm instr.asm tok.asm util.asm) scas -o - -I.. $< | ./bin2c.sh ZASM | tee $@ > /dev/null diff --git a/apps/zasm/instr.asm b/apps/zasm/instr.asm index db8033d..750b428 100644 --- a/apps/zasm/instr.asm +++ b/apps/zasm/instr.asm @@ -6,73 +6,6 @@ INSTR_TBL_CNT .equ 135 ; size in bytes of each row in the primary instructions table INSTR_TBL_ROWSIZE .equ 9 -; run RLA the number of times specified in B -rlaX: - ; first, see if B == 0 to see if we need to bail out - inc b - dec b - ret z ; Z flag means we had B = 0 -.loop: rla - djnz .loop - ret - -; Copy BC bytes from (HL) to (DE). -copy: - ; first, let's see if BC is zero. if it is, we have nothing to do. - ; remember: 16-bit inc/dec don't modify flags. that's why we check B - ; and C separately. - inc b - dec b - jr nz, .proceed - inc c - dec c - ret z ; zero? nothing to do -.proceed: - push bc - push de - push hl - ldir - pop hl - pop de - pop bc - ret - -callHL: - jp (hl) - ret - -; If string at (HL) starts with ( and ends with ), "enter" into the parens -; (advance HL and put a null char at the end of the string) and set Z. -; Otherwise, do nothing and reset Z. -enterParens: - ld a, (hl) - cp '(' - ret nz ; nothing to do - push hl - ld a, 0 ; look for null char - ; advance until we get null -.loop: - cpi - jp z, .found - jr .loop -.found: - dec hl ; cpi over-advances. go back to null-char - dec hl ; looking at the last char before null - ld a, (hl) - cp ')' - jr nz, .doNotEnter - ; We have parens. While we're here, let's put a null - xor a - ld (hl), a - pop hl ; back at the beginning. Let's advance. - inc hl - cp a ; ensure Z - ret ; we're good! -.doNotEnter: - pop hl - call JUMP_UNSETZ - ret - ; Checks whether A is 'N' or 'M' checkNOrM: cp 'N' @@ -772,13 +705,11 @@ processArg: call JUMP_UNSETZ ret -; Parse line at (HL) and write resulting opcode(s) in curUpcode. Returns the -; number of bytes written in A. -parseLine: +; Parse tokens in (tokInstr), (tokArg1) and (tokArg2) and write resulting +; opcode(s) in (curUpcode). Returns the number of bytes written in A. +parseTokens: push hl push de - call tokenize - jr nz, .error ld a, (tokInstr) cp 0 jr z, .error ; for now, we treat blank lines as errors diff --git a/apps/zasm/main.asm b/apps/zasm/main.asm new file mode 100644 index 0000000..5166d91 --- /dev/null +++ b/apps/zasm/main.asm @@ -0,0 +1,48 @@ +#include "user.inc" + +; *** Code *** +.org USER_CODE + +; Parse asm file in (HL) and outputs its upcodes in (DE). Returns the number +; of bytes written in C. +main: + ld bc, 0 ; C is our written bytes counter +.loop: + call parseLine + or a ; is zero? stop + jr z, .stop + add a, c + ld c, a + call gotoNextLine + jr nz, .stop ; error? stop + jr .loop +.stop: + ret + +; Parse line in (HL), write the resulting opcode(s) in (DE) and returns the +; number of written bytes in A. Advances HL where tokenization stopped and DE +; to where we should write the next upcode. +parseLine: + push bc + call tokenize + jr nz, .error + call parseTokens + or a ; is zero? + jr z, .error + ld b, 0 + ld c, a ; written bytes + push hl + ld hl, curUpcode + call copy + pop hl + call JUMP_ADDDE + jr .end +.error: + xor a +.end: + pop bc + ret + +#include "util.asm" +#include "tok.asm" +#include "instr.asm" diff --git a/apps/zasm/tests/runtests.sh b/apps/zasm/tests/runtests.sh index d518027..57f8421 100755 --- a/apps/zasm/tests/runtests.sh +++ b/apps/zasm/tests/runtests.sh @@ -5,13 +5,11 @@ set -e TMPFILE=$(mktemp) SCAS=scas ZASM=../emul/zasm -ASMFILE=../zasm.asm +ASMFILE=../instr.asm -./geninstrs.py $ASMFILE | \ -while read line; do - echo $line | tee "${TMPFILE}" - EXPECTED=$($SCAS -o - "${TMPFILE}" | xxd) - ACTUAL=$(echo $line | $ZASM | xxd) +cmpas() { + EXPECTED=$($SCAS -o - "$1" | xxd) + ACTUAL=$(cat $1 | $ZASM | xxd) if [ "$ACTUAL" == "$EXPECTED" ]; then echo ok else @@ -21,4 +19,15 @@ while read line; do echo $EXPECTED exit 1 fi +} + +./geninstrs.py $ASMFILE | \ +while read line; do + echo $line | tee "${TMPFILE}" + cmpas ${TMPFILE} +done + +for fn in *.asm; do + echo "Comparing ${fn}" + cmpas $fn done diff --git a/apps/zasm/tok.asm b/apps/zasm/tok.asm index ed2b82d..61fef88 100644 --- a/apps/zasm/tok.asm +++ b/apps/zasm/tok.asm @@ -30,25 +30,31 @@ tokenize: ld de, tokArg2 call readWord .end: - cp a ; ensure Z + xor a ; ensure Z pop de ret ; Sets Z is A is ';', CR, LF, or null. -isLineEnd: +isLineEndOrComment: cp ';' ret z - cp 0 + ; Continues onto isLineEnd... + +; Sets Z is A is CR, LF, or null. +isLineEnd: + or a ; same as cp 0 ret z cp 0x0d ret z cp 0x0a ret -; Sets Z is A is ' ' or ',' +; Sets Z is A is ' ' '\t' or ',' isSep: cp ' ' ret z + cp 0x09 + ret z cp ',' ret @@ -56,7 +62,7 @@ isSep: isSepOrLineEnd: call isSep ret z - call isLineEnd + call isLineEndOrComment ret ; read word in (HL) and put it in (DE), null terminated, for a maximum of A @@ -92,7 +98,7 @@ readWord: toWord: .loop: ld a, (hl) - call isLineEnd + call isLineEndOrComment jr z, .error call isSep jr nz, .success @@ -109,6 +115,38 @@ toWord: cp a ret +; Advance HL to the beginning of the next line, that is, right after the next +; 0x10 or 0x13 or both. If we reach null, we stop and error out. +; Sets Z on success, unsets it on error. +gotoNextLine: + dec hl ; a bit weird, but makes the looping easier +.loop: + inc hl + ld a, (hl) + call isLineEnd + jr nz, .loop + ; (HL) is 0x10, 0x13 or 0 + or a ; is 0? + jr z, .error + ; we might have 0x13 followed by 0x10, let's account for this. + ; Yes, 0x10 followed by 0x10 will make us skip two lines, but this is of + ; no real consequence in our context. + inc hl + ld a, (hl) + call isLineEnd + jr nz, .success + or a ; is 0? + jr z, .error + ; There was another line sep. Skip this char + inc hl + ; Continue on to .success +.success: + xor a ; ensure Z + ret +.error: + call JUMP_UNSETZ + ret + ; *** Variables *** tokInstr: diff --git a/apps/zasm/util.asm b/apps/zasm/util.asm new file mode 100644 index 0000000..3af4ee2 --- /dev/null +++ b/apps/zasm/util.asm @@ -0,0 +1,68 @@ +; run RLA the number of times specified in B +rlaX: + ; first, see if B == 0 to see if we need to bail out + inc b + dec b + ret z ; Z flag means we had B = 0 +.loop: rla + djnz .loop + ret + +; Copy BC bytes from (HL) to (DE). +copy: + ; first, let's see if BC is zero. if it is, we have nothing to do. + ; remember: 16-bit inc/dec don't modify flags. that's why we check B + ; and C separately. + inc b + dec b + jr nz, .proceed + inc c + dec c + ret z ; zero? nothing to do +.proceed: + push bc + push de + push hl + ldir + pop hl + pop de + pop bc + ret + +callHL: + jp (hl) + ret + +; If string at (HL) starts with ( and ends with ), "enter" into the parens +; (advance HL and put a null char at the end of the string) and set Z. +; Otherwise, do nothing and reset Z. +enterParens: + ld a, (hl) + cp '(' + ret nz ; nothing to do + push hl + ld a, 0 ; look for null char + ; advance until we get null +.loop: + cpi + jp z, .found + jr .loop +.found: + dec hl ; cpi over-advances. go back to null-char + dec hl ; looking at the last char before null + ld a, (hl) + cp ')' + jr nz, .doNotEnter + ; We have parens. While we're here, let's put a null + xor a + ld (hl), a + pop hl ; back at the beginning. Let's advance. + inc hl + cp a ; ensure Z + ret ; we're good! +.doNotEnter: + pop hl + call JUMP_UNSETZ + ret + + diff --git a/parts/z80/core.asm b/parts/z80/core.asm index b96afac..8703deb 100644 --- a/parts/z80/core.asm +++ b/parts/z80/core.asm @@ -15,11 +15,13 @@ P_NULL: .db 0 ; add the value of A into DE addDE: + push af add a, e jr nc, .end ; no carry? skip inc inc d .end: ld e, a + pop af ret ; copy (DE) into DE, little endian style (addresses in z80 are always have @@ -46,11 +48,13 @@ intoHL: ; add the value of A into HL addHL: + push af add a, l jr nc, .end ; no carry? skip inc inc h .end: ld l, a + pop af ret