shell: implement seek and peek
This introduces `core.asm` which includes routines used by other parts.
This commit is contained in:
parent
9580cc3994
commit
21168f6c4e
193
parts/core.asm
Normal file
193
parts/core.asm
Normal 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
|
177
parts/shell.asm
177
parts/shell.asm
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user