; *** 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 the hex char at A and extract it's 0-15 numerical value. Put the result ; in A. ; ; On success, the carry flag is reset. On error, it is set. parseHex: ; First, let's see if we have an easy 0-9 case add a, 0xc6 ; maps '0'-'9' onto 0xf6-0xff sub 0xf6 ; maps to 0-9 and carries if not a digit ret nc and 0xdf ; converts lowercase to uppercase add a, 0xe9 ; map 0x11-x017 onto 0xFA - 0xFF sub 0xfa ; map onto 0-6 ret c ; we have an A-F digit add a, 10 ; C is clear, map back to 0xA-0xF 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 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