diff --git a/CODE.md b/CODE.md index 2f7dc27..83b06d9 100644 --- a/CODE.md +++ b/CODE.md @@ -51,6 +51,10 @@ 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. +Another important note is that routines returning success with Z generally don't +preserve AF: too complicated. But otherwise, AF is often preserved. For example, +register fiddling routines in core try to preserve AF. + ## Z for success The vast majority of routines use the Z flag to indicate success. When Z is set, @@ -102,4 +106,12 @@ comments. Example: pop af ; <-- lvl 1 I think that this should do the trick, so I'll do this consistently from now on. + +## String length + +Pretty much every routine expecting a string have no provision for a string +that doesn't have null termination within 0xff bytes. Treat strings of such +lengths with extra precaution and distrust proper handling of existing routines +for those strings. + [zasm]: ../apps/zasm/README.md diff --git a/apps/basic/buf.asm b/apps/basic/buf.asm index 3421f5d..a44224a 100644 --- a/apps/basic/buf.asm +++ b/apps/basic/buf.asm @@ -1,97 +1,117 @@ ; *** Consts *** +; maximum number of lines (line number maximum, however, is always 0xffff) +.equ BUF_MAXLINES 0x100 +; Size of the string pool .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 +; A pointer to the first free line +.equ BUF_LFREE BUF_RAMSTART +; A pointer to the first free byte in the pool +.equ BUF_PFREE @+2 +; The line index. Each record consists of 4 bytes: 2 for line number, +; 2 for pointer to string in pool. Kept in order of line numbers. +.equ BUF_LINES @+2 +; The line pool. A list of null terminated strings. BUF_LINES records point +; to those strings. +.equ BUF_POOL @+BUF_MAXLINES*4 .equ BUF_RAMEND @+BUF_POOLSIZE bufInit: + ld hl, BUF_LINES + ld (BUF_LFREE), hl ld hl, BUF_POOL - ld (BUF_FREE), hl + ld (BUF_PFREE), hl ret -; Add line at (HL) with line number DE to the pool. The string at (HL) should +; Add line at (HL) with line number DE to the buffer. 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. +; Error conditions are: +; * not enough space in the pool +; * not enough space in the line index bufAdd: - push hl ; --> lvl 1 - push de ; --> lvl 2 - ; First step: see if we're within the pool's bounds + exx ; preserve HL and DE + ; First step: do we have index space? + ld hl, (BUF_LFREE) + ld de, BUF_POOL + or a ; reset carry + sbc hl, de + exx ; restore + ; no carry? HL >= BUF_POOL, error. Z already unset + ret nc + ; Second step: see if we're within the pool's bounds call strlen inc a ; strlen doesn't include line termination - ld hl, (BUF_FREE) + exx ; preserve HL and DE + ld hl, (BUF_PFREE) call addHL - ; add overhead (3b) - inc hl \ inc hl \ inc hl ld de, BUF_RAMEND sbc hl, de + exx ; restore ; 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 + ret nc + ; We have enough space. + ; Third step: set line index data + push de ; --> lvl 1 + push hl ; --> lvl 2 + ld hl, (BUF_LFREE) + ld (hl), e + inc hl + ld (hl), d + inc hl + ld de, (BUF_PFREE) 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 + ld (BUF_LFREE), hl + pop hl \ push hl ; <-- lvl 2, restore and preserve + + ; Fourth step: copy string to pool + ld de, (BUF_PFREE) 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 + ld (BUF_PFREE), de + pop hl ; <-- lvl 2 + pop de ; <-- lvl 1 ret -; Set IX to point to the first valid line we have in the pool. -; Error if the pool is empty. -; Z for success. +; Set IX to point to the beginning of the pool. +; Z set if (IX) is a valid line, unset if the pool is empty. bufFirst: - ld a, (BUF_POOL+2) - or a - jp z, unsetZ - ld ix, BUF_POOL - xor a ; set Z - ret + ld ix, BUF_LINES + jp bufEOF -; Given a valid line record in IX, move IX to the next valid line. +; Given a valid line record in IX, move IX to the next 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. +; calling. 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 + inc ix \ inc ix \ inc ix \ inc ix + jp bufEOF + +; Returns whether line index at IX is past the end of file, that is, +; whether IX == (BUF_LFREE) +; Z is set when not EOF, unset when EOF. +bufEOF: + push hl + push de + push ix \ pop hl + ld de, (BUF_LFREE) + sbc hl, de + jr z, .empty + cp a ; ensure Z +.end: + pop de + pop hl + ret +.empty: + call unsetZ + jr .end + +; Given a line index in (IX), set HL to its associated string pointer. +bufStr: + ld l, (ix+2) + ld h, (ix+3) ret diff --git a/apps/basic/main.asm b/apps/basic/main.asm index 7afd5fc..7729e26 100644 --- a/apps/basic/main.asm +++ b/apps/basic/main.asm @@ -118,8 +118,7 @@ basLIST: call printstr ld a, ' ' call stdioPutC - push ix \ pop hl - inc hl \ inc hl \ inc hl + call bufStr call printstr call printcrlf call bufNext