shell: add shellParseArgs
Replaces individual command parsing. Quite a big and complex chunk of code, but makes each command much simpler.
This commit is contained in:
parent
83f63c7146
commit
26b125b337
@ -18,6 +18,15 @@ addDE:
|
|||||||
ld e, a
|
ld e, a
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
; add the value of A into HL
|
||||||
|
addHL:
|
||||||
|
add a, l
|
||||||
|
jr nc, .end ; no carry? skip inc
|
||||||
|
inc h
|
||||||
|
.end:
|
||||||
|
ld l, a
|
||||||
|
ret
|
||||||
|
|
||||||
; jump to the location pointed to by IX. This allows us to call IX instead of
|
; jump to the location pointed to by IX. This allows us to call IX instead of
|
||||||
; just jumping it. We use IX because we never use this for arguments.
|
; just jumping it. We use IX because we never use this for arguments.
|
||||||
callIX:
|
callIX:
|
||||||
@ -108,11 +117,11 @@ parseHex:
|
|||||||
; Parses 2 characters of the string pointed to by HL and returns the numerical
|
; 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
|
; 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.
|
; 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.
|
; On success, the carry flag is reset. On error, it is set.
|
||||||
parseHexPair:
|
parseHexPair:
|
||||||
push bc
|
push bc
|
||||||
push hl
|
|
||||||
|
|
||||||
ld a, (hl)
|
ld a, (hl)
|
||||||
call parseHex
|
call parseHex
|
||||||
@ -135,36 +144,12 @@ parseHexPair:
|
|||||||
ld a, b
|
ld a, b
|
||||||
and a, 0xf0
|
and a, 0xf0
|
||||||
rra \ rra \ rra \ rra
|
rra \ rra \ rra \ rra
|
||||||
|
dec hl
|
||||||
|
|
||||||
.end:
|
.end:
|
||||||
pop hl
|
|
||||||
pop bc
|
pop bc
|
||||||
ret
|
ret
|
||||||
|
|
||||||
; Parse a series of A hex pairs from (HL) and put the result in (DE)
|
|
||||||
parseHexChain:
|
|
||||||
push af
|
|
||||||
push bc
|
|
||||||
push de
|
|
||||||
push hl
|
|
||||||
|
|
||||||
ld b, a
|
|
||||||
.loop:
|
|
||||||
call parseHexPair
|
|
||||||
jr c, .end ; error?
|
|
||||||
ld (de), a
|
|
||||||
inc hl
|
|
||||||
inc hl
|
|
||||||
inc de
|
|
||||||
djnz .loop
|
|
||||||
|
|
||||||
.end:
|
|
||||||
pop hl
|
|
||||||
pop de
|
|
||||||
pop bc
|
|
||||||
pop af
|
|
||||||
ret
|
|
||||||
|
|
||||||
; print null-terminated string pointed to by HL
|
; print null-terminated string pointed to by HL
|
||||||
printstr:
|
printstr:
|
||||||
push af
|
push af
|
||||||
@ -232,21 +217,6 @@ strncmp:
|
|||||||
; early, set otherwise)
|
; early, set otherwise)
|
||||||
ret
|
ret
|
||||||
|
|
||||||
; Swap the two bytes at (HL)
|
|
||||||
swapBytes:
|
|
||||||
push af
|
|
||||||
ld a, (hl)
|
|
||||||
ex af, af'
|
|
||||||
inc hl
|
|
||||||
ld a, (hl)
|
|
||||||
ex af, af'
|
|
||||||
ld (hl), a
|
|
||||||
dec hl
|
|
||||||
ex af, af'
|
|
||||||
ld (hl), a
|
|
||||||
pop af
|
|
||||||
ret
|
|
||||||
|
|
||||||
; Transforms the character in A, if it's in the a-z range, into its upcase
|
; Transforms the character in A, if it's in the a-z range, into its upcase
|
||||||
; version.
|
; version.
|
||||||
upcase:
|
upcase:
|
||||||
|
282
parts/shell.asm
282
parts/shell.asm
@ -23,6 +23,10 @@
|
|||||||
; number of entries in shellCmdTbl
|
; number of entries in shellCmdTbl
|
||||||
SHELL_CMD_COUNT .equ 4
|
SHELL_CMD_COUNT .equ 4
|
||||||
|
|
||||||
|
; maximum number of bytes to receive as args in all commands. Determines the
|
||||||
|
; size of the args variable.
|
||||||
|
SHELL_CMD_ARGS_MAXSIZE .equ 3
|
||||||
|
|
||||||
; The command that was type isn't known to the shell
|
; The command that was type isn't known to the shell
|
||||||
SHELL_ERR_UNKNOWN_CMD .equ 0x01
|
SHELL_ERR_UNKNOWN_CMD .equ 0x01
|
||||||
|
|
||||||
@ -34,16 +38,20 @@ SHELL_ERR_BAD_ARGS .equ 0x02
|
|||||||
SHELL_BUFSIZE .equ 0x20
|
SHELL_BUFSIZE .equ 0x20
|
||||||
|
|
||||||
; *** VARIABLES ***
|
; *** VARIABLES ***
|
||||||
; Memory address that the shell is currently "pointing at" for peek and deek
|
; Memory address that the shell is currently "pointing at" for peek, load, call
|
||||||
; operations. Set with seek.
|
; operations. Set with seek.
|
||||||
SHELL_MEM_PTR .equ SHELL_RAMSTART
|
SHELL_MEM_PTR .equ SHELL_RAMSTART
|
||||||
; Used to store formatted hex values just before printing it.
|
; Used to store formatted hex values just before printing it.
|
||||||
SHELL_HEX_FMT .equ SHELL_MEM_PTR+2
|
SHELL_HEX_FMT .equ SHELL_MEM_PTR+2
|
||||||
|
|
||||||
|
; Places where we store arguments specifiers and where resulting values are
|
||||||
|
; written to after parsing.
|
||||||
|
SHELL_CMD_ARGS .equ SHELL_HEX_FMT+2
|
||||||
|
|
||||||
; Command buffer. We read types chars into this buffer until return is pressed
|
; Command buffer. We read types chars into this buffer until return is pressed
|
||||||
; This buffer is null-terminated and we don't keep an index around: we look
|
; This buffer is null-terminated and we don't keep an index around: we look
|
||||||
; for the null-termination every time we write to it. Simpler that way.
|
; for the null-termination every time we write to it. Simpler that way.
|
||||||
SHELL_BUF .equ SHELL_HEX_FMT+2
|
SHELL_BUF .equ SHELL_CMD_ARGS+SHELL_CMD_ARGS_MAXSIZE
|
||||||
|
|
||||||
SHELL_RAMEND .equ SHELL_BUF+SHELL_BUFSIZE
|
SHELL_RAMEND .equ SHELL_BUF+SHELL_BUFSIZE
|
||||||
|
|
||||||
@ -132,7 +140,7 @@ shellParse:
|
|||||||
ld a, 4 ; 4 chars to compare
|
ld a, 4 ; 4 chars to compare
|
||||||
call strncmp
|
call strncmp
|
||||||
jr z, .found
|
jr z, .found
|
||||||
ld a, 7
|
ld a, 7 + SHELL_CMD_ARGS_MAXSIZE
|
||||||
call addDE
|
call addDE
|
||||||
djnz .loop
|
djnz .loop
|
||||||
|
|
||||||
@ -142,28 +150,31 @@ shellParse:
|
|||||||
jr .end
|
jr .end
|
||||||
|
|
||||||
.found:
|
.found:
|
||||||
; all right, we're almost ready to call the cmd. Let's just have DE
|
; we found our command. Now, let's parse our args.
|
||||||
; point to the cmd jump line.
|
|
||||||
ld a, 4
|
ld a, 4
|
||||||
|
call addHL
|
||||||
|
; Now, let's have DE point to the argspecs
|
||||||
|
ld a, 4
|
||||||
|
call addDE
|
||||||
|
; We're ready to parse args
|
||||||
|
call shellParseArgs
|
||||||
|
cp 0
|
||||||
|
jr nz, .parseerror
|
||||||
|
|
||||||
|
ld hl, SHELL_CMD_ARGS
|
||||||
|
; Args parsed, now we can load the routine address and call it.
|
||||||
|
; let's have DE point to the jump line
|
||||||
|
ld a, SHELL_CMD_ARGS_MAXSIZE
|
||||||
call addDE
|
call addDE
|
||||||
ld ixh, d
|
ld ixh, d
|
||||||
ld ixl, e
|
ld ixl, e
|
||||||
; Now, let's swap HL and DE because, well because that's how we're set.
|
; Ready to roll!
|
||||||
ex hl, de ; DE = cmd str pointer
|
|
||||||
|
|
||||||
; Before we call our command, we want to set up the pointer to the arg
|
|
||||||
; list. Normally, it's DE+5 (DE+4 is the space) unless DE+4 is null,
|
|
||||||
; which means no arg.
|
|
||||||
ld a, 4
|
|
||||||
call addDE
|
|
||||||
ld a, (DE)
|
|
||||||
cp 0
|
|
||||||
jr z, .noarg ; char is null? we have no arg
|
|
||||||
inc de
|
|
||||||
.noarg:
|
|
||||||
; DE points to args, IX points to jump line. Ready to roll!
|
|
||||||
call callIX
|
call callIX
|
||||||
|
jr .end
|
||||||
|
|
||||||
|
.parseerror:
|
||||||
|
ld a, SHELL_ERR_BAD_ARGS
|
||||||
|
call shellPrintErr
|
||||||
.end:
|
.end:
|
||||||
pop ix
|
pop ix
|
||||||
pop hl
|
pop hl
|
||||||
@ -193,9 +204,99 @@ shellPrintErr:
|
|||||||
.str:
|
.str:
|
||||||
.db "ERR ", 0
|
.db "ERR ", 0
|
||||||
|
|
||||||
|
; Parse arguments at (HL) with specifiers at (DE) into (SHELL_CMD_ARGS).
|
||||||
|
; (HL) should point to the character *just* after the name of the command
|
||||||
|
; because we verify, in the case that we have args, that we have a space there.
|
||||||
|
;
|
||||||
|
; 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
|
||||||
|
;
|
||||||
|
; Sets A to nonzero if there was an error during parsing, zero otherwise.
|
||||||
|
; If there was an error during parsing, carry is set.
|
||||||
|
shellParseArgs:
|
||||||
|
push bc
|
||||||
|
push de
|
||||||
|
push hl
|
||||||
|
push ix
|
||||||
|
|
||||||
|
ld ix, SHELL_CMD_ARGS
|
||||||
|
ld a, SHELL_CMD_ARGS_MAXSIZE
|
||||||
|
ld b, a
|
||||||
|
xor c
|
||||||
|
.loop:
|
||||||
|
; init the arg value to a default 0
|
||||||
|
xor a
|
||||||
|
ld (ix), a
|
||||||
|
|
||||||
|
ld a, (hl)
|
||||||
|
; is this the end of the line?
|
||||||
|
cp 0
|
||||||
|
jr z, .endofargs
|
||||||
|
|
||||||
|
; do we have a proper space char?
|
||||||
|
cp ' '
|
||||||
|
jr z, .hasspace ; We're fine
|
||||||
|
|
||||||
|
; is our previous arg a multibyte? (argspec still in C)
|
||||||
|
bit 1, c
|
||||||
|
jr z, .error ; bit not set? error
|
||||||
|
dec hl ; offset the "inc hl" below
|
||||||
|
|
||||||
|
.hasspace:
|
||||||
|
; 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 the next loop
|
||||||
|
inc hl ; (hl) points to a space, go next
|
||||||
|
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)
|
||||||
|
djnz .loop
|
||||||
|
; If we get here, it means that our next char *has* to be a null char
|
||||||
|
ld a, (hl)
|
||||||
|
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)
|
||||||
|
cp 0
|
||||||
|
jr z, .success ; no arg? success
|
||||||
|
bit 2, a
|
||||||
|
jr nz, .success ; if set, arg is optional. success
|
||||||
|
jr .error
|
||||||
|
|
||||||
|
.success:
|
||||||
|
xor a
|
||||||
|
jr .end
|
||||||
|
.error:
|
||||||
|
inc a
|
||||||
|
.end:
|
||||||
|
pop ix
|
||||||
|
pop hl
|
||||||
|
pop de
|
||||||
|
pop bc
|
||||||
|
ret
|
||||||
|
|
||||||
; *** COMMANDS ***
|
; *** COMMANDS ***
|
||||||
; When these commands are called, DE points to the first character of the
|
; When these commands are called, HL points to the first byte of the
|
||||||
; command args.
|
; parsed command args.
|
||||||
|
|
||||||
; Set memory pointer to the specified address (word).
|
; Set memory pointer to the specified address (word).
|
||||||
; Example: seek 01fe
|
; Example: seek 01fe
|
||||||
@ -205,35 +306,26 @@ shellSeek:
|
|||||||
push de
|
push de
|
||||||
push hl
|
push hl
|
||||||
|
|
||||||
ex de, hl
|
|
||||||
ld de, SHELL_MEM_PTR
|
|
||||||
ld a, 2
|
|
||||||
call parseHexChain
|
|
||||||
jr c, .error
|
|
||||||
; z80 is little endian. in a "ld hl, (nn)" op, L is loaded from the
|
; z80 is little endian. in a "ld hl, (nn)" op, L is loaded from the
|
||||||
; first byte, H is loaded from the second. We have to swap our result.
|
; first byte, H is loaded from the second.
|
||||||
ld hl, SHELL_MEM_PTR
|
ld a, (hl)
|
||||||
call swapBytes
|
ld (SHELL_MEM_PTR+1), a
|
||||||
jr .success
|
inc hl
|
||||||
|
ld a, (hl)
|
||||||
|
ld (SHELL_MEM_PTR), a
|
||||||
|
|
||||||
.error:
|
ld de, (SHELL_MEM_PTR)
|
||||||
ld a, SHELL_ERR_BAD_ARGS
|
|
||||||
call shellPrintErr
|
|
||||||
jr .end
|
|
||||||
|
|
||||||
.success:
|
|
||||||
ld a, (SHELL_MEM_PTR+1)
|
|
||||||
ld hl, SHELL_HEX_FMT
|
ld hl, SHELL_HEX_FMT
|
||||||
|
ld a, d
|
||||||
call fmtHexPair
|
call fmtHexPair
|
||||||
ld a, 2
|
ld a, 2
|
||||||
call printnstr
|
call printnstr
|
||||||
ld a, (SHELL_MEM_PTR)
|
ld a, e
|
||||||
call fmtHexPair
|
call fmtHexPair
|
||||||
ld a, 2
|
ld a, 2
|
||||||
call printnstr
|
call printnstr
|
||||||
call printcrlf
|
call printcrlf
|
||||||
|
|
||||||
.end:
|
|
||||||
pop hl
|
pop hl
|
||||||
pop de
|
pop de
|
||||||
pop af
|
pop af
|
||||||
@ -250,25 +342,12 @@ shellPeek:
|
|||||||
push de
|
push de
|
||||||
push hl
|
push hl
|
||||||
|
|
||||||
ld b, 1 ; by default, we run the loop once
|
ld a, (hl)
|
||||||
ld a, (de)
|
|
||||||
cp 0
|
cp 0
|
||||||
jr z, .success ; no arg? don't try to parse
|
jr nz, .arg1isset ; if arg1 is set, no need for a default
|
||||||
|
ld a, 1 ; default for arg1
|
||||||
ex de, hl
|
.arg1isset:
|
||||||
call parseHexPair
|
ld b, a
|
||||||
jr c, .error
|
|
||||||
cp 0
|
|
||||||
jr z, .error ; zero isn't a good arg, error
|
|
||||||
ld b, a ; loop the number of times specified in arg
|
|
||||||
jr .success
|
|
||||||
|
|
||||||
.error:
|
|
||||||
ld a, SHELL_ERR_BAD_ARGS
|
|
||||||
call shellPrintErr
|
|
||||||
jr .end
|
|
||||||
|
|
||||||
.success:
|
|
||||||
ld hl, (SHELL_MEM_PTR)
|
ld hl, (SHELL_MEM_PTR)
|
||||||
.loop: ld a, (hl)
|
.loop: ld a, (hl)
|
||||||
ex hl, de
|
ex hl, de
|
||||||
@ -300,17 +379,7 @@ shellLoad:
|
|||||||
push bc
|
push bc
|
||||||
push hl
|
push hl
|
||||||
|
|
||||||
ld a, (de)
|
ld a, (hl)
|
||||||
call parseHex
|
|
||||||
jr c, .error
|
|
||||||
jr .success
|
|
||||||
|
|
||||||
.error:
|
|
||||||
ld a, SHELL_ERR_BAD_ARGS
|
|
||||||
call shellPrintErr
|
|
||||||
jr .end
|
|
||||||
|
|
||||||
.success:
|
|
||||||
ld b, a
|
ld b, a
|
||||||
ld hl, (SHELL_MEM_PTR)
|
ld hl, (SHELL_MEM_PTR)
|
||||||
.loop: SHELL_GETC
|
.loop: SHELL_GETC
|
||||||
@ -330,80 +399,47 @@ shellLoad:
|
|||||||
; Example: run 42 cafe
|
; Example: run 42 cafe
|
||||||
shellCall:
|
shellCall:
|
||||||
push af
|
push af
|
||||||
push de
|
|
||||||
push hl
|
push hl
|
||||||
|
push ix
|
||||||
|
|
||||||
ld a, (de)
|
|
||||||
cp 0
|
|
||||||
jr z, .defA ; no arg? don't try to parse
|
|
||||||
call parseHex
|
|
||||||
jr c, .error
|
|
||||||
; We have a proper A arg, in A. We push it for later use, just before
|
|
||||||
; the actual call.
|
|
||||||
push af
|
|
||||||
|
|
||||||
; Let's try DE parsing now
|
|
||||||
inc de
|
|
||||||
inc de
|
|
||||||
ld a, (de)
|
|
||||||
cp 0
|
|
||||||
jr z, .defDE ; no arg? don't try to parse
|
|
||||||
inc de ; we're on a space (maybe...) we parse the
|
|
||||||
; next char
|
|
||||||
ex hl, de ; we need HL to point to our hex str for
|
|
||||||
; parseHexChain
|
|
||||||
; We need a tmp 2 bytes space for our result. Let's use SHELL_HEX_FMT.
|
|
||||||
ld de, SHELL_HEX_FMT
|
|
||||||
ld a, 2
|
|
||||||
call parseHexChain
|
|
||||||
jr c, .error
|
|
||||||
ex hl, de ; result is in DE, we need it in HL
|
|
||||||
call swapBytes
|
|
||||||
; Alright, we have a proper address in (SHELL_HEX_FMT), let's push it
|
|
||||||
; to the stack
|
|
||||||
ld hl, (SHELL_HEX_FMT)
|
|
||||||
push hl
|
|
||||||
jr .success
|
|
||||||
|
|
||||||
.error:
|
|
||||||
ld a, SHELL_ERR_BAD_ARGS
|
|
||||||
call shellPrintErr
|
|
||||||
jr .end
|
|
||||||
|
|
||||||
.defA:
|
|
||||||
xor a
|
|
||||||
push af
|
|
||||||
.defDE:
|
|
||||||
ld hl, 0
|
|
||||||
push hl
|
|
||||||
.success:
|
|
||||||
; Let's recap here. At this point, we have:
|
; Let's recap here. At this point, we have:
|
||||||
; 1. The address we want to execute in (SHELL_MEM_PTR)
|
; 1. The address we want to execute in (SHELL_MEM_PTR)
|
||||||
; 2. our HL arg on top of the stack
|
; 2. our A arg as the first byte of (HL)
|
||||||
; 3. our A arg underneath.
|
; 2. our HL arg as (HL+1) and (HL+2)
|
||||||
; Ready, set, go!
|
; Ready, set, go!
|
||||||
ld a, (SHELL_MEM_PTR)
|
ld a, (SHELL_MEM_PTR)
|
||||||
ld ixl, a
|
ld ixl, a
|
||||||
ld a, (SHELL_MEM_PTR+1)
|
ld a, (SHELL_MEM_PTR+1)
|
||||||
ld ixh, a
|
ld ixh, a
|
||||||
pop hl
|
ld a, (hl)
|
||||||
pop af
|
ex af, af'
|
||||||
|
inc hl
|
||||||
|
ld a, (hl)
|
||||||
|
exx
|
||||||
|
ld h, a
|
||||||
|
exx
|
||||||
|
inc hl
|
||||||
|
ld a, (hl)
|
||||||
|
exx
|
||||||
|
ld l, a
|
||||||
|
ex af, af'
|
||||||
call callIX
|
call callIX
|
||||||
|
|
||||||
.end:
|
.end:
|
||||||
|
pop ix
|
||||||
pop hl
|
pop hl
|
||||||
pop de
|
|
||||||
pop af
|
pop af
|
||||||
ret
|
ret
|
||||||
|
|
||||||
; Format: 4 bytes name followed by 3 bytes jump. fill names with zeroes
|
; Format: 4 bytes name followed by SHELL_CMD_ARGS_MAXSIZE bytes specifiers,
|
||||||
|
; followed by 3 bytes jump. fill names with zeroes
|
||||||
shellCmdTbl:
|
shellCmdTbl:
|
||||||
.db "seek"
|
.db "seek", 0b011, 0b001, 0
|
||||||
jp shellSeek
|
jp shellSeek
|
||||||
.db "peek"
|
.db "peek", 0b101, 0, 0
|
||||||
jp shellPeek
|
jp shellPeek
|
||||||
.db "load"
|
.db "load", 0b001, 0, 0
|
||||||
jp shellLoad
|
jp shellLoad
|
||||||
.db "call"
|
.db "call", 0b101, 0b111, 0b001
|
||||||
jp shellCall
|
jp shellCall
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user