From 0bd58fd178e1a61f242f0889b2be6cfd0bc1a8a7 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Mon, 18 Nov 2019 15:17:56 -0500 Subject: [PATCH] basic: parse hex, binary and char literals Same thing as in zasm. --- CODE.md | 5 + apps/basic/main.asm | 2 +- apps/lib/parse.asm | 165 +++++++++++++++++++++++++++++++ apps/lib/util.asm | 22 +++++ apps/zasm/parse.asm | 164 ------------------------------ apps/zasm/util.asm | 22 ----- tools/tests/unit/test_util_z.asm | 1 + 7 files changed, 194 insertions(+), 187 deletions(-) diff --git a/CODE.md b/CODE.md index 322b117..13ba495 100644 --- a/CODE.md +++ b/CODE.md @@ -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. diff --git a/apps/basic/main.asm b/apps/basic/main.asm index 63a75ae..b28c251 100644 --- a/apps/basic/main.asm +++ b/apps/basic/main.asm @@ -105,7 +105,7 @@ basBYE: .db "Goodbye!", 0 basPRINT: - call parseDecimal + call parseLiteral jp nz, basERR push ix \ pop de ld hl, BAS_SCRATCHPAD diff --git a/apps/lib/parse.asm b/apps/lib/parse.asm index 683f3ba..52e5cef 100644 --- a/apps/lib/parse.asm +++ b/apps/lib/parse.asm @@ -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 + diff --git a/apps/lib/util.asm b/apps/lib/util.asm index 22fdf32..b8a187f 100644 --- a/apps/lib/util.asm +++ b/apps/lib/util.asm @@ -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 + diff --git a/apps/zasm/parse.asm b/apps/zasm/parse.asm index 524e304..f55cf3f 100644 --- a/apps/zasm/parse.asm +++ b/apps/zasm/parse.asm @@ -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. diff --git a/apps/zasm/util.asm b/apps/zasm/util.asm index dbca51f..4281761 100644 --- a/apps/zasm/util.asm +++ b/apps/zasm/util.asm @@ -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 diff --git a/tools/tests/unit/test_util_z.asm b/tools/tests/unit/test_util_z.asm index d48cfc9..7dfd10d 100644 --- a/tools/tests/unit/test_util_z.asm +++ b/tools/tests/unit/test_util_z.asm @@ -2,6 +2,7 @@ jp test .inc "core.asm" .inc "str.asm" +.inc "lib/util.asm" .inc "zasm/util.asm" testNum: .db 1