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.
; *** CONSTS ***
CR .equ 0x0d
LF .equ 0x0a
; number of entries in shellCmdTbl
SHELL_CMD_COUNT .equ 2
@ -25,8 +23,19 @@ SHELL_ERR_UNKNOWN_CMD .equ 0x01
; Arguments for the command weren't properly formatted
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 ***
shellInit:
xor a
ld (SHELL_MEM_PTR), a
; print prompt
ld hl, .prompt
call printstr
@ -40,32 +49,14 @@ shellLoop:
call chkbuf
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:
ld a, CR
ld a, ASCII_CR
call aciaPutC
ld a, LF
ld a, ASCII_LF
call aciaPutC
ret
; check if the input buffer is full or ends in CR or LF. If it does, prints it
; back and empty it.
chkbuf:
@ -81,9 +72,9 @@ chkbuf:
ld a, (hl) ; now, that's our char we have in A
inc hl ; put HL back where it was
cp CR
cp ASCII_CR
jr z, .do ; char is CR? do!
cp LF
cp ASCII_LF
jr z, .do ; char is LF? do!
; nothing matched? don't do anything
@ -101,51 +92,12 @@ chkbuf:
call shellParse
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
shellParse:
push af
push bc
push de
push hl
ld de, shellCmdTbl
ld a, SHELL_CMD_COUNT
@ -165,19 +117,34 @@ shellParse:
jr .end
.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
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
ex hl, de
.end:
pop hl
pop de
pop bc
pop af
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:
push af
push hl
@ -185,9 +152,10 @@ shellPrintErr:
ld hl, .str
call printstr
; ascii for '0' is 0x30
add a, 0x30
call aciaPutC
ld hl, SHELL_HEX_FMT
call fmtHexPair
ld a, 2
call printnstr
call printcrlf
pop hl
@ -198,19 +166,66 @@ shellPrintErr:
.db "ERR ", 0
; *** COMMANDS ***
shellSeek:
ld hl, .str
call printstr
ret
.str:
.db "seek called", CR, LF, 0
; When these commands are called, DE points to the first character of the
; command args.
shellPeek:
ld hl, .str
call printstr
; Set memory pointer to the specified address.
; Example: seek 01fe
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
.str:
.db "peek called", CR, LF, 0
; Format: 4 bytes name followed by 2 bytes jump. fill names with zeroes
shellCmdTbl:

View File

@ -47,7 +47,7 @@ This is what your glue code would look like:
jr init
.fill 0x38-$
jr aciaInt
jp aciaInt
init:
di
@ -63,8 +63,10 @@ init:
ei
call shellLoop
#include "core.asm"
ACIA_RAMSTART .equ RAMSTART
#include "acia.asm"
SHELL_RAMSTART .equ ACIA_RAMEND
#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
to `shell.asm`.
What comes below is actual code include from the acia and shell modules. As you
can see, we need to tell each module where to put their variables. `shell.asm`
doesn't have variables, but if it did, we would have a `SHELL_RAMSTART .equ
ACIA_RAMEND` just below the `acia.asm` include. `ACIA_RAMEND` is defined in
`acia.asm`.
What comes below is actual code include from parts we want to include in our
OS. As you can see, we need to tell each module where to put their variables.
See `parts/README.md` for details.
### Build the image