1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-26 10:18:05 +11:00

Compare commits

..

No commits in common. "dcb96aefe9654504bebae79c0f9a1f1a5d6edbe8" and "15628da7de8703e9e6d9873e25289c50ab65bb14" have entirely different histories.

13 changed files with 410 additions and 216 deletions

View File

@ -74,8 +74,6 @@ There are decimal, hexadecimal and binary literals. A "straight" number is
parsed as a decimal. Hexadecimal literals must be prefixed with `0x` (`0xf4`).
Binary must be prefixed with `0b` (`0b01100110`).
A decimal literal cannot start with `0`, with the exception of the `0` literal.
Decimals and hexadecimal are "flexible". Whether they're written in a byte or
a word, you don't need to prefix them with zeroes. Watch out for overflow,
however.

View File

@ -22,5 +22,6 @@ jp at28wMain
.inc "core.asm"
.inc "lib/util.asm"
.inc "lib/parse.asm"
.inc "lib/args.asm"
.inc "at28w/main.asm"
USER_RAMSTART:

View File

@ -11,15 +11,15 @@
; *** Code ***
at28wMain:
ld de, AT28W_MAXBYTES
ld a, (hl)
or a
jr z, at28wInner ; no arg
call parseHexadecimal ; --> DE
ld de, .argspecs
ld ix, AT28W_MAXBYTES
call parseArgs
jr z, at28wInner
; bad args
ld a, SHELL_ERR_BAD_ARGS
ret
.argspecs:
.db 0b111, 0b101, 0
at28wInner:
; Reminder: words in parseArgs aren't little endian. High byte is first.

111
apps/lib/args.asm Normal file
View File

@ -0,0 +1,111 @@
; *** Requirements ***
; lib/parse
;
; *** Consts ***
; maximum number of bytes to receive as args in all commands. Determines the
; size of the args variable.
.equ PARSE_ARG_MAXCOUNT 3
; *** Code ***
; Parse arguments at (HL) with specifiers at (DE) into (IX).
;
; Args specifiers are a series of flag for each arg:
; Bit 0 - arg present: if unset, we stop parsing there
; Bit 1 - is word: this arg is a word rather than a byte. Because our
; destination are bytes anyway, this doesn't change much except
; for whether we expect a space between the hex pairs. If set,
; you still need to have a specifier for the second part of
; the multibyte.
; Bit 2 - optional: If set and not present during parsing, we don't error out
; and write zero
;
; Bit 3 - String argument: If set, this argument is a string. A pointer to the
; read string, null terminated (max 0x20 chars) will
; be placed in the next two bytes. This has to be the
; last argument of the list and it stops parsing.
; Sets A to nonzero if there was an error during parsing, zero otherwise.
parseArgs:
push bc
push de
push hl
push ix
; init the arg value to a default 0
xor a
ld (ix), a
ld (ix+1), a
ld (ix+2), a
ld b, PARSE_ARG_MAXCOUNT
.loop:
ld a, (hl)
; is this the end of the line?
or a ; cp 0
jr z, .endofargs
; Get the specs
ld a, (de)
bit 0, a ; do we have an arg?
jr z, .error ; not set? then we have too many args
ld c, a ; save the specs for multibyte check later
bit 3, a ; is our arg a string?
jr z, .notAString
; our arg is a string. Let's place HL in our next two bytes and call
; it a day. Little endian, remember
ld (ix), l
ld (ix+1), h
jr .success ; directly to success: skip endofargs checks
.notAString:
call parseHexPair
jr c, .error
; we have a good arg and we need to write A in (IX).
ld (ix), a
; Good! increase counters
inc de
inc ix
inc hl ; get to following char (generally a space)
; Our arg is parsed, our pointers are increased. Normally, HL should
; point to a space *unless* our argspec indicates a multibyte arg.
bit 1, c
jr nz, .nospacecheck ; bit set? no space check
; do we have a proper space char (or null char)?
ld a, (hl)
or a
jr z, .endofargs
cp ' '
jr nz, .error
inc hl
.nospacecheck:
djnz .loop
; If we get here, it means that our next char *has* to be a null char
ld a, (hl)
or a ; cp 0
jr z, .success ; zero? great!
jr .error
.endofargs:
; We encountered our null char. Let's verify that we either have no
; more args or that they are optional
ld a, (de)
or a
jr z, .success ; no arg? success
bit 2, a
jr z, .error ; if unset, arg is not optional. error
; success
.success:
xor a
jr .end
.error:
inc a
.end:
pop ix
pop hl
pop de
pop bc
ret

View File

@ -22,6 +22,42 @@ parseHex:
ret
; Parses 2 characters of the string pointed to by HL and returns the numerical
; value in A. If the second character is a "special" character (<0x21) we don't
; error out: the result will be the one from the first char only.
; HL is set to point to the last char of the pair.
;
; On success, the carry flag is reset. On error, it is set.
parseHexPair:
push bc
ld a, (hl)
call parseHex
jr c, .end ; error? goto end, keeping the C flag on
rla \ rla \ rla \ rla ; let's push this in MSB
ld b, a
inc hl
ld a, (hl)
cp 0x21
jr c, .single ; special char? single digit
call parseHex
jr c, .end ; error?
or b ; join left-shifted + new. we're done!
; C flag was set on parseHex and is necessarily clear at this point
jr .end
.single:
; If we have a single digit, our result is already stored in B, but
; we have to right-shift it back.
ld a, b
and 0xf0
rra \ rra \ rra \ rra
dec hl
.end:
pop bc
ret
; Parse the decimal char at A and extract it's 0-9 numerical value. Put the
; result in A.
;
@ -104,45 +140,71 @@ parseDecimal:
pop hl ; <-- lvl 1, orig
jp unsetZ
; Parse string at (HL) as a hexadecimal value without the "0x" prefix and
; return value in DE.
; HL is advanced to the character following the last successfully read char.
; Sets Z on success.
; Parse string at (HL) as a hexadecimal value and return value in DE under the
; same conditions as parseLiteral.
parseHexadecimal:
ld a, (hl)
call parseHex
jp c, unsetZ ; we need at least one char
push bc
ld de, 0
ld b, 0
.loop:
; we push to B to verify overflow
rl e \ rl d \ rl b
rl e \ rl d \ rl b
rl e \ rl d \ rl b
rl e \ rl d \ rl b
or e
ld e, a
; did we overflow?
ld a, b
or a
jr nz, .end ; overflow, NZ already set
; next char
call hasHexPrefix
ret nz
push hl
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 nc, .loop
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:
pop bc
pop hl
ret
; Parse string at (HL) as a binary value (010101) without the "0b" prefix and
; return value in E. D is always zero.
; 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 E.
; D is always zero.
; Sets Z on success.
parseBinaryLiteral:
call hasBinPrefix
ret nz
push bc
push hl
ld d, 0
inc hl ; get rid of "0b"
inc hl
call strlen
or a
jr z, .error ; empty, error
@ -175,20 +237,17 @@ parseBinaryLiteral:
pop bc
ret
; Parses the string at (HL) and returns the 16-bit value in DE. 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:
ld de, 0 ; pre-fill
; Sets Z if (HL) has a '0b' prefix.
hasBinPrefix:
ld a, (hl)
cp 0x27 ; apostrophe
jr z, .char
cp '0'
jr z, .hexOrBin
jp parseDecimal
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 E. D is always zero.
@ -196,45 +255,43 @@ parseLiteral:
; 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.
.char:
parseCharLiteral:
ld a, 0x27 ; apostrophe (') char
cp (hl)
ret nz
push hl
inc hl
inc hl
cp (hl)
jr nz, .charEnd ; not ending with an apostrophe
jr nz, .end ; not ending with an apostrophe
inc hl
ld a, (hl)
or a ; cp 0
jr nz, .charEnd ; string has to end there
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 e, (hl)
ld a, (hl)
ld e, a
cp a ; ensure Z
.charEnd:
.end:
pop hl
ret
.hexOrBin:
inc hl
ld a, (hl)
inc hl ; already place it for hex or bin
cp 'x'
jr z, .hex
cp 'b'
jr z, .bin
; special case: single '0'. set Z if we hit have null terminating.
or a
.hexOrBinEnd:
dec hl \ dec hl ; replace HL
ret ; Z already set
.hex:
push hl
; Parses the string at (HL) and returns the 16-bit value in DE. 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
pop hl
jr .hexOrBinEnd
.bin:
ret z
call parseBinaryLiteral
jr .hexOrBinEnd
ret z
jp parseDecimal

View File

@ -5,31 +5,6 @@ isWS:
cp 0x09
ret
; Advance HL to next WS.
; Set Z if WS found, unset if end-of-string.
toWS:
ld a, (hl)
call isWS
ret z
or a
jp z, unsetZ
inc hl
jr toWS
; Consume following whitespaces in HL until a non-WS is hit.
; Set Z if non-WS found, unset if end-of-string.
rdWS:
ld a, (hl)
call isWS
jr nz, .ok
or a
jp z, unsetZ
inc hl
jr rdWS
.ok:
cp a ; ensure Z
ret
; Copy string from (HL) in (DE), that is, copy bytes until a null char is
; encountered. The null char is also copied.
; HL and DE point to the char right after the null char.

View File

@ -68,6 +68,7 @@ jp zasmMain
.inc "lib/util.asm"
.inc "lib/ari.asm"
.inc "lib/parse.asm"
.inc "lib/args.asm"
.inc "zasm/util.asm"
.equ IO_RAMSTART USER_RAMSTART
.inc "zasm/io.asm"

View File

@ -25,6 +25,7 @@ jp zasmMain
.inc "lib/util.asm"
.inc "lib/ari.asm"
.inc "lib/parse.asm"
.inc "lib/args.asm"
.inc "zasm/util.asm"
.equ IO_RAMSTART USER_RAMSTART
.inc "zasm/io.asm"

View File

@ -23,34 +23,34 @@
; HL is set to the last lineno to be read.
; Sets Z on success, unset on error. On error, A contains an error code (ERR_*)
zasmMain:
; Parse args in (HL)
; blkdev in
call parseHexadecimal ; --> DE
jr nz, .badargs
ld a, e
; Parse args. HL points to string already
; We don't allocate memory just to hold this. Because this happens
; before initialization, we don't really care where those args are
; parsed. That's why we borrow zasm's RAMSTART for a little while.
ld de, .argspecs
ld ix, ZASM_RAMSTART
call parseArgs
jr z, .goodargs
; bad args
ld hl, 0
ld de, 0
ld a, SHELL_ERR_BAD_ARGS
ret
.goodargs:
; HL now points to parsed args
; Init I/O
ld a, (ZASM_RAMSTART) ; blkdev in ID
ld de, IO_IN_BLK
call blkSel
; blkdev in
call rdWS
jr nz, .badargs
call parseHexadecimal ; --> DE
jr nz, .badargs
ld a, e
ld a, (ZASM_RAMSTART+1) ; blkdev out ID
ld de, IO_OUT_BLK
call blkSel
; .org high byte
ld e, 0 ; in case we .skipOrgSet
call rdWS
jr nz, .skipOrgSet ; no org argument
call parseHexadecimal ; --> DE
jr nz, .badargs
.skipOrgSet:
; Init .org with value of E
; Init .org
; This is the 3rd argument, optional, will be zero if not given.
; Save in "@" too
ld a, e
ld a, (ZASM_RAMSTART+2)
ld (ZASM_ORG+1), a ; high byte of .org
ld (DIREC_LASTVAL+1), a
xor a
@ -82,11 +82,8 @@ zasmMain:
.end:
jp ioLineNo ; --> HL, --> DE, returns
.badargs:
; bad args
ld a, SHELL_ERR_BAD_ARGS
ret
.argspecs:
.db 0b001, 0b001, 0b101
.sFirstPass:
.db "First pass", 0
.sSecondPass:

View File

@ -16,6 +16,7 @@
jp upcase
jp findchar
jp parseHex
jp parseHexPair
jp blkSel
jp blkSet
jp fsFindFN
@ -144,9 +145,9 @@ basFindCmdExtra:
jp basPgmHook
.mycmds:
.db "ed", 0
.dw 0x1f00
.dw 0x1e00
.db "zasm", 0
.dw 0x2400
.dw 0x2300
.db 0xff
f0GetB:
@ -165,13 +166,13 @@ f1PutB:
ld ix, FS_HANDLES+FS_HANDLE_SIZE
jp fsPutB
; last time I checked, PC at this point was 0x1e92. Let's give us a nice margin
; last time I checked, PC at this point was 0x1df8. Let's give us a nice margin
; for the start of ed.
.fill 0x1f00-$
.fill 0x1e00-$
.bin "ed.bin"
; Last check: 0x23b0
.fill 0x2400-$
; Last check: 0x22dd
.fill 0x2300-$
.bin "zasm.bin"
.fill 0x7ff0-$

View File

@ -14,6 +14,7 @@
.equ upcase @+3
.equ findchar @+3
.equ parseHex @+3
.equ parseHexPair @+3
.equ blkSel @+3
.equ blkSet @+3
.equ fsFindFN @+3

View File

@ -1,85 +0,0 @@
jp test
.inc "core.asm"
.inc "lib/util.asm"
.inc "lib/parse.asm"
zasmGetPC:
ret
testNum: .db 1
test:
ld sp, 0xffff
call testParseHex
call testParseHexadecimal
; success
xor a
halt
testParseHex:
ld a, '8'
call parseHex
jp c, fail
cp 8
jp nz, fail
call nexttest
ld a, 'e'
call parseHex
jp c, fail
cp 0xe
jp nz, fail
call nexttest
ld a, 'x'
call parseHex
jp nc, fail
call nexttest
ret
testParseHexadecimal:
ld hl, .s99
call parseHexadecimal
jp nz, fail
ld a, e
cp 0x99
jp nz, fail
call nexttest
ld hl, .saB
call parseHexadecimal
jp nz, fail
ld a, e
cp 0xab
jp nz, fail
call nexttest
; The string "Foo" will not cause a failure. We will parse up to "o"
; and then stop.
ld hl, .sFoo
call parseHexadecimal
jp nz, fail
ld a, e
cp 0xf
call nexttest
ret
.sFoo: .db "Foo", 0
.saB: .db "aB", 0
.s99: .db "99", 0
nexttest:
ld a, (testNum)
inc a
ld (testNum), a
ret
fail:
ld a, (testNum)
halt
; used as RAM
sandbox:

View File

@ -0,0 +1,136 @@
jp test
.inc "core.asm"
.inc "lib/util.asm"
.inc "lib/parse.asm"
.inc "lib/args.asm"
zasmGetPC:
ret
testNum: .db 1
test:
ld hl, 0xffff
ld sp, hl
call testParseHex
call testParseHexPair
call testParseArgs
; success
xor a
halt
testParseHex:
ld a, '8'
call parseHex
jp c, fail
cp 8
jp nz, fail
call nexttest
ld a, 'e'
call parseHex
jp c, fail
cp 0xe
jp nz, fail
call nexttest
ld a, 'x'
call parseHex
jp nc, fail
call nexttest
ret
testParseHexPair:
ld hl, .s99
call parseHexPair
jp c, fail
cp 0x99
jp nz, fail
call nexttest
ld hl, .saB
call parseHexPair
jp c, fail
cp 0xab
jp nz, fail
call nexttest
ld hl, .sFoo
call parseHexPair
jp nc, fail
call nexttest
ret
.sFoo: .db "Foo", 0
.saB: .db "aB", 0
.s99: .db "99", 0
testParseArgs:
ld hl, .t1+6
ld de, .t1
ld iy, .t1+3
call .testargs
ld hl, .t2+6
ld de, .t2
ld iy, .t2+3
call .testargs
ld hl, .t3+6
ld de, .t3
ld iy, .t3+3
call .testargs
ret
; HL and DE must be set, and IY must point to expected results in IX
.testargs:
ld ix, sandbox
call parseArgs
jp nz, fail
ld a, (ix)
cp (iy)
jp nz, fail
ld a, (ix+1)
cp (iy+1)
jp nz, fail
ld a, (ix+2)
cp (iy+2)
jp nz, fail
jp nexttest
; Test data format: 3 bytes specs, 3 bytes expected (IX), then the arg string.
; Empty args with empty specs
.t1:
.db 0b0000, 0b0000, 0b0000
.db 0, 0, 0
.db 0
; One arg, one byte spec
.t2:
.db 0b0001, 0b0000, 0b0000
.db 0xe4, 0, 0
.db "e4", 0
; 3 args, 3 bytes spec
.t3:
.db 0b0001, 0b0001, 0b0001
.db 0xe4, 0xab, 0x99
.db "e4 ab 99", 0
nexttest:
ld a, (testNum)
inc a
ld (testNum), a
ret
fail:
ld a, (testNum)
halt
; used as RAM
sandbox: