mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-02 20:20:55 +11:00
Compare commits
3 Commits
019d05f64c
...
f5b04fc02f
Author | SHA1 | Date | |
---|---|---|---|
|
f5b04fc02f | ||
|
0bd58fd178 | ||
|
1cea6e71e0 |
19
CODE.md
19
CODE.md
@ -32,6 +32,25 @@ Thus, code that glue parts together could look like:
|
||||
MOD2_RAMSTART .equ MOD1_RAMEND
|
||||
#include "mod2.asm"
|
||||
|
||||
## Register protection
|
||||
|
||||
As a general rule, all routines systematically protect registers they use,
|
||||
including input parameters. This allows us to stop worrying, each time we call
|
||||
a routine, whether our registers are all messed up.
|
||||
|
||||
Some routines stray from that rule, but the fact that they destroy a particular
|
||||
register is documented. An undocumented register change is considered a bug.
|
||||
Clean up after yourself, you nasty routine!
|
||||
|
||||
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.
|
||||
|
24
TRICKS.txt
24
TRICKS.txt
@ -1,6 +1,8 @@
|
||||
This file describe tricks and conventions that are used throughout the code and
|
||||
might need explanation.
|
||||
|
||||
*** Quickies
|
||||
|
||||
or a: Equivalent to "cp 0", but results in a shorter opcode.
|
||||
|
||||
xor a: sets A to 0 more efficiently than ld a, 0
|
||||
@ -8,12 +10,16 @@ xor a: sets A to 0 more efficiently than ld a, 0
|
||||
and 0xbf: Given a letter in the a-z range, changes it to its uppercase value
|
||||
if it's already uppercased, then it stays that way.
|
||||
|
||||
*** Z flag for results
|
||||
|
||||
Z if almost always used as a success indicator for routines. Set for success,
|
||||
Reset for failure. "xor a" (destroys A) and "cp a" (preserves A) are used to
|
||||
ensure Z is set. To ensure that it is reset, it's a bit more complicated and
|
||||
"unsetZ" routine exists for that, although that in certain circumstances,
|
||||
"inc a \ dec a" or "or a" can work.
|
||||
|
||||
*** Little endian
|
||||
|
||||
z80 is little endian in its 16-bit loading operations. For example, "ld hl, (0)"
|
||||
will load the contents of memory address 0 in L and memory address 1 in H. This
|
||||
little-endianess is followed by Collapse OS in most situations. When it's not,
|
||||
@ -25,3 +31,21 @@ are stored as "big endian pair of little endian 16-bit numbers". For example,
|
||||
if "ld dehl, (0)" existed and if the first 4 bytes of memory were 0x01, 0x02,
|
||||
0x03 and 0x04, then DE (being the "high" word) would be 0x0201 and HL would be
|
||||
0x0403.
|
||||
|
||||
*** DAA
|
||||
|
||||
When it comes to dealing with decimals, the DAA instruction, which look a bit
|
||||
obscur, can be very useful. It transforms the result of a previous arithmetic
|
||||
operation involving two BCD (binary coded decimal, one digit in high nibble,
|
||||
the other digit in low nibble. For example, 0x99 represents 99) into a valid
|
||||
BCD. For example, 0x12+0x19=0x2b, but after calling DAA, it will be 0x31.
|
||||
|
||||
To clear misunderstanding: this does **not** transform an arbitrary value into
|
||||
BCD. For example, "ld a, 0xff \ daa" isn't going to magically give you a binary
|
||||
coded 255 (how could it?). This is designed to be ran after an arithmetic
|
||||
operation.
|
||||
|
||||
A common trick to transform an arbitrary number to BCD is to loop 8 times over
|
||||
your bitstream, SLA your bits out of your binary value and then run
|
||||
"adc a, a \ daa" over it (with provisions for carries if you expect numbers
|
||||
over 99).
|
||||
|
@ -11,7 +11,12 @@
|
||||
|
||||
.inc "core.asm"
|
||||
.inc "lib/util.asm"
|
||||
.inc "lib/ari.asm"
|
||||
.inc "lib/parse.asm"
|
||||
.inc "lib/fmt.asm"
|
||||
.equ EXPR_PARSE parseLiteral
|
||||
.inc "lib/expr.asm"
|
||||
.inc "basic/tok.asm"
|
||||
.equ BAS_RAMSTART USER_RAMSTART
|
||||
.inc "basic/main.asm"
|
||||
USER_RAMSTART:
|
||||
|
@ -1,10 +1,13 @@
|
||||
; *** Constants ***
|
||||
.equ BAS_SCRATCHPAD_SIZE 0x20
|
||||
; *** Variables ***
|
||||
; Value of `SP` when basic was first invoked. This is where SP is going back to
|
||||
; on restarts.
|
||||
.equ BAS_INITSP BAS_RAMSTART
|
||||
; **Pointer** to current line number
|
||||
.equ BAS_PCURLN @+2
|
||||
.equ BAS_RAMEND @+2
|
||||
.equ BAS_SCRATCHPAD @+2
|
||||
.equ BAS_RAMEND @+BAS_SCRATCHPAD_SIZE
|
||||
|
||||
; *** Code ***
|
||||
basStart:
|
||||
@ -39,24 +42,37 @@ basPrompt:
|
||||
.db "> ", 0
|
||||
|
||||
basDirect:
|
||||
; First, get cmd length
|
||||
call fnWSIdx
|
||||
cp 7
|
||||
jr nc, .unknown ; Too long, can't possibly fit anything.
|
||||
; A contains whitespace IDX, save it in B
|
||||
ld b, a
|
||||
ex de, hl
|
||||
ld hl, basCmds1
|
||||
ld hl, basCmds1+2
|
||||
.loop:
|
||||
ld a, 4
|
||||
ld a, b ; whitespace IDX
|
||||
call strncmp
|
||||
jr z, .found
|
||||
ld a, 6
|
||||
ld a, 8
|
||||
call addHL
|
||||
ld a, (hl)
|
||||
cp 0xff
|
||||
jr nz, .loop
|
||||
.unknown:
|
||||
ld hl, .sUnknown
|
||||
jr basPrintLn
|
||||
|
||||
.found:
|
||||
inc hl \ inc hl \ inc hl \ inc hl
|
||||
dec hl \ dec hl
|
||||
call intoHL
|
||||
jp (hl)
|
||||
push hl \ pop ix
|
||||
; Bring back command string from DE to HL
|
||||
ex de, hl
|
||||
ld a, b ; cmd's length
|
||||
call addHL
|
||||
call rdWS
|
||||
jp (ix)
|
||||
|
||||
.sUnknown:
|
||||
.db "Unknown command", 0
|
||||
@ -66,7 +82,17 @@ basPrintLn:
|
||||
call printstr
|
||||
jp printcrlf
|
||||
|
||||
basERR:
|
||||
ld hl, .sErr
|
||||
jr basPrintLn
|
||||
.sErr:
|
||||
.db "ERR", 0
|
||||
|
||||
; *** Commands ***
|
||||
; A command receives its argument through (HL), which is already placed to
|
||||
; either:
|
||||
; 1 - the end of the string if the command has no arg.
|
||||
; 2 - the beginning of the arg, with whitespace properly skipped.
|
||||
basBYE:
|
||||
ld hl, .sBye
|
||||
call basPrintLn
|
||||
@ -78,10 +104,20 @@ basBYE:
|
||||
.sBye:
|
||||
.db "Goodbye!", 0
|
||||
|
||||
basPRINT:
|
||||
call parseExpr
|
||||
jp nz, basERR
|
||||
push ix \ pop de
|
||||
ld hl, BAS_SCRATCHPAD
|
||||
call fmtDecimal
|
||||
jp basPrintLn
|
||||
|
||||
; direct only
|
||||
basCmds1:
|
||||
.db "bye", 0
|
||||
.dw basBYE
|
||||
.db "bye", 0, 0, 0
|
||||
; statements
|
||||
basCmds2:
|
||||
.db 0xff ; end of table
|
||||
.dw basPRINT
|
||||
.db "print", 0
|
||||
.db 0xff, 0xff, 0xff ; end of table
|
||||
|
48
apps/basic/tok.asm
Normal file
48
apps/basic/tok.asm
Normal file
@ -0,0 +1,48 @@
|
||||
; Expect at least one whitespace (0x20, 0x09) at (HL), and then advance HL
|
||||
; until a non-whitespace character is met.
|
||||
; HL is advanced to the first non-whitespace char.
|
||||
; Sets Z on success, unset on failure.
|
||||
; Failure is either not having a first whitespace or reaching the end of the
|
||||
; string.
|
||||
; Sets Z if we found a non-whitespace char, unset if we found the end of string.
|
||||
rdWS:
|
||||
ld a, (hl)
|
||||
call isSep
|
||||
ret nz ; failure
|
||||
.loop:
|
||||
inc hl
|
||||
ld a, (hl)
|
||||
call isSep
|
||||
jr z, .loop
|
||||
or a ; cp 0
|
||||
jp z, .fail
|
||||
cp a ; ensure Z
|
||||
ret
|
||||
.fail:
|
||||
; A is zero at this point
|
||||
inc a ; unset Z
|
||||
ret
|
||||
|
||||
; Find the first whitespace in (HL) and returns its index in A
|
||||
; Sets Z if whitespace is found, unset if end of string was found.
|
||||
; In the case where no whitespace was found, A returns the length of the string.
|
||||
fnWSIdx:
|
||||
push hl
|
||||
push bc
|
||||
ld b, 0
|
||||
.loop:
|
||||
ld a, (hl)
|
||||
call isSep
|
||||
jr z, .found
|
||||
or a
|
||||
jr z, .eos
|
||||
inc hl
|
||||
inc b
|
||||
jr .loop
|
||||
.eos:
|
||||
inc a ; unset Z
|
||||
.found: ; Z already set from isSep
|
||||
ld a, b
|
||||
pop bc
|
||||
pop hl
|
||||
ret
|
27
apps/lib/ari.asm
Normal file
27
apps/lib/ari.asm
Normal file
@ -0,0 +1,27 @@
|
||||
; Borrowed from Tasty Basic by Dimitri Theulings (GPL).
|
||||
; Divide HL by DE, placing the result in BC and the remainder in HL.
|
||||
divide:
|
||||
push hl ; --> lvl 1
|
||||
ld l, h ; divide h by de
|
||||
ld h, 0
|
||||
call .dv1
|
||||
ld b, c ; save result in b
|
||||
ld a, l ; (remainder + l) / de
|
||||
pop hl ; <-- lvl 1
|
||||
ld h, a
|
||||
.dv1:
|
||||
ld c, 0xff ; result in c
|
||||
.dv2:
|
||||
inc c ; dumb routine
|
||||
call .subde ; divide using subtract and count
|
||||
jr nc, .dv2
|
||||
add hl, de
|
||||
ret
|
||||
.subde:
|
||||
ld a, l
|
||||
sub e ; subtract de from hl
|
||||
ld l, a
|
||||
ld a, h
|
||||
sbc a, d
|
||||
ld h, a
|
||||
ret
|
@ -1,3 +1,15 @@
|
||||
; *** Requirements ***
|
||||
; findchar
|
||||
; multDEBC
|
||||
;
|
||||
; *** Defines ***
|
||||
;
|
||||
; EXPR_PARSE: routine to call to parse literals or symbols that are part of
|
||||
; the expression. Routine's signature:
|
||||
; String in (HL), returns its parsed value to IX. Z for success.
|
||||
;
|
||||
; *** Code ***
|
||||
;
|
||||
; Parse expression in string at (HL) and returns the result in IX.
|
||||
; We expect (HL) to be disposable: we mutate it to avoid having to make a copy.
|
||||
; Sets Z on success, unset on error.
|
||||
@ -19,7 +31,7 @@ _parseExpr:
|
||||
ld a, '*'
|
||||
call _findAndSplit
|
||||
jp z, _applyMult
|
||||
jp parseNumberOrSymbol
|
||||
jp EXPR_PARSE
|
||||
|
||||
; Given a string in (HL) and a separator char in A, return a splitted string,
|
||||
; that is, the same (HL) string but with the found A char replaced by a null
|
46
apps/lib/fmt.asm
Normal file
46
apps/lib/fmt.asm
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
; Format the number in DE into the string at (HL) in a decimal form.
|
||||
; Null-terminated. DE is considered an unsigned number.
|
||||
fmtDecimal:
|
||||
push ix
|
||||
push hl
|
||||
push de
|
||||
push af
|
||||
|
||||
push hl \ pop ix
|
||||
ex de, hl ; orig number now in HL
|
||||
ld e, 0
|
||||
.loop1:
|
||||
call .div10
|
||||
push hl ; push remainder. --> lvl E
|
||||
inc e
|
||||
ld a, b ; result 0?
|
||||
or c
|
||||
push bc \ pop hl
|
||||
jr nz, .loop1 ; not zero, continue
|
||||
; We now have C digits to print in the stack.
|
||||
; Spit them!
|
||||
push ix \ pop hl ; restore orig HL.
|
||||
ld b, e
|
||||
.loop2:
|
||||
pop de ; <-- lvl E
|
||||
ld a, '0'
|
||||
add a, e
|
||||
ld (hl), a
|
||||
inc hl
|
||||
djnz .loop2
|
||||
|
||||
; null terminate
|
||||
xor a
|
||||
ld (hl), a
|
||||
pop af
|
||||
pop de
|
||||
pop hl
|
||||
pop ix
|
||||
ret
|
||||
.div10:
|
||||
push de
|
||||
ld de, 0x000a
|
||||
call divide
|
||||
pop de
|
||||
ret
|
@ -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
|
||||
|
||||
|
@ -26,3 +26,67 @@ strcpy:
|
||||
pop hl
|
||||
ret
|
||||
|
||||
; Compares strings pointed to by HL and DE until one of them hits its null char.
|
||||
; If equal, Z is set. If not equal, Z is reset.
|
||||
strcmp:
|
||||
push hl
|
||||
push de
|
||||
|
||||
.loop:
|
||||
ld a, (de)
|
||||
cp (hl)
|
||||
jr nz, .end ; not equal? break early. NZ is carried out
|
||||
; to the called
|
||||
or a ; If our chars are null, stop the cmp
|
||||
jr z, .end ; The positive result will be carried to the
|
||||
; caller
|
||||
inc hl
|
||||
inc de
|
||||
jr .loop
|
||||
|
||||
.end:
|
||||
pop de
|
||||
pop hl
|
||||
; Because we don't call anything else than CP that modify the Z flag,
|
||||
; our Z value will be that of the last cp (reset if we broke the loop
|
||||
; 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
|
||||
|
||||
; DE * BC -> DE (high) and HL (low)
|
||||
multDEBC:
|
||||
ld hl, 0
|
||||
ld a, 0x10
|
||||
.loop:
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
jr nc, .noinc
|
||||
add hl, bc
|
||||
jr nc, .noinc
|
||||
inc de
|
||||
.noinc:
|
||||
dec a
|
||||
jr nz, .loop
|
||||
ret
|
||||
|
@ -31,8 +31,6 @@
|
||||
; _blkSeek
|
||||
; _blkTell
|
||||
; printstr
|
||||
; FS_HANDLE_SIZE
|
||||
; BLOCKDEV_SIZE
|
||||
|
||||
.inc "user.h"
|
||||
|
||||
@ -80,7 +78,8 @@ jp zasmMain
|
||||
.equ DIREC_RAMSTART INS_RAMEND
|
||||
.inc "zasm/directive.asm"
|
||||
.inc "zasm/parse.asm"
|
||||
.inc "zasm/expr.asm"
|
||||
.equ EXPR_PARSE parseNumberOrSymbol
|
||||
.inc "lib/expr.asm"
|
||||
.equ SYM_RAMSTART DIREC_RAMEND
|
||||
.inc "zasm/symbol.asm"
|
||||
.equ ZASM_RAMSTART SYM_RAMEND
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
@ -98,32 +76,6 @@ strncmpI:
|
||||
; early, set otherwise)
|
||||
ret
|
||||
|
||||
; Compares strings pointed to by HL and DE until one of them hits its null char.
|
||||
; If equal, Z is set. If not equal, Z is reset.
|
||||
strcmp:
|
||||
push hl
|
||||
push de
|
||||
|
||||
.loop:
|
||||
ld a, (de)
|
||||
cp (hl)
|
||||
jr nz, .end ; not equal? break early. NZ is carried out
|
||||
; to the called
|
||||
cp 0 ; If our chars are null, stop the cmp
|
||||
jr z, .end ; The positive result will be carried to the
|
||||
; caller
|
||||
inc hl
|
||||
inc de
|
||||
jr .loop
|
||||
|
||||
.end:
|
||||
pop de
|
||||
pop hl
|
||||
; Because we don't call anything else than CP that modify the Z flag,
|
||||
; our Z value will be that of the last cp (reset if we broke the loop
|
||||
; early, set otherwise)
|
||||
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.
|
||||
@ -219,19 +171,3 @@ findStringInList:
|
||||
ret
|
||||
|
||||
|
||||
; DE * BC -> DE (high) and HL (low)
|
||||
multDEBC:
|
||||
ld hl, 0
|
||||
ld a, 0x10
|
||||
.loop:
|
||||
add hl, hl
|
||||
rl e
|
||||
rl d
|
||||
jr nc, .noinc
|
||||
add hl, bc
|
||||
jr nc, .noinc
|
||||
inc de
|
||||
.noinc:
|
||||
dec a
|
||||
jr nz, .loop
|
||||
ret
|
||||
|
@ -18,7 +18,8 @@ jp test
|
||||
.inc "zasm/parse.asm"
|
||||
.equ SYM_RAMSTART DIREC_LASTVAL+2
|
||||
.inc "zasm/symbol.asm"
|
||||
.inc "zasm/expr.asm"
|
||||
.equ EXPR_PARSE parseNumberOrSymbol
|
||||
.inc "lib/expr.asm"
|
||||
|
||||
; Pretend that we aren't in first pass
|
||||
zasmIsFirstPass:
|
||||
|
70
tools/tests/unit/test_lib_fmt.asm
Normal file
70
tools/tests/unit/test_lib_fmt.asm
Normal file
@ -0,0 +1,70 @@
|
||||
jp test
|
||||
|
||||
.inc "core.asm"
|
||||
.inc "lib/util.asm"
|
||||
.inc "lib/ari.asm"
|
||||
.inc "lib/fmt.asm"
|
||||
|
||||
testNum: .db 1
|
||||
|
||||
test:
|
||||
ld sp, 0xffff
|
||||
|
||||
call testFmtDecimal
|
||||
|
||||
; success
|
||||
xor a
|
||||
halt
|
||||
|
||||
testFmtDecimal:
|
||||
ld ix, .t1
|
||||
call .test
|
||||
ld ix, .t2
|
||||
call .test
|
||||
ld ix, .t3
|
||||
call .test
|
||||
ld ix, .t4
|
||||
call .test
|
||||
ld ix, .t5
|
||||
call .test
|
||||
ret
|
||||
.test:
|
||||
ld e, (ix)
|
||||
ld d, (ix+1)
|
||||
ld hl, sandbox
|
||||
call fmtDecimal
|
||||
ld hl, sandbox
|
||||
push ix \ pop de
|
||||
inc de \ inc de
|
||||
call strcmp
|
||||
jp nz, fail
|
||||
jp nexttest
|
||||
.t1:
|
||||
.dw 1234
|
||||
.db "1234", 0
|
||||
.t2:
|
||||
.dw 9999
|
||||
.db "9999", 0
|
||||
.t3:
|
||||
.dw 0
|
||||
.db "0", 0
|
||||
.t4:
|
||||
.dw 0x7fff
|
||||
.db "32767", 0
|
||||
.t5:
|
||||
.dw 0xffff
|
||||
.db "65535", 0
|
||||
|
||||
nexttest:
|
||||
ld a, (testNum)
|
||||
inc a
|
||||
ld (testNum), a
|
||||
ret
|
||||
|
||||
fail:
|
||||
ld a, (testNum)
|
||||
halt
|
||||
|
||||
; used as RAM
|
||||
sandbox:
|
||||
|
@ -2,6 +2,7 @@ jp test
|
||||
|
||||
.inc "core.asm"
|
||||
.inc "str.asm"
|
||||
.inc "lib/util.asm"
|
||||
.inc "zasm/util.asm"
|
||||
|
||||
testNum: .db 1
|
||||
|
@ -7,8 +7,8 @@ jp runTests
|
||||
.inc "lib/util.asm"
|
||||
.inc "zasm/util.asm"
|
||||
.inc "lib/parse.asm"
|
||||
.inc "zasm/parse.asm"
|
||||
.inc "zasm/expr.asm"
|
||||
.equ EXPR_PARSE parseLiteral
|
||||
.inc "lib/expr.asm"
|
||||
.equ INS_RAMSTART RAMSTART
|
||||
.inc "zasm/instr.asm"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user