1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-30 20:18:06 +11:00

Make userspace parse args the same way the shell does

This commit is contained in:
Virgil Dupras 2019-06-02 14:05:20 -04:00
parent 57356e47b4
commit f8bd8eeaaf
11 changed files with 169 additions and 128 deletions

View File

@ -7,3 +7,14 @@ be run.
That doesn't mean that you can't include that code in your kernel though, but That doesn't mean that you can't include that code in your kernel though, but
you will typically not want to do that. you will typically not want to do that.
## Userspace convention
We execute a userspace application by calling the address it's loaded into. This
means: a userspace application is expected to return.
Whatever calls the userspace app (usually, it will be the shell), should set
HL to a pointer to unparsed arguments in string form, null terminated.
The userspace application is expected to set A on return. 0 means success,
non-zero means error.

View File

@ -37,11 +37,13 @@
; fsSeek ; fsSeek
; fsTell ; fsTell
; cpHLDE ; cpHLDE
; parseArgs
; FS_HANDLE_SIZE ; FS_HANDLE_SIZE
; *** Variables *** ; *** Variables ***
#include "user.h" #include "user.h"
#include "err.h"
.org USER_CODE .org USER_CODE
jp zasmMain jp zasmMain

View File

@ -15,15 +15,28 @@
.equ ZASM_ORG ZASM_CTX_PC+2 .equ ZASM_ORG ZASM_CTX_PC+2
.equ ZASM_RAMEND ZASM_ORG+2 .equ ZASM_RAMEND ZASM_ORG+2
; Read file through blockdev ID in H and outputs its upcodes through blockdev ; Takes 2 byte arguments, blkdev in and blkdev out, expressed as IDs.
; ID in L. HL is set to the last lineno to be read. ; Read file through blkdev in and outputs its upcodes through blkdev out.
; HL is set to the last lineno to be read.
; Sets Z on success, unset on error. On error, A contains an error code (ERR_*) ; Sets Z on success, unset on error. On error, A contains an error code (ERR_*)
zasmMain: zasmMain:
; Parse args. HL points to string already
; We don't allocate memory just to hold this. Because this happens
; before initialization, we don't really care where those args are
; parsed.
ld de, .argspecs
ld ix, ZASM_RAMSTART
call parseArgs
ld a, SHELL_ERR_BAD_ARGS
ret nz
; HL now points to parsed args
; Init I/O ; Init I/O
ld a, h ld a, (ZASM_RAMSTART) ; blkdev in ID
ld de, IO_IN_GETC ld de, IO_IN_GETC
call blkSel call blkSel
ld a, l inc hl
ld a, (ZASM_RAMSTART+1) ; blkdev out ID
ld de, IO_OUT_GETC ld de, IO_OUT_GETC
call blkSel call blkSel
@ -47,6 +60,9 @@ zasmMain:
.end: .end:
jp ioLineNo ; --> HL, --> DE, returns jp ioLineNo ; --> HL, --> DE, returns
.argspecs:
.db 0b001, 0b001, 0
; Sets Z according to whether we're in first pass. ; Sets Z according to whether we're in first pass.
zasmIsFirstPass: zasmIsFirstPass:
ld a, (ZASM_FIRST_PASS) ld a, (ZASM_FIRST_PASS)

11
kernel/err.h Normal file
View File

@ -0,0 +1,11 @@
; Error codes used throughout the kernel
; The command that was type isn't known to the shell
.equ SHELL_ERR_UNKNOWN_CMD 0x01
; Arguments for the command weren't properly formatted
.equ SHELL_ERR_BAD_ARGS 0x02
; IO routines (GetC, PutC) returned an error in a load/save command
.equ SHELL_ERR_IO_ERROR 0x05

View File

@ -1,3 +1,10 @@
; *** Consts ***
; maximum number of bytes to receive as args in all commands. Determines the
; size of the args variable.
.equ PARSE_ARG_MAXCOUNT 3
; *** Code ***
; Parse the hex char at A and extract it's 0-15 numerical value. Put the result ; Parse the hex char at A and extract it's 0-15 numerical value. Put the result
; in A. ; in A.
; ;
@ -61,3 +68,101 @@ parseHexPair:
.end: .end:
pop bc pop bc
ret ret
; TODO: make parseArgs not expect a leading space.
; Parse arguments at (HL) with specifiers at (DE) into (IX).
;
; Args specifiers are a series of flag for each arg:
; Bit 0 - arg present: if unset, we stop parsing there
; Bit 1 - is word: this arg is a word rather than a byte. Because our
; destination are bytes anyway, this doesn't change much except
; for whether we expect a space between the hex pairs. If set,
; you still need to have a specifier for the second part of
; the multibyte.
; Bit 2 - optional: If set and not present during parsing, we don't error out
; and write zero
;
; Bit 3 - String argument: If set, this argument is a string. A pointer to the
; read string, null terminated (max 0x20 chars) will
; be placed in the next two bytes. This has to be the
; last argument of the list and it stops parsing.
; Sets A to nonzero if there was an error during parsing, zero otherwise.
parseArgs:
push bc
push de
push hl
push ix
ld b, PARSE_ARG_MAXCOUNT
xor c
.loop:
; init the arg value to a default 0
xor a
ld (ix), a
ld a, (hl)
; is this the end of the line?
or a ; cp 0
jr z, .endofargs
; do we have a proper space char?
cp ' '
jr z, .hasspace ; We're fine
; is our previous arg a multibyte? (argspec still in C)
bit 1, c
jr z, .error ; bit not set? error
dec hl ; offset the "inc hl" below
.hasspace:
; Get the specs
ld a, (de)
bit 0, a ; do we have an arg?
jr z, .error ; not set? then we have too many args
ld c, a ; save the specs for the next loop
inc hl ; (hl) points to a space, go next
bit 3, a ; is our arg a string?
jr z, .notAString
; our arg is a string. Let's place HL in our next two bytes and call
; it a day. Little endian, remember
ld (ix), l
ld (ix+1), h
jr .success ; directly to success: skip endofargs checks
.notAString:
call parseHexPair
jr c, .error
; we have a good arg and we need to write A in (IX).
ld (ix), a
; Good! increase counters
inc de
inc ix
inc hl ; get to following char (generally a space)
djnz .loop
; If we get here, it means that our next char *has* to be a null char
ld a, (hl)
cp 0
jr z, .success ; zero? great!
jr .error
.endofargs:
; We encountered our null char. Let's verify that we either have no
; more args or that they are optional
ld a, (de)
cp 0
jr z, .success ; no arg? success
bit 2, a
jr nz, .success ; if set, arg is optional. success
jr .error
.success:
xor a
jr .end
.error:
inc a
.end:
pop ix
pop hl
pop de
pop bc
ret

View File

@ -20,6 +20,8 @@
; hexadecimal form, without prefix or suffix. ; hexadecimal form, without prefix or suffix.
; *** REQUIREMENTS *** ; *** REQUIREMENTS ***
; err
; core
; parse ; parse
; stdio ; stdio
@ -33,19 +35,6 @@
; 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 number of bytes to receive as args in all commands. Determines the
; size of the args variable.
.equ SHELL_CMD_ARGS_MAXSIZE 3
; The command that was type isn't known to the shell
.equ SHELL_ERR_UNKNOWN_CMD 0x01
; Arguments for the command weren't properly formatted
.equ SHELL_ERR_BAD_ARGS 0x02
; IO routines (GetC, PutC) returned an error in a load/save command
.equ SHELL_ERR_IO_ERROR 0x05
; Size of the shell command buffer. If a typed command reaches this size, the ; Size of the shell command buffer. If a typed command reaches this size, the
; command is flushed immediately (same as pressing return). ; command is flushed immediately (same as pressing return).
.equ SHELL_BUFSIZE 0x20 .equ SHELL_BUFSIZE 0x20
@ -62,7 +51,7 @@
; Command buffer. We read types chars into this buffer until return is pressed ; 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 ; 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. ; for the null-termination every time we write to it. Simpler that way.
.equ SHELL_BUF SHELL_CMD_ARGS+SHELL_CMD_ARGS_MAXSIZE .equ SHELL_BUF SHELL_CMD_ARGS+PARSE_ARG_MAXCOUNT
; Pointer to a hook to call when a cmd name isn't found ; Pointer to a hook to call when a cmd name isn't found
.equ SHELL_CMDHOOK SHELL_BUF+SHELL_BUFSIZE .equ SHELL_CMDHOOK SHELL_BUF+SHELL_BUFSIZE
@ -185,14 +174,15 @@ shellParse:
call addDE call addDE
; We're ready to parse args ; We're ready to parse args
call shellParseArgs ld ix, SHELL_CMD_ARGS
call parseArgs
or a ; cp 0 or a ; cp 0
jr nz, .parseerror jr nz, .parseerror
ld hl, SHELL_CMD_ARGS
; Args parsed, now we can load the routine address and call it. ; Args parsed, now we can load the routine address and call it.
; let's have DE point to the jump line ; let's have DE point to the jump line
ld a, SHELL_CMD_ARGS_MAXSIZE ld hl, SHELL_CMD_ARGS
ld a, PARSE_ARG_MAXCOUNT
call addDE call addDE
push de \ pop ix push de \ pop ix
; Ready to roll! ; Ready to roll!
@ -230,115 +220,13 @@ shellPrintErr:
.str: .str:
.db "ERR ", 0 .db "ERR ", 0
; Parse arguments at (HL) with specifiers at (DE) into (SHELL_CMD_ARGS).
; (HL) should point to the character *just* after the name of the command
; because we verify, in the case that we have args, that we have a space there.
;
; Args specifiers are a series of flag for each arg:
; Bit 0 - arg present: if unset, we stop parsing there
; Bit 1 - is word: this arg is a word rather than a byte. Because our
; destination are bytes anyway, this doesn't change much except
; for whether we expect a space between the hex pairs. If set,
; you still need to have a specifier for the second part of
; the multibyte.
; Bit 2 - optional: If set and not present during parsing, we don't error out
; and write zero
;
; Bit 3 - String argument: If set, this argument is a string. A pointer to the
; read string, null terminated (max 0x20 chars) will
; be placed in the next two bytes. This has to be the
; last argument of the list and it stops parsing.
; Sets A to nonzero if there was an error during parsing, zero otherwise.
; If there was an error during parsing, carry is set.
shellParseArgs:
push bc
push de
push hl
push ix
ld ix, SHELL_CMD_ARGS
ld a, SHELL_CMD_ARGS_MAXSIZE
ld b, a
xor c
.loop:
; init the arg value to a default 0
xor a
ld (ix), a
ld a, (hl)
; is this the end of the line?
cp 0
jr z, .endofargs
; do we have a proper space char?
cp ' '
jr z, .hasspace ; We're fine
; is our previous arg a multibyte? (argspec still in C)
bit 1, c
jr z, .error ; bit not set? error
dec hl ; offset the "inc hl" below
.hasspace:
; Get the specs
ld a, (de)
bit 0, a ; do we have an arg?
jr z, .error ; not set? then we have too many args
ld c, a ; save the specs for the next loop
inc hl ; (hl) points to a space, go next
bit 3, a ; is our arg a string?
jr z, .notAString
; our arg is a string. Let's place HL in our next two bytes and call
; it a day. Little endian, remember
ld (ix), l
ld (ix+1), h
jr .success ; directly to success: skip endofargs checks
.notAString:
call parseHexPair
jr c, .error
; we have a good arg and we need to write A in (IX).
ld (ix), a
; Good! increase counters
inc de
inc ix
inc hl ; get to following char (generally a space)
djnz .loop
; If we get here, it means that our next char *has* to be a null char
ld a, (hl)
cp 0
jr z, .success ; zero? great!
jr .error
.endofargs:
; We encountered our null char. Let's verify that we either have no
; more args or that they are optional
ld a, (de)
cp 0
jr z, .success ; no arg? success
bit 2, a
jr nz, .success ; if set, arg is optional. success
jr .error
.success:
xor a
jr .end
.error:
inc a
.end:
pop ix
pop hl
pop de
pop bc
ret
; *** COMMANDS *** ; *** COMMANDS ***
; A command is a 4 char names, followed by a SHELL_CMD_ARGS_MAXSIZE bytes of ; A command is a 4 char names, followed by a PARSE_ARG_MAXCOUNT bytes of
; argument specs, followed by the routine. Then, a simple table of addresses ; argument specs, followed by the routine. Then, a simple table of addresses
; is compiled in a block and this is what is iterated upon when we want all ; is compiled in a block and this is what is iterated upon when we want all
; available commands. ; available commands.
; ;
; Format: 4 bytes name followed by SHELL_CMD_ARGS_MAXSIZE bytes specifiers, ; Format: 4 bytes name followed by PARSE_ARG_MAXCOUNT bytes specifiers,
; followed by 3 bytes jump. fill names with zeroes ; followed by 3 bytes jump. fill names with zeroes
; ;
; When these commands are called, HL points to the first byte of the ; When these commands are called, HL points to the first byte of the

View File

@ -54,7 +54,7 @@ updatebootstrap: $(ZASMBIN)
.PHONY: rescue .PHONY: rescue
rescue: rescue:
scas -o zasm/kernel.bin -I $(KERNEL) zasm/glue.asm scas -o zasm/kernel.bin -I $(KERNEL) zasm/glue.asm
scas -o zasm/zasm.bin -I $(APPS) $(APPS)/zasm/glue.asm scas -o zasm/zasm.bin -I $(APPS) -I $(KERNEL) $(APPS)/zasm/glue.asm
.PHONY: clean .PHONY: clean
clean: clean:

View File

@ -14,6 +14,7 @@
jp printstr jp printstr
#include "core.asm" #include "core.asm"
#include "err.h"
#include "parse.asm" #include "parse.asm"
.equ BLOCKDEV_RAMSTART RAMSTART .equ BLOCKDEV_RAMSTART RAMSTART

View File

@ -27,8 +27,10 @@ jp fsGetC
jp fsSeek jp fsSeek
jp fsTell jp fsTell
jp cpHLDE jp cpHLDE
jp parseArgs
#include "core.asm" #include "core.asm"
#include "err.h"
#include "parse.asm" #include "parse.asm"
.equ BLOCKDEV_RAMSTART RAMSTART .equ BLOCKDEV_RAMSTART RAMSTART
.equ BLOCKDEV_COUNT 3 .equ BLOCKDEV_COUNT 3
@ -50,12 +52,14 @@ init:
ld de, BLOCKDEV_GETC ld de, BLOCKDEV_GETC
call blkSel call blkSel
call fsOn call fsOn
ld h, 0 ; input blkdev ld hl, .zasmArgs
ld l, 1 ; output blkdev
call USER_CODE call USER_CODE
; signal the emulator we're done ; signal the emulator we're done
halt halt
.zasmArgs:
.db " 0 1", 0
; *** I/O *** ; *** I/O ***
emulGetC: emulGetC:
in a, (STDIO_PORT) in a, (STDIO_PORT)

View File

@ -2,6 +2,7 @@
.equ ACIA_CTL 0x80 ; Control and status. RS off. .equ ACIA_CTL 0x80 ; Control and status. RS off.
.equ ACIA_IO 0x81 ; Transmit. RS on. .equ ACIA_IO 0x81 ; Transmit. RS on.
#include "err.h"
#include "core.asm" #include "core.asm"
#include "parse.asm" #include "parse.asm"
.equ ACIA_RAMSTART RAMSTART .equ ACIA_RAMSTART RAMSTART

View File

@ -21,7 +21,9 @@
.equ fsSeek 0x30 .equ fsSeek 0x30
.equ fsTell 0x33 .equ fsTell 0x33
.equ cpHLDE 0x36 .equ cpHLDE 0x36
.equ parseArgs 0x39
#include "err.h"
#include "zasm/const.asm" #include "zasm/const.asm"
#include "zasm/util.asm" #include "zasm/util.asm"
.equ IO_RAMSTART USER_RAMSTART .equ IO_RAMSTART USER_RAMSTART