From f8bd8eeaaf3dc1a3b546c71ad49a7bf16a27b8d1 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Sun, 2 Jun 2019 14:05:20 -0400 Subject: [PATCH] Make userspace parse args the same way the shell does --- apps/README.md | 11 +++ apps/zasm/glue.asm | 2 + apps/zasm/main.asm | 24 +++++-- kernel/err.h | 11 +++ kernel/parse.asm | 105 +++++++++++++++++++++++++++++ kernel/shell.asm | 130 +++--------------------------------- tools/emul/Makefile | 2 +- tools/emul/shell/shell_.asm | 1 + tools/emul/zasm/glue.asm | 8 ++- tools/tests/zasm/test6.asm | 1 + tools/tests/zasm/test7.asm | 2 + 11 files changed, 169 insertions(+), 128 deletions(-) create mode 100644 kernel/err.h diff --git a/apps/README.md b/apps/README.md index 5ec9992..7be6e2e 100644 --- a/apps/README.md +++ b/apps/README.md @@ -7,3 +7,14 @@ be run. That doesn't mean that you can't include that code in your kernel though, but 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. diff --git a/apps/zasm/glue.asm b/apps/zasm/glue.asm index 1c7cf4e..ca939bb 100644 --- a/apps/zasm/glue.asm +++ b/apps/zasm/glue.asm @@ -37,11 +37,13 @@ ; fsSeek ; fsTell ; cpHLDE +; parseArgs ; FS_HANDLE_SIZE ; *** Variables *** #include "user.h" +#include "err.h" .org USER_CODE jp zasmMain diff --git a/apps/zasm/main.asm b/apps/zasm/main.asm index 38437fd..9126c5e 100644 --- a/apps/zasm/main.asm +++ b/apps/zasm/main.asm @@ -15,15 +15,28 @@ .equ ZASM_ORG ZASM_CTX_PC+2 .equ ZASM_RAMEND ZASM_ORG+2 -; Read file through blockdev ID in H and outputs its upcodes through blockdev -; ID in L. HL is set to the last lineno to be read. +; Takes 2 byte arguments, blkdev in and blkdev out, expressed as IDs. +; 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_*) 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 - ld a, h + ld a, (ZASM_RAMSTART) ; blkdev in ID ld de, IO_IN_GETC call blkSel - ld a, l + inc hl + ld a, (ZASM_RAMSTART+1) ; blkdev out ID ld de, IO_OUT_GETC call blkSel @@ -47,6 +60,9 @@ zasmMain: .end: jp ioLineNo ; --> HL, --> DE, returns +.argspecs: + .db 0b001, 0b001, 0 + ; Sets Z according to whether we're in first pass. zasmIsFirstPass: ld a, (ZASM_FIRST_PASS) diff --git a/kernel/err.h b/kernel/err.h new file mode 100644 index 0000000..d8ce244 --- /dev/null +++ b/kernel/err.h @@ -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 + diff --git a/kernel/parse.asm b/kernel/parse.asm index 4a2e313..f4cc5b7 100644 --- a/kernel/parse.asm +++ b/kernel/parse.asm @@ -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 ; in A. ; @@ -61,3 +68,101 @@ parseHexPair: .end: pop bc 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 diff --git a/kernel/shell.asm b/kernel/shell.asm index ef75807..4d76991 100644 --- a/kernel/shell.asm +++ b/kernel/shell.asm @@ -20,6 +20,8 @@ ; hexadecimal form, without prefix or suffix. ; *** REQUIREMENTS *** +; err +; core ; parse ; stdio @@ -33,19 +35,6 @@ ; number of entries in shellCmdTbl .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 ; command is flushed immediately (same as pressing return). .equ SHELL_BUFSIZE 0x20 @@ -62,7 +51,7 @@ ; 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. -.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 .equ SHELL_CMDHOOK SHELL_BUF+SHELL_BUFSIZE @@ -185,14 +174,15 @@ shellParse: call addDE ; We're ready to parse args - call shellParseArgs + ld ix, SHELL_CMD_ARGS + call parseArgs or a ; cp 0 jr nz, .parseerror - ld hl, SHELL_CMD_ARGS ; Args parsed, now we can load the routine address and call it. ; 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 push de \ pop ix ; Ready to roll! @@ -230,115 +220,13 @@ shellPrintErr: .str: .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 *** -; 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 ; is compiled in a block and this is what is iterated upon when we want all ; 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 ; ; When these commands are called, HL points to the first byte of the diff --git a/tools/emul/Makefile b/tools/emul/Makefile index 097874f..0ccfa52 100644 --- a/tools/emul/Makefile +++ b/tools/emul/Makefile @@ -54,7 +54,7 @@ updatebootstrap: $(ZASMBIN) .PHONY: rescue rescue: 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 clean: diff --git a/tools/emul/shell/shell_.asm b/tools/emul/shell/shell_.asm index 643a89d..ad68233 100644 --- a/tools/emul/shell/shell_.asm +++ b/tools/emul/shell/shell_.asm @@ -14,6 +14,7 @@ jp printstr #include "core.asm" +#include "err.h" #include "parse.asm" .equ BLOCKDEV_RAMSTART RAMSTART diff --git a/tools/emul/zasm/glue.asm b/tools/emul/zasm/glue.asm index 2b54c26..f105220 100644 --- a/tools/emul/zasm/glue.asm +++ b/tools/emul/zasm/glue.asm @@ -27,8 +27,10 @@ jp fsGetC jp fsSeek jp fsTell jp cpHLDE +jp parseArgs #include "core.asm" +#include "err.h" #include "parse.asm" .equ BLOCKDEV_RAMSTART RAMSTART .equ BLOCKDEV_COUNT 3 @@ -50,12 +52,14 @@ init: ld de, BLOCKDEV_GETC call blkSel call fsOn - ld h, 0 ; input blkdev - ld l, 1 ; output blkdev + ld hl, .zasmArgs call USER_CODE ; signal the emulator we're done halt +.zasmArgs: + .db " 0 1", 0 + ; *** I/O *** emulGetC: in a, (STDIO_PORT) diff --git a/tools/tests/zasm/test6.asm b/tools/tests/zasm/test6.asm index 7c6ab7b..bd7420f 100644 --- a/tools/tests/zasm/test6.asm +++ b/tools/tests/zasm/test6.asm @@ -2,6 +2,7 @@ .equ ACIA_CTL 0x80 ; Control and status. RS off. .equ ACIA_IO 0x81 ; Transmit. RS on. +#include "err.h" #include "core.asm" #include "parse.asm" .equ ACIA_RAMSTART RAMSTART diff --git a/tools/tests/zasm/test7.asm b/tools/tests/zasm/test7.asm index 54ea35a..a405205 100644 --- a/tools/tests/zasm/test7.asm +++ b/tools/tests/zasm/test7.asm @@ -21,7 +21,9 @@ .equ fsSeek 0x30 .equ fsTell 0x33 .equ cpHLDE 0x36 +.equ parseArgs 0x39 +#include "err.h" #include "zasm/const.asm" #include "zasm/util.asm" .equ IO_RAMSTART USER_RAMSTART