stdio: make stdioGetC and stdioReadline blocking

ref #64.

Also, fix a bug in the shell where it would write outside the buffer's
bounds when given a completely filled buffer without a space character
in it.
This commit is contained in:
Virgil Dupras 2019-11-03 20:32:27 -05:00
parent 16bf8e28c0
commit 2a513e6f57
2 changed files with 50 additions and 92 deletions

View File

@ -35,6 +35,10 @@
; number of entries in shellCmdTbl ; number of entries in shellCmdTbl
.equ SHELL_CMD_COUNT 6+SHELL_EXTRA_CMD_COUNT .equ SHELL_CMD_COUNT 6+SHELL_EXTRA_CMD_COUNT
; maximum length for shell commands. Should be confortably below stdio's
; readline buffer length.
.equ SHELL_MAX_CMD_LEN 0x10
; *** VARIABLES *** ; *** VARIABLES ***
; Memory address that the shell is currently "pointing at" for peek, load, call ; Memory address that the shell is currently "pointing at" for peek, load, call
; operations. Set with mptr. ; operations. Set with mptr.
@ -67,12 +71,8 @@ shellInit:
; Inifite loop that processes input. Because it's infinite, you should jump ; Inifite loop that processes input. Because it's infinite, you should jump
; to it rather than call it. Saves two precious bytes in the stack. ; to it rather than call it. Saves two precious bytes in the stack.
shellLoop: shellLoop:
; Let's wait until a line is typed. call stdioReadLine
call stdioReadC
jr nz, shellLoop ; not done? loop
; We're done. Process line.
call printcrlf call printcrlf
call stdioGetLine
call shellParse call shellParse
ld hl, .prompt ld hl, .prompt
call printstr call printstr
@ -86,7 +86,7 @@ shellParse:
; first thing: is command empty? ; first thing: is command empty?
ld a, (hl) ld a, (hl)
or a or a
ret z ; empty, nthing to do ret z ; empty, nothing to do
push af push af
push bc push bc
@ -104,6 +104,13 @@ shellParse:
; no arg, (HL) is zero to facilitate processing later, add a second ; no arg, (HL) is zero to facilitate processing later, add a second
; null next to that one to indicate unambiguously that we have no args. ; null next to that one to indicate unambiguously that we have no args.
inc hl inc hl
; Oh wait, before we proceed, is our cmd length within limits? cmd len
; is currently in A from findchar
cp SHELL_MAX_CMD_LEN
jr c, .hasArgs ; within limits
; outside limits
ld a, SHELL_ERR_UNKNOWN_CMD
jr .error
.hasArgs: .hasArgs:
xor a xor a
ld (hl), a ld (hl), a

View File

@ -4,6 +4,13 @@
; in", that is, the console through which the user is connected in a decoupled ; in", that is, the console through which the user is connected in a decoupled
; manner. ; manner.
; ;
; Those GetC/PutC routines are hooked in during stdioInit and have this API:
;
; GetC: Blocks until a character is read from the device and return that
; character in A.
;
; PutC: Write character specified onto the device.
;
; *** Consts *** ; *** Consts ***
; Size of the readline buffer. If a typed line reaches this size, the line is ; Size of the readline buffer. If a typed line reaches this size, the line is
; flushed immediately (same as pressing return). ; flushed immediately (same as pressing return).
@ -12,25 +19,20 @@
; *** Variables *** ; *** Variables ***
; Used to store formatted hex values just before printing it. ; Used to store formatted hex values just before printing it.
.equ STDIO_HEX_FMT STDIO_RAMSTART .equ STDIO_HEX_FMT STDIO_RAMSTART
.equ STDIO_GETC STDIO_HEX_FMT+2 .equ STDIO_GETC @+2
.equ STDIO_PUTC STDIO_GETC+2 .equ STDIO_PUTC @+2
; Line buffer. We read types chars into this buffer until return is pressed ; Line 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 ; This buffer is null-terminated.
; for the null-termination every time we write to it. Simpler that way. .equ STDIO_BUF @+2
.equ STDIO_BUF STDIO_PUTC+2
; Index where the next char will go in stdioGetC. ; Index where the next char will go in stdioGetC.
.equ STDIO_BUFIDX STDIO_BUF+STDIO_BUFSIZE .equ STDIO_RAMEND @+STDIO_BUFSIZE
.equ STDIO_RAMEND STDIO_BUFIDX+1
; Sets GetC to the routine where HL points to and PutC to DE. ; Sets GetC to the routine where HL points to and PutC to DE.
stdioInit: stdioInit:
ld (STDIO_GETC), hl ld (STDIO_GETC), hl
ld (STDIO_PUTC), de ld (STDIO_PUTC), de
xor a
ld (STDIO_BUF), a
ld (STDIO_BUFIDX), a
ret ret
stdioGetC: stdioGetC:
@ -105,21 +107,19 @@ printHexPair:
pop af pop af
ret ret
; Call stdioGetC and put the result in the buffer. Sets Z according to whether ; Repeatedly calls stdioGetC until a whole line was read, that is, when CR or
; the buffer is "complete", that is, whether CR or LF have been pressed or if ; LF is read or if the buffer is full. Sets HL to the beginning of the read
; the the buffer is full. Z is set if the line is "complete", unset if not. ; line, which is null-terminated.
; The next call to stdioReadC after a completed line will start a new line.
; ;
; This routine also takes care of echoing received characters back to the TTY. ; This routine also takes care of echoing received characters back to the TTY.
; ; It also manages backspaces properly.
; This routine doesn't wait after a typed char. If nothing is typed, we return stdioReadLine:
; immediately with Z flag unset. push bc
; ld hl, STDIO_BUF
; Note that this routine doesn't bother returning the typed character. ld b, STDIO_BUFSIZE-1
stdioReadC: .loop:
; Let's wait until something is typed. ; Let's wait until something is typed.
call stdioGetC call stdioGetC
ret nz ; nothing typed? nothing to do
; got it. Now, is it a CR or LF? ; got it. Now, is it a CR or LF?
cp ASCII_CR cp ASCII_CR
jr z, .complete ; char is CR? buffer complete! jr z, .complete ; char is CR? buffer complete!
@ -134,62 +134,28 @@ stdioReadC:
call stdioPutC call stdioPutC
; Ok, gotta add it do the buffer ; Ok, gotta add it do the buffer
; save char for later
ex af, af'
ld a, (STDIO_BUFIDX)
push hl ; --> lvl 1
ld hl, STDIO_BUF
; make HL point to dest spot
call addHL
; Write our char down
ex af, af'
ld (hl), a ld (hl), a
; follow up with a null char
inc hl inc hl
xor a djnz .loop
ld (hl), a ; buffer overflow, complete line
pop hl ; <-- lvl 1
; inc idx, which still is in AF'
ex af, af'
inc a
cp STDIO_BUFSIZE-1 ; -1 is because we always want to keep our
; last char at zero.
jr z, .complete ; end of buffer reached? buffer is full.
; not complete. save idx back
ld (STDIO_BUFIDX), a
; Z already unset
ret
.complete: .complete:
; The line in our buffer is complete. ; The line in our buffer is complete.
; But before we do that, let's take care of a special case: the empty ; Let's null-terminate it and return.
; line. If we didn't add any character since the last "complete", then xor a
; our buffer's content is the content from the last time. Let's set this ld (hl), a
; to an empty string. ld hl, STDIO_BUF
ld a, (STDIO_BUFIDX) pop bc
or a
jr nz, .completeSkip
ld (STDIO_BUF), a
.completeSkip:
xor a ; sets Z
ld (STDIO_BUFIDX), a
ret ret
.delchr: .delchr:
ld a, (STDIO_BUFIDX) ; Deleting is a tricky business. We have to decrease HL and increase B
or a ; so that everything stays consistent. We also have to make sure that
jp z, unsetZ ; buf empty? nothing to do ; We don't do buffer underflows.
; buffer not empty, let's go back one char and set a null char there. ld a, b
dec a cp STDIO_BUFSIZE-1
ld (STDIO_BUFIDX), a jr z, .loop ; beginning of line, nothing to delete
push hl ;<| dec hl
ld hl, STDIO_BUF ; | inc b
; make HL point to dest spot |
call addHL ; |
xor a ; |
ld (hl), a ; |
pop hl ;<|
; Char deleted in buffer, now send BS + space + BS for the terminal ; Char deleted in buffer, now send BS + space + BS for the terminal
; to clear its previous char ; to clear its previous char
ld a, ASCII_BS ld a, ASCII_BS
@ -198,19 +164,4 @@ stdioReadC:
call stdioPutC call stdioPutC
ld a, ASCII_BS ld a, ASCII_BS
call stdioPutC call stdioPutC
jp unsetZ jr .loop
; Make HL point to the line buffer. It is always null terminated.
stdioGetLine:
ld hl, STDIO_BUF
ret
; Repeatedly call stdioReadC until Z is set, then make HL point to the read
; buffer.
stdioReadLine:
call stdioReadC
jr nz, stdioReadLine
ld hl, STDIO_BUF
ret