shell: implement seek and peek

This introduces `core.asm` which includes routines used by other parts.
This commit is contained in:
Virgil Dupras 2019-04-14 11:11:13 -04:00
parent 9580cc3994
commit 21168f6c4e
3 changed files with 295 additions and 87 deletions

193
parts/core.asm Normal file
View File

@ -0,0 +1,193 @@
; core
;
; Routines used by pretty much all parts. You will want to include it first
; in your glue file.
; *** CONSTS ***
ASCII_CR .equ 0x0d
ASCII_LF .equ 0x0a
; *** CODE ***
; add the value of A into DE
addDE:
add a, e
jr nc, .end ; no carry? skip inc
inc d
.end:
ld e, a
ret
; Format the lower nibble of A into a hex char and stores the result in A.
fmtHex:
and a, 0xf
cp 10
jr nc, .alpha ; if >= 10, we have alpha
add a, '0'
ret
.alpha:
add a, 'A'-10
ret
; Formats value in A into a string hex pair. Stores it in the memory location
; that HL points to. Does *not* add a null char at the end.
fmtHexPair:
push af
; let's start with the rightmost char
inc hl
call fmtHex
ld (hl), a
; and now with the leftmost
dec hl
pop af
push af
and a, 0xf0
rra \ rra \ rra \ rra
call fmtHex
ld (hl), a
pop af
ret
; jump to the location pointed to by HL. This allows us to call HL instead of
; just jumping it.
jumpHL:
jp hl
ret
; 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
cp '0'
jr c, .error ; if < '0', we have a problem
cp '9'+1
jr nc, .alpha ; if >= '9'+1, we might have alpha
; We are in the 0-9 range
sub a, '0' ; C is clear
ret
.alpha:
call upcase
cp 'A'
jr c, .error ; if < 'A', we have a problem
cp 'F'+1
jr nc, .error ; if >= 'F', we have a problem
; We have alpha.
sub a, 'A'-10 ; C is clear
ret
.error:
scf
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.
;
; On success, the carry flag is reset. On error, it is set.
parseHexPair:
push bc
push hl
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 a, 0xf0
rra \ rra \ rra \ rra
.end:
pop hl
pop bc
ret
; print null-terminated string pointed to by HL
printstr:
push af
push hl
.loop:
ld a, (hl) ; load character to send
or a ; is it zero?
jr z, .end ; if yes, we're finished
call aciaPutC
inc hl
jr .loop
.end:
pop hl
pop af
ret
; print A characters from string that HL points to
printnstr:
push bc
push hl
ld b, a
.loop:
ld a, (hl) ; load character to send
call aciaPutC
inc hl
djnz .loop
.end:
pop hl
pop bc
ret
; Compares strings pointed to by HL and DE up to A count of characters. If
; equal, Z is set. If not equal, Z is reset.
strncmp:
push bc
push hl
push de
ld b, a
.loop:
ld a, (de)
cp (hl)
jr nz, .end ; not equal? break early
inc hl
inc de
djnz .loop
.end:
pop de
pop hl
pop bc
; 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
; Transforms the character in A, if it's in the a-z range, into its upcase
; version.
upcase:
cp 'a'
ret c ; A < 'a'. nothing to do
cp 'z'+1
ret nc ; A >= 'z'+1. nothing to do
; 'a' - 'A' == 0x20
sub 0x20
ret

View File

@ -13,8 +13,6 @@
; See constants below for error codes. ; See constants below for error codes.
; *** CONSTS *** ; *** CONSTS ***
CR .equ 0x0d
LF .equ 0x0a
; number of entries in shellCmdTbl ; number of entries in shellCmdTbl
SHELL_CMD_COUNT .equ 2 SHELL_CMD_COUNT .equ 2
@ -25,8 +23,19 @@ SHELL_ERR_UNKNOWN_CMD .equ 0x01
; Arguments for the command weren't properly formatted ; Arguments for the command weren't properly formatted
SHELL_ERR_BAD_ARGS .equ 0x02 SHELL_ERR_BAD_ARGS .equ 0x02
; *** VARIABLES ***
; Memory address that the shell is currently "pointing at" for peek and deek
; operations. Set with seek.
SHELL_MEM_PTR .equ SHELL_RAMSTART
; Used to store formatted hex values just before printing it.
SHELL_HEX_FMT .equ SHELL_MEM_PTR+2
SHELL_RAMEND .equ SHELL_HEX_FMT+2
; *** CODE *** ; *** CODE ***
shellInit: shellInit:
xor a
ld (SHELL_MEM_PTR), a
; print prompt ; print prompt
ld hl, .prompt ld hl, .prompt
call printstr call printstr
@ -40,32 +49,14 @@ shellLoop:
call chkbuf call chkbuf
jr shellLoop jr shellLoop
; print null-terminated string pointed to by HL
printstr:
push af
push hl
.loop:
ld a, (hl) ; load character to send
or a ; is it zero?
jr z, .end ; if yes, we're finished
call aciaPutC
inc hl
jr .loop
.end:
pop hl
pop af
ret
printcrlf: printcrlf:
ld a, CR ld a, ASCII_CR
call aciaPutC call aciaPutC
ld a, LF ld a, ASCII_LF
call aciaPutC call aciaPutC
ret ret
; check if the input buffer is full or ends in CR or LF. If it does, prints it ; check if the input buffer is full or ends in CR or LF. If it does, prints it
; back and empty it. ; back and empty it.
chkbuf: chkbuf:
@ -81,9 +72,9 @@ chkbuf:
ld a, (hl) ; now, that's our char we have in A ld a, (hl) ; now, that's our char we have in A
inc hl ; put HL back where it was inc hl ; put HL back where it was
cp CR cp ASCII_CR
jr z, .do ; char is CR? do! jr z, .do ; char is CR? do!
cp LF cp ASCII_LF
jr z, .do ; char is LF? do! jr z, .do ; char is LF? do!
; nothing matched? don't do anything ; nothing matched? don't do anything
@ -101,51 +92,12 @@ chkbuf:
call shellParse call shellParse
ret ret
; Compares strings pointed to by HL and DE up to A count of characters. If
; equal, Z is set. If not equal, Z is reset.
strncmp:
push bc
push hl
push de
ld b, a
.loop:
ld a, (de)
cp (hl)
jr nz, .end ; not equal? break early
inc hl
inc de
djnz .loop
.end:
pop de
pop hl
pop bc
; 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
; add the value of A into DE
addDE:
add a, e
jr nc, .end ; no carry? skip inc
inc d
.end:
ld e, a
ret
; jump to the location pointed to by HL. This allows us to call HL instead of
; just jumping it.
jumpHL:
jp hl
ret
; Parse command (null terminated) at HL and calls it ; Parse command (null terminated) at HL and calls it
shellParse: shellParse:
push af push af
push bc push bc
push de push de
push hl
ld de, shellCmdTbl ld de, shellCmdTbl
ld a, SHELL_CMD_COUNT ld a, SHELL_CMD_COUNT
@ -165,19 +117,34 @@ shellParse:
jr .end jr .end
.found: .found:
; all right, we're almost ready to call the cmd. Let's just have DE
; point to the cmd jump line.
ld a, 4 ld a, 4
call addDE call addDE
ex hl, de ; Now, let's swap HL and DE because, welll because that's how we're set.
ex hl, de ; HL = jump line, 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, HL points to jump line. Ready to roll!
call jumpHL call jumpHL
ex hl, de
.end: .end:
pop hl
pop de pop de
pop bc pop bc
pop af pop af
ret ret
; Print the error code set in A (doesn't work for codes > 9 yet...) ; Print the error code set in A (in hex)
shellPrintErr: shellPrintErr:
push af push af
push hl push hl
@ -185,9 +152,10 @@ shellPrintErr:
ld hl, .str ld hl, .str
call printstr call printstr
; ascii for '0' is 0x30 ld hl, SHELL_HEX_FMT
add a, 0x30 call fmtHexPair
call aciaPutC ld a, 2
call printnstr
call printcrlf call printcrlf
pop hl pop hl
@ -198,19 +166,66 @@ shellPrintErr:
.db "ERR ", 0 .db "ERR ", 0
; *** COMMANDS *** ; *** COMMANDS ***
shellSeek: ; When these commands are called, DE points to the first character of the
ld hl, .str ; command args.
call printstr
ret
.str:
.db "seek called", CR, LF, 0
shellPeek: ; Set memory pointer to the specified address.
ld hl, .str ; Example: seek 01fe
call printstr
shellSeek:
push de
push hl
ex de, hl
call parseHexPair
jr c, .error
ld (SHELL_MEM_PTR), a
inc hl
inc hl
call parseHexPair
jr c, .error
ld (SHELL_MEM_PTR+1), a
jr .success
.error:
ld a, SHELL_ERR_BAD_ARGS
call shellPrintErr
jr .end
.success:
ld a, (SHELL_MEM_PTR)
ld hl, SHELL_HEX_FMT
call fmtHexPair
ld a, 2
call printnstr
ld a, (SHELL_MEM_PTR+1)
call fmtHexPair
ld a, 2
call printnstr
call printcrlf
.end:
pop hl
pop de
ret
; peek byte where memory pointer points to aby display its value
shellPeek:
push af
push hl
ld hl, (SHELL_MEM_PTR)
ld a, (hl)
ld hl, SHELL_HEX_FMT
call fmtHexPair
ld a, 2
call printnstr
call printcrlf
pop hl
pop af
ret ret
.str:
.db "peek called", CR, LF, 0
; Format: 4 bytes name followed by 2 bytes jump. fill names with zeroes ; Format: 4 bytes name followed by 2 bytes jump. fill names with zeroes
shellCmdTbl: shellCmdTbl:

View File

@ -47,7 +47,7 @@ This is what your glue code would look like:
jr init jr init
.fill 0x38-$ .fill 0x38-$
jr aciaInt jp aciaInt
init: init:
di di
@ -63,8 +63,10 @@ init:
ei ei
call shellLoop call shellLoop
#include "core.asm"
ACIA_RAMSTART .equ RAMSTART ACIA_RAMSTART .equ RAMSTART
#include "acia.asm" #include "acia.asm"
SHELL_RAMSTART .equ ACIA_RAMEND
#include "shell.asm" #include "shell.asm"
``` ```
@ -83,11 +85,9 @@ We set interrupt mode to 1 because that's what `acia.asm` is written around.
Then, we init ACIA, shell, enable interrupt and give control of the main loop Then, we init ACIA, shell, enable interrupt and give control of the main loop
to `shell.asm`. to `shell.asm`.
What comes below is actual code include from the acia and shell modules. As you What comes below is actual code include from parts we want to include in our
can see, we need to tell each module where to put their variables. `shell.asm` OS. As you can see, we need to tell each module where to put their variables.
doesn't have variables, but if it did, we would have a `SHELL_RAMSTART .equ See `parts/README.md` for details.
ACIA_RAMEND` just below the `acia.asm` include. `ACIA_RAMEND` is defined in
`acia.asm`.
### Build the image ### Build the image