collapseos/parts/shell.asm

410 lines
7.8 KiB
NASM
Raw Normal View History

; shell
;
; Runs a shell over a block device interface.
; Status: incomplete. As it is now, it spits a welcome prompt, wait for input
; and compare the first 4 chars of the input with a command table and call the
; appropriate routine if it's found, an error if it's not.
;
2019-04-15 04:24:29 +10:00
; Commands, for now, are partially implemented.
;
; See constants below for error codes.
2019-04-15 01:22:45 +10:00
;
; All numerical values in the Collapse OS shell are represented and parsed in
; hexadecimal form, without prefix or suffix.
2019-04-15 04:24:29 +10:00
; *** DEFINES ***
; SHELL_GETC: Macro that calls a GetC routine
; SHELL_PUTC: Macro that calls a PutC routine
; SHELL_RAMSTART
; *** CONSTS ***
; number of entries in shellCmdTbl
2019-04-15 07:01:28 +10:00
SHELL_CMD_COUNT .equ 4
; The command that was type isn't known to the shell
SHELL_ERR_UNKNOWN_CMD .equ 0x01
; Arguments for the command weren't properly formatted
SHELL_ERR_BAD_ARGS .equ 0x02
; Size of the shell command buffer. If a typed command reaches this size, the
; command is flushed immediately (same as pressing return).
SHELL_BUFSIZE .equ 0x20
; *** 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
; 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
; for the null-termination every time we write to it. Simpler that way.
SHELL_BUF .equ SHELL_HEX_FMT+2
SHELL_RAMEND .equ SHELL_BUF+SHELL_BUFSIZE
; *** CODE ***
shellInit:
xor a
ld (SHELL_MEM_PTR), a
ld (SHELL_BUF), a
2019-04-15 04:53:16 +10:00
; print welcome
ld hl, .welcome
call printstr
ret
2019-04-15 04:53:16 +10:00
.welcome:
.db "Collapse OS", ASCII_CR, ASCII_LF, "> ", 0
shellLoop:
; First, let's wait until something is typed.
2019-04-15 04:24:29 +10:00
SHELL_GETC
; got it. Now, is it a CR or LF?
cp ASCII_CR
jr z, .do ; char is CR? do!
cp ASCII_LF
jr z, .do ; char is LF? do!
; Echo the received character right away so that we see what we type
SHELL_PUTC
; Ok, gotta add it do the buffer
; save char for later
ex af, af'
ld hl, SHELL_BUF
call findnull ; HL points to where we need to write
; A is the number of chars in the buf
cp SHELL_BUFSIZE
jr z, .do ; A == bufsize? then our buffer is full. do!
; bring the char back in A
ex af, af'
; Buffer not full, not CR or LF. Let's put that char in our buffer and
; read again.
ld (hl), a
; Now, write a zero to the next byte to properly terminate our string.
inc hl
xor a
ld (hl), a
2019-04-15 01:22:45 +10:00
jr shellLoop
.do:
2019-04-15 04:53:16 +10:00
call printcrlf
ld hl, SHELL_BUF
call shellParse
; empty our buffer by writing a zero to its first char
xor a
ld (hl), a
2019-04-15 04:53:16 +10:00
ld hl, .prompt
call printstr
2019-04-15 01:22:45 +10:00
jr shellLoop
2019-04-15 04:53:16 +10:00
.prompt:
.db "> ", 0
2019-04-15 01:22:45 +10:00
printcrlf:
ld a, ASCII_CR
2019-04-15 04:24:29 +10:00
SHELL_PUTC
2019-04-15 01:22:45 +10:00
ld a, ASCII_LF
2019-04-15 04:24:29 +10:00
SHELL_PUTC
ret
; Parse command (null terminated) at HL and calls it
shellParse:
push af
push bc
push de
push hl
2019-04-15 07:01:28 +10:00
push ix
ld de, shellCmdTbl
ld a, SHELL_CMD_COUNT
ld b, a
.loop:
ld a, 4 ; 4 chars to compare
call strncmp
jr z, .found
2019-04-15 04:53:16 +10:00
ld a, 7
call addDE
djnz .loop
; exhausted loop? not found
ld a, SHELL_ERR_UNKNOWN_CMD
call shellPrintErr
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
2019-04-15 07:01:28 +10:00
ld ixh, d
ld ixl, e
; Now, let's swap HL and DE because, well because that's how we're set.
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:
2019-04-15 07:01:28 +10:00
; DE points to args, IX points to jump line. Ready to roll!
call callIX
.end:
2019-04-15 07:01:28 +10:00
pop ix
pop hl
pop de
pop bc
pop af
ret
; Print the error code set in A (in hex)
shellPrintErr:
push af
push hl
ld hl, .str
call printstr
ld hl, SHELL_HEX_FMT
call fmtHexPair
ld a, 2
call printnstr
call printcrlf
pop hl
pop af
ret
.str:
.db "ERR ", 0
; *** COMMANDS ***
; When these commands are called, DE points to the first character of the
; command args.
; Set memory pointer to the specified address (word).
; Example: seek 01fe
shellSeek:
push af
push de
push hl
ex de, hl
2019-04-15 07:01:28 +10:00
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
2019-04-15 07:01:28 +10:00
; first byte, H is loaded from the second. We have to swap our result.
ld hl, SHELL_MEM_PTR
call swapBytes
jr .success
.error:
ld a, SHELL_ERR_BAD_ARGS
call shellPrintErr
jr .end
.success:
ld a, (SHELL_MEM_PTR+1)
ld hl, SHELL_HEX_FMT
call fmtHexPair
ld a, 2
call printnstr
ld a, (SHELL_MEM_PTR)
call fmtHexPair
ld a, 2
call printnstr
call printcrlf
.end:
pop hl
pop de
pop af
ret
; peek byte where memory pointer points to any display its value. If the
; optional numerical byte arg is supplied, this number of bytes will be printed
;
; Example: peek 2 (will print 2 bytes)
shellPeek:
push af
push bc
push de
push hl
ld b, 1 ; by default, we run the loop once
ld a, (de)
cp 0
jr z, .success ; no arg? don't try to parse
ex de, hl
call parseHexPair
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)
.loop: ld a, (hl)
ex hl, de
ld hl, SHELL_HEX_FMT
call fmtHexPair
ld a, 2
call printnstr
ex hl, de
inc hl
djnz .loop
call printcrlf
.end:
pop hl
pop de
pop bc
pop af
ret
2019-04-15 04:53:16 +10:00
; Load the specified number of bytes (max 0xff) from IO and write them in the
; current memory pointer (which doesn't change). For now, we can only load from
; SHELL_GETC, but a method of selecting IO sources is coming, making this
; command much more useful.
; Control is returned to the shell only after all bytes are read.
;
; Example: load 42
shellLoad:
push af
push bc
push hl
ld a, (de)
call parseHex
jr c, .error
jr .success
.error:
ld a, SHELL_ERR_BAD_ARGS
call shellPrintErr
jr .end
.success:
ld b, a
ld hl, (SHELL_MEM_PTR)
.loop: SHELL_GETC
ld (hl), a
inc hl
djnz .loop
.end:
pop hl
pop bc
pop af
ret
2019-04-15 07:01:28 +10:00
; Calls the routine where the memory pointer currently points. This can take two
; parameters, A and HL. The first one is a byte, the second, a word. These are
; the values that A and HL are going to be set to just before calling.
; Example: run 42 cafe
shellCall:
push af
push de
push hl
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:
; 1. The address we want to execute in (SHELL_MEM_PTR)
; 2. our HL arg on top of the stack
; 3. our A arg underneath.
; Ready, set, go!
ld a, (SHELL_MEM_PTR)
ld ixl, a
ld a, (SHELL_MEM_PTR+1)
ld ixh, a
pop hl
pop af
call callIX
.end:
pop hl
pop de
pop af
ret
2019-04-15 04:53:16 +10:00
; Format: 4 bytes name followed by 3 bytes jump. fill names with zeroes
shellCmdTbl:
.db "seek"
2019-04-15 04:53:16 +10:00
jp shellSeek
.db "peek"
2019-04-15 04:53:16 +10:00
jp shellPeek
.db "load"
jp shellLoad
2019-04-15 07:01:28 +10:00
.db "call"
jp shellCall