Browse Source

Make userspace parse args the same way the shell does

pull/10/head
Virgil Dupras 5 years ago
parent
commit
f8bd8eeaaf
11 changed files with 169 additions and 128 deletions
  1. +11
    -0
      apps/README.md
  2. +2
    -0
      apps/zasm/glue.asm
  3. +20
    -4
      apps/zasm/main.asm
  4. +11
    -0
      kernel/err.h
  5. +105
    -0
      kernel/parse.asm
  6. +9
    -121
      kernel/shell.asm
  7. +1
    -1
      tools/emul/Makefile
  8. +1
    -0
      tools/emul/shell/shell_.asm
  9. +6
    -2
      tools/emul/zasm/glue.asm
  10. +1
    -0
      tools/tests/zasm/test6.asm
  11. +2
    -0
      tools/tests/zasm/test7.asm

+ 11
- 0
apps/README.md View File

@@ -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.

+ 2
- 0
apps/zasm/glue.asm View File

@@ -37,11 +37,13 @@
; fsSeek
; fsTell
; cpHLDE
; parseArgs
; FS_HANDLE_SIZE

; *** Variables ***

#include "user.h"
#include "err.h"
.org USER_CODE

jp zasmMain


+ 20
- 4
apps/zasm/main.asm View File

@@ -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)


+ 11
- 0
kernel/err.h 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


+ 105
- 0
kernel/parse.asm 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
; 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

+ 9
- 121
kernel/shell.asm View File

@@ -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


+ 1
- 1
tools/emul/Makefile View File

@@ -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:


+ 1
- 0
tools/emul/shell/shell_.asm View File

@@ -14,6 +14,7 @@
jp printstr

#include "core.asm"
#include "err.h"
#include "parse.asm"

.equ BLOCKDEV_RAMSTART RAMSTART


+ 6
- 2
tools/emul/zasm/glue.asm View File

@@ -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)


+ 1
- 0
tools/tests/zasm/test6.asm View File

@@ -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


+ 2
- 0
tools/tests/zasm/test7.asm View File

@@ -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


Loading…
Cancel
Save