zasm: add support for numerical constants

This commit is contained in:
Virgil Dupras 2019-04-17 14:47:42 -04:00
parent 3fe5eb3e60
commit 7996a9997a

View File

@ -33,6 +33,64 @@ rlaX:
djnz .loop djnz .loop
ret ret
; Parse the decimal char at A and extract it's 0-9 numerical value. Put the
; result in A.
;
; On success, the carry flag is reset. On error, it is set.
parseDecimal:
; First, let's see if we have an easy 0-9 case
cp '0'
ret c ; if < '0', we have a problem
cp '9'+1
; We are in the 0-9 range
sub a, '0' ; C is clear
ret
; Parses the string at (HL) and returns the 16-bit value in IX.
; As soon as the number doesn't fit 16-bit any more, parsing stops and the
; number is invalid. If the number is valid, Z is set, otherwise, unset.
parseNumber:
push hl
push de
ld ix, 0
.loop:
ld a, (hl)
cp 0
jr z, .end ; success!
call parseDecimal
jr c, .error
; Now, let's add A to IX. First, multiply by 10.
ld d, ixh ; we need a copy of the initial copy for later
ld e, ixl
add ix, ix ; x2
add ix, ix ; x4
add ix, ix ; x8
add ix, de ; x9
add ix, de ; x10
add a, ixl
jr nc, .nocarry
inc ixh
.nocarry:
ld ixl, a
; We didn't bother checking for the C flag at each step because we
; check for overflow afterwards. If ixh < d, we overflowed
ld a, ixh
cp d
jr c, .error ; carry is set? overflow
inc hl
jr .loop
.error:
call unsetZ
.end:
pop de
pop hl
ret
; Sets Z is A is ';', CR, LF, or null. ; Sets Z is A is ';', CR, LF, or null.
isLineEnd: isLineEnd:
cp ';' cp ';'
@ -112,14 +170,24 @@ toWord:
; HL is advanced to the next word. Z is set if there's a next word. ; HL is advanced to the next word. Z is set if there's a next word.
readArg: readArg:
push de push de
ld de, tmpVal ld de, tmpBuf
call readWord call readWord
push hl push hl
ld hl, tmpVal ld hl, tmpBuf
call parseArg call parseArg
pop hl pop hl
pop de pop de
ld (de), a ld (de), a
; When A is a number, IX is set with the value of that number. Because
; We don't use the space allocated to store those numbers in any other
; occasion, we store IX there unconditonally, LSB first.
inc de
ld a, ixl
ld (de), a
inc de
ld a, ixh
ld (de), a
call toWord call toWord
ret ret
@ -168,6 +236,9 @@ strlen:
; Return value 0xff holds a special meaning: arg is not empty, but doesn't match ; Return value 0xff holds a special meaning: arg is not empty, but doesn't match
; any argspec (A == 0 means arg is empty). A return value of 0xff means an ; any argspec (A == 0 means arg is empty). A return value of 0xff means an
; error. ; error.
;
; If the parsed argument is a number constant, 'N' is returned and IX contains
; the value of that constant.
parseArg: parseArg:
call strlen call strlen
cp 0 cp 0
@ -190,8 +261,14 @@ parseArg:
ld a, 5 ld a, 5
call JUMP_ADDDE call JUMP_ADDDE
djnz .loop1 djnz .loop1
; exhausted? we have a problem os specifying a wrong argspec. This is
; an internal consistency error. ; We exhausted the argspecs. Let's see if it's a number
call parseNumber
jr nz, .notanumber
; Alright, we have a parsed number in IX. We're done.
ld a, 'N'
jr .end
.notanumber:
ld a, 0xff ld a, 0xff
jr .end jr .end
.found: .found:
@ -291,11 +368,24 @@ findInGroup:
; Compare argspec from instruction table in A with argument in (HL). ; Compare argspec from instruction table in A with argument in (HL).
; For constant args, it's easy: if A == (HL), it's a success. ; For constant args, it's easy: if A == (HL), it's a success.
; If it's not this, then we check if it's a numerical arg.
; If A is a group ID, we do something else: we check that (HL) exists in the ; If A is a group ID, we do something else: we check that (HL) exists in the
; groupspec (argGrpTbl) ; groupspec (argGrpTbl)
matchArg: matchArg:
cp a, (hl) cp a, (hl)
ret z ret z
; not an exact match, let's check for numerical constants.
cp 'N'
jr z, .expectsNumber
cp 'n'
jr z, .expectsNumber
jr .notNumber
.expectsNumber:
ld a, (hl)
cp 'N' ; In parsed arg, we don't have 'n', only 'N'
ret ; whether we match or not, the result of Z is the good
; one
.notNumber:
; A bit of a delicate situation here: we want A to go in H but also ; A bit of a delicate situation here: we want A to go in H but also
; (HL) to go in A. If not careful, we overwrite each other. EXX is ; (HL) to go in A. If not careful, we overwrite each other. EXX is
; necessary to avoid invoving other registers. ; necessary to avoid invoving other registers.
@ -336,6 +426,8 @@ matchPrimaryRow:
; Parse line at (HL) and write resulting opcode(s) in (DE). Returns the number ; Parse line at (HL) and write resulting opcode(s) in (DE). Returns the number
; of bytes written in A. ; of bytes written in A.
;
; Overwrites IX
parseLine: parseLine:
call readLine call readLine
; Check whether we have errors. We don't do any parsing if we do. ; Check whether we have errors. We don't do any parsing if we do.
@ -368,7 +460,6 @@ parseLine:
; We have our matching instruction row. We're getting pretty near our ; We have our matching instruction row. We're getting pretty near our
; goal here! ; goal here!
; First, let's go in IX mode. It's easier to deal with offsets here. ; First, let's go in IX mode. It's easier to deal with offsets here.
push ix
ld ixh, d ld ixh, d
ld ixl, e ld ixl, e
; First, let's see if we're dealing with a group here ; First, let's see if we're dealing with a group here
@ -409,18 +500,63 @@ parseLine:
pop hl pop hl
; Success! ; Success!
jr .end jr .writeFirstOpcode
.notgroup: .notgroup:
; not a group? easy as pie: we return the opcode directly. ; not a group? easy as pie: we return the opcode directly.
ld a, (ix+7) ; upcode is on 8th byte ld a, (ix+7) ; upcode is on 8th byte
.end: .writeFirstOpcode:
; At the end, we have our final opcode in A! ; At the end, we have our final opcode in A!
pop ix
pop de pop de
ld (de), a ld (de), a
; Good, we are probably finished here for many primary opcodes. However,
; some primary opcodes take 8 or 16 bit constants as an argument and
; if that's the case here, we need to write it too.
; We still have our instruction row in IX. Let's revisit it.
push hl ; we use HL to point to the currently read arg
ld a, (ix+4) ; first argspec
ld hl, curArg1
cp 'N'
jr z, .withWord
cp 'n'
jr z, .withByte
ld a, (ix+5) ; second argspec
ld hl, curArg2
cp 'N'
jr z, .withWord
cp 'n'
jr z, .withByte
; nope, no number, aright, only one opcode
ld a, 1 ld a, 1
jr .end
.withByte:
; verify that the MSB in argument is zero
inc hl
inc hl ; MSB is 2nd byte
ld a, (hl)
dec hl ; HL now points to LSB
cp 0
jr nz, .numberTruncated
; Clear to proceed. HL already points to our number
inc de
ldi
ld a, 2
jr .end
.withWord:
inc hl ; HL now points to LSB
ldi ; LSB written, we point to MSB now
ldi ; MSB written
ld a, 3
jr .end
.numberTruncated:
; problem: not zero, so value is truncated. error
xor a
.end:
pop hl
ret ret
; In instruction metadata below, argument types arge indicated with a single ; In instruction metadata below, argument types arge indicated with a single
; char mnemonic that is called "argspec". This is the table of correspondance. ; char mnemonic that is called "argspec". This is the table of correspondance.
; Single letters are represented by themselves, so we don't need as much ; Single letters are represented by themselves, so we don't need as much
@ -523,6 +659,7 @@ instrTBlPrimary:
.db "LD",0,0, 's', 'h', 0, 0x0a ; LD SP, HL .db "LD",0,0, 's', 'h', 0, 0x0a ; LD SP, HL
.db "LD",0,0, 'l', 0xb, 0, 0b01110000 ; LD (HL), r .db "LD",0,0, 'l', 0xb, 0, 0b01110000 ; LD (HL), r
.db "LD",0,0, 0xb, 'l', 3, 0b01000110 ; LD r, (HL) .db "LD",0,0, 0xb, 'l', 3, 0b01000110 ; LD r, (HL)
.db "LD",0,0, 'l', 'n', 0, 0x36 ; LD (HL), n
.db "NOP", 0, 0, 0, 0, 0x00 ; NOP .db "NOP", 0, 0, 0, 0, 0x00 ; NOP
.db "OR",0,0, 'l', 0, 0, 0xb6 ; OR (HL) .db "OR",0,0, 'l', 0, 0, 0xb6 ; OR (HL)
.db "OR",0,0, 0xb, 0, 0, 0b10110000 ; OR r .db "OR",0,0, 0xb, 0, 0, 0b10110000 ; OR r
@ -556,6 +693,6 @@ curArg2:
.db 0, 0, 0 .db 0, 0, 0
; space for tmp stuff ; space for tmp stuff
tmpVal: tmpBuf:
.db 0, 0, 0, 0, 0 .fill 0x20