diff --git a/CODE.md b/CODE.md index 13ba495..2f7dc27 100644 --- a/CODE.md +++ b/CODE.md @@ -51,6 +51,21 @@ Therefore, shadow registers should only be used in code that doesn't call routines or that call a routine that explicitly states that it preserves shadow registers. +## Z for success + +The vast majority of routines use the Z flag to indicate success. When Z is set, +it indicates success. When Z is unset, it indicates error. This follows the +tradition of a zero indicating success and a nonzero indicating error. + +Important note: only Z indicate success. Many routines return a meaningful +nonzero value in A and still set Z to indicate success. + +In error conditions, however, most of the time A is set to an error code. + +In many routines, this is specified verbosely, but it's repeated so often that +I started writing it in short form, "Z for success", which means what is +described here. + ## Stack management Keeping the stack "balanced" is a big challenge when writing assembler code. diff --git a/apps/basic/README.md b/apps/basic/README.md index d2c38c2..e0b9f61 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -14,3 +14,17 @@ writing from scratch, so here I am, writing from scratch again... The biggest challenge here is to extract code from zasm, adapt it to fit BASIC, not break anything, and have the wisdom to see when copy/pasting is a better idea. + +## Design goal + +The reason for including a BASIC dialect in Collapse OS is to supply some form +of system administration swiss knife. zasm, ed and the shell can do +theoretically anything, but some tasks (which are difficult to predict) can +possibly be overly tedious. One can think, for example, about hardware +debugging. Poking and peeking around when not sure what we're looking for can +be a lot more effective with the help of variables, conditions and for-loops in +an interpreter. + +Because the goal is not to provide a foundation for complex programs, I'm +planning on intentionally crippling this BASIC dialect for the sake of +simplicity. diff --git a/apps/basic/buf.asm b/apps/basic/buf.asm new file mode 100644 index 0000000..3421f5d --- /dev/null +++ b/apps/basic/buf.asm @@ -0,0 +1,97 @@ +; *** Consts *** +.equ BUF_POOLSIZE 0x1000 + +; *** Variables *** +; A pointer to free space in the pool. +.equ BUF_FREE BUF_RAMSTART +; The line pool. Each line consists of a two bytes binary number followed by +; a one byte length followed by the command string, which doesn't include its +; line number (example "10 print 123" becomes "print 123"), but which is null +; terminated. The one byte length includes null termination. For example, if +; we have a line record starting at 0x1000 and that its length field indicates +; 0x42, this means that the next line starts at 0x1045 (0x42+2+1). +.equ BUF_POOL @+2 +.equ BUF_RAMEND @+BUF_POOLSIZE + +bufInit: + ld hl, BUF_POOL + ld (BUF_FREE), hl + ret + +; Add line at (HL) with line number DE to the pool. The string at (HL) should +; not contain the line number prefix or the whitespace between the line number +; and the comment. +; Note that an empty string is *not* an error. It will be saved as a line. +; Don't send strings that are more than 0xfe in length. It won't work well. +; Z for success. +; The only error condition that is handled is when there is not enough space +; left in the pool to add a string of (HL)'s size. In that case, nothing will +; be done and Z will be unset. +; +; DESTROYED REGISTER: DE. Too much pushpopping around to keep it. Not worth it. +bufAdd: + push hl ; --> lvl 1 + push de ; --> lvl 2 + ; First step: see if we're within the pool's bounds + call strlen + inc a ; strlen doesn't include line termination + ld hl, (BUF_FREE) + call addHL + ; add overhead (3b) + inc hl \ inc hl \ inc hl + ld de, BUF_RAMEND + sbc hl, de + ; no carry? HL >= BUF_RAMEND, error. Z already unset + jr nc, .error + ; We have enough space, proceed + ld hl, (BUF_FREE) + pop de ; <-- lvl 2 + ld (hl), e + inc hl + ld (hl), d + inc hl + ; A has been untouched since that strlen call. Let's use it as-is. + ld (hl), a + inc hl ; HL now points to dest for our string. + ex de, hl + pop hl \ push hl ; <--> lvl 1. recall orig, but also preserve + call strcpyM + ; Copying done. Let's update the free zone marker. + ld (BUF_FREE), de + xor a ; set Z + pop hl ; <-- lvl 1 + ret +.error: + pop de + pop hl + ret + +; Set IX to point to the first valid line we have in the pool. +; Error if the pool is empty. +; Z for success. +bufFirst: + ld a, (BUF_POOL+2) + or a + jp z, unsetZ + ld ix, BUF_POOL + xor a ; set Z + ret + +; Given a valid line record in IX, move IX to the next valid line. +; This routine doesn't check that IX is valid. Ensure IX validity before +; calling. This routine also doesn't check that the next line is within the +; bounds of the pool because this check is done during bufAdd. +; The only possible error is if there is no next line. +; Z for success. +bufNext: + push de ; --> lvl 1 + ld d, 0 + ld e, (ix+2) + add ix, de + inc ix \ inc ix \ inc ix + pop de ; <-- lvl 1 + ld a, (ix+2) + or a + jp z, unsetZ + xor a ; set Z + ret diff --git a/apps/basic/glue.asm b/apps/basic/glue.asm index 8c409cb..db70b93 100644 --- a/apps/basic/glue.asm +++ b/apps/basic/glue.asm @@ -17,6 +17,8 @@ .equ EXPR_PARSE parseLiteral .inc "lib/expr.asm" .inc "basic/tok.asm" -.equ BAS_RAMSTART USER_RAMSTART +.equ BUF_RAMSTART USER_RAMSTART +.inc "basic/buf.asm" +.equ BAS_RAMSTART BUF_RAMEND .inc "basic/main.asm" USER_RAMSTART: diff --git a/apps/basic/main.asm b/apps/basic/main.asm index c7b25b6..7afd5fc 100644 --- a/apps/basic/main.asm +++ b/apps/basic/main.asm @@ -12,6 +12,7 @@ ; *** Code *** basStart: ld (BAS_INITSP), sp + call bufInit xor a ld hl, .welcome call printstr @@ -32,12 +33,13 @@ basPrompt: call basDirect jr basPrompt .number: - ; do nothing for now, we only support direct mode. - ld hl, .sNumber - call basPrintLn + push ix \ pop de + call toWS + call rdWS + call bufAdd + jp nz, basERR + call printcrlf jr basPrompt -.sNumber: - .db "A number!", 0 .sPrompt: .db "> ", 0 @@ -104,6 +106,27 @@ basBYE: .sBye: .db "Goodbye!", 0 +basLIST: + call printcrlf + call bufFirst + ret nz +.loop: + ld e, (ix) + ld d, (ix+1) + ld hl, BAS_SCRATCHPAD + call fmtDecimal + call printstr + ld a, ' ' + call stdioPutC + push ix \ pop hl + inc hl \ inc hl \ inc hl + call printstr + call printcrlf + call bufNext + jr z, .loop + ret + + basPRINT: call parseExpr jp nz, basERR @@ -116,6 +139,8 @@ basPRINT: basCmds1: .dw basBYE .db "bye", 0, 0, 0 + .dw basLIST + .db "list", 0, 0 ; statements basCmds2: .dw basPRINT diff --git a/apps/basic/tok.asm b/apps/basic/tok.asm index b933794..d073898 100644 --- a/apps/basic/tok.asm +++ b/apps/basic/tok.asm @@ -46,3 +46,11 @@ fnWSIdx: pop bc pop hl ret + +; Advance HL to the next whitespace or to the end of string. +toWS: + ld a, (hl) + call isSep + ret z + inc hl + jr toWS