mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-08 16:48:06 +11:00
8ccddbcb0e
They serve a different purpose. The goal of the ACIA buffer is to ensure that we don't miss an input. The goal of the shell buffer is to wait until the user presses return. The ACIA buffer has been moved to shell and replaced with a circular buffer, a more appropriate data structure for this kind of purpose. Also, introduce `aciaGetC`.
285 lines
5.5 KiB
NASM
285 lines
5.5 KiB
NASM
; shell
|
|
;
|
|
; Runs a shell over an asynchronous communication interface adapter (ACIA).
|
|
; for now, this unit is tightly coupled to acia.asm, but it will eventually be
|
|
; more general than that.
|
|
|
|
; 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.
|
|
;
|
|
; Commands, for now, are dummy.
|
|
;
|
|
; See constants below for error codes.
|
|
;
|
|
; All numerical values in the Collapse OS shell are represented and parsed in
|
|
; hexadecimal form, without prefix or suffix.
|
|
|
|
; *** CONSTS ***
|
|
|
|
; number of entries in shellCmdTbl
|
|
SHELL_CMD_COUNT .equ 2
|
|
|
|
; 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
|
|
|
|
; print prompt
|
|
ld hl, .prompt
|
|
call printstr
|
|
call printcrlf
|
|
ret
|
|
|
|
.prompt:
|
|
.db "Collapse OS", 0
|
|
|
|
shellLoop:
|
|
; First, let's wait until something is typed.
|
|
call aciaGetC
|
|
; 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!
|
|
|
|
; 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
|
|
|
|
jr shellLoop
|
|
|
|
.do:
|
|
ld hl, SHELL_BUF
|
|
call shellParse
|
|
; empty our buffer by writing a zero to its first char
|
|
xor a
|
|
ld (hl), a
|
|
|
|
jr shellLoop
|
|
|
|
printcrlf:
|
|
ld a, ASCII_CR
|
|
call aciaPutC
|
|
ld a, ASCII_LF
|
|
call aciaPutC
|
|
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
|
|
ld b, a
|
|
|
|
.loop:
|
|
ld a, 4 ; 4 chars to compare
|
|
call strncmp
|
|
jr z, .found
|
|
ld a, 6
|
|
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
|
|
; 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
|
|
|
|
.end:
|
|
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
|
|
call parseHexPair
|
|
jr c, .error
|
|
; z80 is little endian. in a "ld hl, (nn)" op, L is loaded from the
|
|
; first byte, H is loaded from the second
|
|
ld (SHELL_MEM_PTR+1), a
|
|
inc hl
|
|
inc hl
|
|
call parseHexPair
|
|
jr c, .error
|
|
ld (SHELL_MEM_PTR), a
|
|
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
|
|
|
|
; Format: 4 bytes name followed by 2 bytes jump. fill names with zeroes
|
|
shellCmdTbl:
|
|
.db "seek"
|
|
jr shellSeek
|
|
.db "peek"
|
|
jr shellPeek
|
|
|