mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-02 18: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
|
MOD2_RAMSTART .equ MOD1_RAMEND
|
||||||
#include "mod2.asm"
|
#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
|
## Stack management
|
||||||
|
|
||||||
Keeping the stack "balanced" is a big challenge when writing assembler code.
|
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
|
This file describe tricks and conventions that are used throughout the code and
|
||||||
might need explanation.
|
might need explanation.
|
||||||
|
|
||||||
|
*** Quickies
|
||||||
|
|
||||||
or a: Equivalent to "cp 0", but results in a shorter opcode.
|
or a: Equivalent to "cp 0", but results in a shorter opcode.
|
||||||
|
|
||||||
xor a: sets A to 0 more efficiently than ld a, 0
|
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
|
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.
|
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,
|
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
|
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
|
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,
|
"unsetZ" routine exists for that, although that in certain circumstances,
|
||||||
"inc a \ dec a" or "or a" can work.
|
"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)"
|
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
|
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,
|
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,
|
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
|
0x03 and 0x04, then DE (being the "high" word) would be 0x0201 and HL would be
|
||||||
0x0403.
|
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 "core.asm"
|
||||||
.inc "lib/util.asm"
|
.inc "lib/util.asm"
|
||||||
|
.inc "lib/ari.asm"
|
||||||
.inc "lib/parse.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
|
.equ BAS_RAMSTART USER_RAMSTART
|
||||||
.inc "basic/main.asm"
|
.inc "basic/main.asm"
|
||||||
USER_RAMSTART:
|
USER_RAMSTART:
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
; *** Constants ***
|
||||||
|
.equ BAS_SCRATCHPAD_SIZE 0x20
|
||||||
; *** Variables ***
|
; *** Variables ***
|
||||||
; Value of `SP` when basic was first invoked. This is where SP is going back to
|
; Value of `SP` when basic was first invoked. This is where SP is going back to
|
||||||
; on restarts.
|
; on restarts.
|
||||||
.equ BAS_INITSP BAS_RAMSTART
|
.equ BAS_INITSP BAS_RAMSTART
|
||||||
; **Pointer** to current line number
|
; **Pointer** to current line number
|
||||||
.equ BAS_PCURLN @+2
|
.equ BAS_PCURLN @+2
|
||||||
.equ BAS_RAMEND @+2
|
.equ BAS_SCRATCHPAD @+2
|
||||||
|
.equ BAS_RAMEND @+BAS_SCRATCHPAD_SIZE
|
||||||
|
|
||||||
; *** Code ***
|
; *** Code ***
|
||||||
basStart:
|
basStart:
|
||||||
@ -39,24 +42,37 @@ basPrompt:
|
|||||||
.db "> ", 0
|
.db "> ", 0
|
||||||
|
|
||||||
basDirect:
|
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
|
ex de, hl
|
||||||
ld hl, basCmds1
|
ld hl, basCmds1+2
|
||||||
.loop:
|
.loop:
|
||||||
ld a, 4
|
ld a, b ; whitespace IDX
|
||||||
call strncmp
|
call strncmp
|
||||||
jr z, .found
|
jr z, .found
|
||||||
ld a, 6
|
ld a, 8
|
||||||
call addHL
|
call addHL
|
||||||
ld a, (hl)
|
ld a, (hl)
|
||||||
cp 0xff
|
cp 0xff
|
||||||
jr nz, .loop
|
jr nz, .loop
|
||||||
|
.unknown:
|
||||||
ld hl, .sUnknown
|
ld hl, .sUnknown
|
||||||
jr basPrintLn
|
jr basPrintLn
|
||||||
|
|
||||||
.found:
|
.found:
|
||||||
inc hl \ inc hl \ inc hl \ inc hl
|
dec hl \ dec hl
|
||||||
call intoHL
|
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:
|
.sUnknown:
|
||||||
.db "Unknown command", 0
|
.db "Unknown command", 0
|
||||||
@ -66,7 +82,17 @@ basPrintLn:
|
|||||||
call printstr
|
call printstr
|
||||||
jp printcrlf
|
jp printcrlf
|
||||||
|
|
||||||
|
basERR:
|
||||||
|
ld hl, .sErr
|
||||||
|
jr basPrintLn
|
||||||
|
.sErr:
|
||||||
|
.db "ERR", 0
|
||||||
|
|
||||||
; *** Commands ***
|
; *** 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:
|
basBYE:
|
||||||
ld hl, .sBye
|
ld hl, .sBye
|
||||||
call basPrintLn
|
call basPrintLn
|
||||||
@ -78,10 +104,20 @@ basBYE:
|
|||||||
.sBye:
|
.sBye:
|
||||||
.db "Goodbye!", 0
|
.db "Goodbye!", 0
|
||||||
|
|
||||||
|
basPRINT:
|
||||||
|
call parseExpr
|
||||||
|
jp nz, basERR
|
||||||
|
push ix \ pop de
|
||||||
|
ld hl, BAS_SCRATCHPAD
|
||||||
|
call fmtDecimal
|
||||||
|
jp basPrintLn
|
||||||
|
|
||||||
; direct only
|
; direct only
|
||||||
basCmds1:
|
basCmds1:
|
||||||
.db "bye", 0
|
|
||||||
.dw basBYE
|
.dw basBYE
|
||||||
|
.db "bye", 0, 0, 0
|
||||||
; statements
|
; statements
|
||||||
basCmds2:
|
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.
|
; 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.
|
; We expect (HL) to be disposable: we mutate it to avoid having to make a copy.
|
||||||
; Sets Z on success, unset on error.
|
; Sets Z on success, unset on error.
|
||||||
@ -19,7 +31,7 @@ _parseExpr:
|
|||||||
ld a, '*'
|
ld a, '*'
|
||||||
call _findAndSplit
|
call _findAndSplit
|
||||||
jp z, _applyMult
|
jp z, _applyMult
|
||||||
jp parseNumberOrSymbol
|
jp EXPR_PARSE
|
||||||
|
|
||||||
; Given a string in (HL) and a separator char in A, return a splitted string,
|
; 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
|
; 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:
|
.error:
|
||||||
pop hl
|
pop hl
|
||||||
jp unsetZ
|
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
|
pop hl
|
||||||
ret
|
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
|
; _blkSeek
|
||||||
; _blkTell
|
; _blkTell
|
||||||
; printstr
|
; printstr
|
||||||
; FS_HANDLE_SIZE
|
|
||||||
; BLOCKDEV_SIZE
|
|
||||||
|
|
||||||
.inc "user.h"
|
.inc "user.h"
|
||||||
|
|
||||||
@ -80,7 +78,8 @@ jp zasmMain
|
|||||||
.equ DIREC_RAMSTART INS_RAMEND
|
.equ DIREC_RAMSTART INS_RAMEND
|
||||||
.inc "zasm/directive.asm"
|
.inc "zasm/directive.asm"
|
||||||
.inc "zasm/parse.asm"
|
.inc "zasm/parse.asm"
|
||||||
.inc "zasm/expr.asm"
|
.equ EXPR_PARSE parseNumberOrSymbol
|
||||||
|
.inc "lib/expr.asm"
|
||||||
.equ SYM_RAMSTART DIREC_RAMEND
|
.equ SYM_RAMSTART DIREC_RAMEND
|
||||||
.inc "zasm/symbol.asm"
|
.inc "zasm/symbol.asm"
|
||||||
.equ ZASM_RAMSTART SYM_RAMEND
|
.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
|
; Parse string in (HL) and return its numerical value whether its a number
|
||||||
; literal or a symbol. Returns value in IX.
|
; literal or a symbol. Returns value in IX.
|
||||||
; Sets Z if number or symbol is valid, unset otherwise.
|
; Sets Z if number or symbol is valid, unset otherwise.
|
||||||
|
@ -30,28 +30,6 @@ toggleZ:
|
|||||||
cp a
|
cp a
|
||||||
ret
|
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
|
; Sets Z if string at (HL) is one character long
|
||||||
strIs1L:
|
strIs1L:
|
||||||
xor a
|
xor a
|
||||||
@ -98,32 +76,6 @@ strncmpI:
|
|||||||
; early, set otherwise)
|
; early, set otherwise)
|
||||||
ret
|
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
|
; 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.
|
; (advance HL and put a null char at the end of the string) and set Z.
|
||||||
; Otherwise, do nothing and reset Z.
|
; Otherwise, do nothing and reset Z.
|
||||||
@ -219,19 +171,3 @@ findStringInList:
|
|||||||
ret
|
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"
|
.inc "zasm/parse.asm"
|
||||||
.equ SYM_RAMSTART DIREC_LASTVAL+2
|
.equ SYM_RAMSTART DIREC_LASTVAL+2
|
||||||
.inc "zasm/symbol.asm"
|
.inc "zasm/symbol.asm"
|
||||||
.inc "zasm/expr.asm"
|
.equ EXPR_PARSE parseNumberOrSymbol
|
||||||
|
.inc "lib/expr.asm"
|
||||||
|
|
||||||
; Pretend that we aren't in first pass
|
; Pretend that we aren't in first pass
|
||||||
zasmIsFirstPass:
|
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 "core.asm"
|
||||||
.inc "str.asm"
|
.inc "str.asm"
|
||||||
|
.inc "lib/util.asm"
|
||||||
.inc "zasm/util.asm"
|
.inc "zasm/util.asm"
|
||||||
|
|
||||||
testNum: .db 1
|
testNum: .db 1
|
||||||
|
@ -7,8 +7,8 @@ jp runTests
|
|||||||
.inc "lib/util.asm"
|
.inc "lib/util.asm"
|
||||||
.inc "zasm/util.asm"
|
.inc "zasm/util.asm"
|
||||||
.inc "lib/parse.asm"
|
.inc "lib/parse.asm"
|
||||||
.inc "zasm/parse.asm"
|
.equ EXPR_PARSE parseLiteral
|
||||||
.inc "zasm/expr.asm"
|
.inc "lib/expr.asm"
|
||||||
.equ INS_RAMSTART RAMSTART
|
.equ INS_RAMSTART RAMSTART
|
||||||
.inc "zasm/instr.asm"
|
.inc "zasm/instr.asm"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user