zasm: assemble multiple lines at once

This commit is contained in:
Virgil Dupras 2019-04-30 15:51:39 -04:00
parent 2653826dff
commit 57c3dfece8
7 changed files with 183 additions and 85 deletions

View File

@ -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

View File

@ -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

48
apps/zasm/main.asm Normal file
View File

@ -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"

View File

@ -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

View File

@ -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:

68
apps/zasm/util.asm Normal file
View File

@ -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

View File

@ -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