basic: parse hex, binary and char literals

Same thing as in zasm.
This commit is contained in:
Virgil Dupras 2019-11-18 15:17:56 -05:00
parent 1cea6e71e0
commit 0bd58fd178
7 changed files with 194 additions and 187 deletions

View File

@ -46,6 +46,11 @@ Another exception to this rule are "top-level" routines, that is, routines that
aren't designed to be called from other parts of Collapse OS. Those are
generally routines close to an application's main loop.
It is important to note, however, that shadow registers aren't preserved.
Therefore, shadow registers should only be used in code that doesn't call
routines or that call a routine that explicitly states that it preserves
shadow registers.
## Stack management
Keeping the stack "balanced" is a big challenge when writing assembler code.

View File

@ -105,7 +105,7 @@ basBYE:
.db "Goodbye!", 0
basPRINT:
call parseDecimal
call parseLiteral
jp nz, basERR
push ix \ pop de
ld hl, BAS_SCRATCHPAD

View File

@ -136,3 +136,168 @@ parseDecimal:
.error:
pop hl
jp unsetZ
; Parse string at (HL) as a hexadecimal value and return value in IX under the
; same conditions as parseLiteral.
parseHexadecimal:
call hasHexPrefix
ret nz
push hl
push de
ld d, 0
inc hl ; get rid of "0x"
inc hl
call strlen
cp 3
jr c, .single
cp 4
jr c, .doubleShort ; 0x123
cp 5
jr c, .double ; 0x1234
; too long, error
jr .error
.double:
call parseHexPair
jr c, .error
inc hl ; now HL is on first char of next pair
ld d, a
jr .single
.doubleShort:
ld a, (hl)
call parseHex
jr c, .error
inc hl ; now HL is on first char of next pair
ld d, a
.single:
call parseHexPair
jr c, .error
ld e, a
cp a ; ensure Z
jr .end
.error:
call unsetZ
.end:
push de \ pop ix
pop de
pop hl
ret
; Sets Z if (HL) has a '0x' prefix.
hasHexPrefix:
ld a, (hl)
cp '0'
ret nz
push hl
inc hl
ld a, (hl)
cp 'x'
pop hl
ret
; Parse string at (HL) as a binary value (0b010101) and return value in IX.
; High IX byte is always clear.
; Sets Z on success.
parseBinaryLiteral:
call hasBinPrefix
ret nz
push bc
push hl
push de
ld d, 0
inc hl ; get rid of "0b"
inc hl
call strlen
or a
jr z, .error ; empty, error
cp 9
jr nc, .error ; >= 9, too long
; We have a string of 8 or less chars. What we'll do is that for each
; char, we rotate left and set the LSB according to whether we have '0'
; or '1'. Error out on anything else. C is our stored result.
ld b, a ; we loop for "strlen" times
ld c, 0 ; our stored result
.loop:
rlc c
ld a, (hl)
inc hl
cp '0'
jr z, .nobit ; no bit to set
cp '1'
jr nz, .error ; not 0 or 1
; We have a bit to set
inc c
.nobit:
djnz .loop
ld e, c
cp a ; ensure Z
jr .end
.error:
call unsetZ
.end:
push de \ pop ix
pop de
pop hl
pop bc
ret
; Sets Z if (HL) has a '0b' prefix.
hasBinPrefix:
ld a, (hl)
cp '0'
ret nz
push hl
inc hl
ld a, (hl)
cp 'b'
pop hl
ret
; Parse string at (HL) and, if it is a char literal, sets Z and return
; corresponding value in IX. High IX byte is always clear.
;
; A valid char literal starts with ', ends with ' and has one character in the
; middle. No escape sequence are accepted, but ''' will return the apostrophe
; character.
parseCharLiteral:
ld a, 0x27 ; apostrophe (') char
cp (hl)
ret nz
push hl
push de
inc hl
inc hl
cp (hl)
jr nz, .end ; not ending with an apostrophe
inc hl
ld a, (hl)
or a ; cp 0
jr nz, .end ; string has to end there
; Valid char, good
ld d, a ; A is zero, take advantage of that
dec hl
dec hl
ld a, (hl)
ld e, a
cp a ; ensure Z
.end:
push de \ pop ix
pop de
pop hl
ret
; Parses the string at (HL) and returns the 16-bit value in IX. The string
; can be a decimal literal (1234), a hexadecimal literal (0x1234) or a char
; literal ('X').
;
; 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.
parseLiteral:
call parseCharLiteral
ret z
call parseHexadecimal
ret z
call parseBinaryLiteral
ret z
jp parseDecimal

View File

@ -52,3 +52,25 @@ strcmp:
; early, set otherwise)
ret
; Returns length of string at (HL) in A.
; Doesn't include null termination.
strlen:
push bc
push hl
ld bc, 0
xor a ; look for null char
.loop:
cpi
jp z, .found
jr .loop
.found:
; How many char do we have? the (NEG BC)-1, which started at 0 and
; decreased at each CPI call. In this routine, we stay in the 8-bit
; realm, so C only.
ld a, c
neg
dec a
pop hl
pop bc
ret

View File

@ -1,167 +1,3 @@
; Parse string at (HL) as a hexadecimal value and return value in IX under the
; same conditions as parseLiteral.
parseHexadecimal:
call hasHexPrefix
ret nz
push hl
push de
ld d, 0
inc hl ; get rid of "0x"
inc hl
call strlen
cp 3
jr c, .single
cp 4
jr c, .doubleShort ; 0x123
cp 5
jr c, .double ; 0x1234
; too long, error
jr .error
.double:
call parseHexPair
jr c, .error
inc hl ; now HL is on first char of next pair
ld d, a
jr .single
.doubleShort:
ld a, (hl)
call parseHex
jr c, .error
inc hl ; now HL is on first char of next pair
ld d, a
.single:
call parseHexPair
jr c, .error
ld e, a
cp a ; ensure Z
jr .end
.error:
call unsetZ
.end:
push de \ pop ix
pop de
pop hl
ret
; Sets Z if (HL) has a '0x' prefix.
hasHexPrefix:
ld a, (hl)
cp '0'
ret nz
push hl
inc hl
ld a, (hl)
cp 'x'
pop hl
ret
; Parse string at (HL) as a binary value (0b010101) and return value in IX.
; High IX byte is always clear.
; Sets Z on success.
parseBinaryLiteral:
call hasBinPrefix
ret nz
push bc
push hl
push de
ld d, 0
inc hl ; get rid of "0b"
inc hl
call strlen
or a
jr z, .error ; empty, error
cp 9
jr nc, .error ; >= 9, too long
; We have a string of 8 or less chars. What we'll do is that for each
; char, we rotate left and set the LSB according to whether we have '0'
; or '1'. Error out on anything else. C is our stored result.
ld b, a ; we loop for "strlen" times
ld c, 0 ; our stored result
.loop:
rlc c
ld a, (hl)
inc hl
cp '0'
jr z, .nobit ; no bit to set
cp '1'
jr nz, .error ; not 0 or 1
; We have a bit to set
inc c
.nobit:
djnz .loop
ld e, c
cp a ; ensure Z
jr .end
.error:
call unsetZ
.end:
push de \ pop ix
pop de
pop hl
pop bc
ret
; Sets Z if (HL) has a '0b' prefix.
hasBinPrefix:
ld a, (hl)
cp '0'
ret nz
push hl
inc hl
ld a, (hl)
cp 'b'
pop hl
ret
; Parse string at (HL) and, if it is a char literal, sets Z and return
; corresponding value in IX. High IX byte is always clear.
;
; A valid char literal starts with ', ends with ' and has one character in the
; middle. No escape sequence are accepted, but ''' will return the apostrophe
; character.
parseCharLiteral:
ld a, 0x27 ; apostrophe (') char
cp (hl)
ret nz
push hl
push de
inc hl
inc hl
cp (hl)
jr nz, .end ; not ending with an apostrophe
inc hl
ld a, (hl)
or a ; cp 0
jr nz, .end ; string has to end there
; Valid char, good
ld d, a ; A is zero, take advantage of that
dec hl
dec hl
ld a, (hl)
ld e, a
cp a ; ensure Z
.end:
push de \ pop ix
pop de
pop hl
ret
; Parses the string at (HL) and returns the 16-bit value in IX. The string
; can be a decimal literal (1234), a hexadecimal literal (0x1234) or a char
; literal ('X').
;
; 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.
parseLiteral:
call parseCharLiteral
ret z
call parseHexadecimal
ret z
call parseBinaryLiteral
ret z
jp parseDecimal
; Parse string in (HL) and return its numerical value whether its a number
; literal or a symbol. Returns value in IX.
; Sets Z if number or symbol is valid, unset otherwise.

View File

@ -30,28 +30,6 @@ toggleZ:
cp a
ret
; Returns length of string at (HL) in A.
; Doesn't include null termination.
strlen:
push bc
push hl
ld bc, 0
ld a, 0 ; look for null char
.loop:
cpi
jp z, .found
jr .loop
.found:
; How many char do we have? the (NEG BC)-1, which started at 0 and
; decreased at each CPI call. In this routine, we stay in the 8-bit
; realm, so C only.
ld a, c
neg
dec a
pop hl
pop bc
ret
; Sets Z if string at (HL) is one character long
strIs1L:
xor a

View File

@ -2,6 +2,7 @@ jp test
.inc "core.asm"
.inc "str.asm"
.inc "lib/util.asm"
.inc "zasm/util.asm"
testNum: .db 1