;============================================================================== ; txtlib.s — Text buffer library (hand-optimised Z80, sdcccall(1)) ; ; Replaces lib/txtlib.c. Every public function matches the ABI ; generated by SDCC for the prototypes in inc/txtlib.h. ; ; Buffer layout: buf[0] = max capacity, buf[1] = current length, ; buf[2..] = character data. ; ; sdcccall(1) register conventions: ; 1st ptr/u16 → HL 2nd ptr/u16 → DE ; 1st u8 → A 2nd u8 (when 1st is u8) → L ; Further args → stack (callee-cleans) ; Return u8 → A Return u16 → HL ;============================================================================== .module txtlib_mod .globl _buf_append_str .globl _buf_append_char .globl _buf_append_u8_hex .globl _buf_append_u16_dec .globl _buf_append_u32_dec .globl _buf_append_mmss .globl _print_line .globl _wc_prsrw_attr ; from wc_api.s .area _CODE ;---------------------------------------------------------------------- ; Helper: nibble (0–15 in A) → ASCII char ('0'–'F') in A ;---------------------------------------------------------------------- _nib2asc: cp a, #10 jr c, 001$ add a, #7 001$: add a, #0x30 ret ;====================================================================== ; uint8_t buf_append_str(char *buf, const char *src) ; HL=buf DE=src → A=added (no stack params) ;====================================================================== _buf_append_str:: ld c, (hl) ; C = max inc hl ld b, (hl) ; B = curlen ld a, c sub a, b ; A = remaining jr z, bas_z jr c, bas_z push hl ; save &buf[1] ld c, a ; C = remaining inc hl ; HL → buf[2] ld a, b add a, l ld l, a jr nc, bas_nc inc h bas_nc: ld b, #0 ; B = added bas_lp: ld a, (de) or a, a jr z, bas_dn ld (hl), a inc hl inc de inc b dec c jr nz, bas_lp bas_dn: pop hl ; &buf[1] ld a, (hl) add a, b ld (hl), a ld a, b ret bas_z: xor a, a ret ;====================================================================== ; uint8_t buf_append_char(char *buf, char c) ; HL=buf stack: c(1) → A=0|1 callee-cleans 1 ;====================================================================== _buf_append_char:: push ix ld ix, #0 add ix, sp ; 4(ix) = c ld c, (hl) ; max inc hl ld a, (hl) ; curlen cp a, c jr nc, bac_f ld e, a inc a ld (hl), a ; buf[1]++ inc hl ; → buf[2] ld a, e add a, l ld l, a jr nc, bac_nc inc h bac_nc: ld a, 4 (ix) ld (hl), a ld a, #1 jr bac_r bac_f: xor a, a bac_r: pop ix pop hl inc sp ; skip c (1 byte) jp (hl) ;====================================================================== ; uint8_t buf_append_u8_hex(char *buf, uint8_t val) ; HL=buf stack: val(1) → A=0|2 callee-cleans 1 ;====================================================================== _buf_append_u8_hex:: push ix ld ix, #0 add ix, sp ; 4(ix) = val ld c, (hl) ; max inc hl ld a, c sub a, (hl) ; remaining cp a, #2 jr c, bh8_f ld b, (hl) ; curlen ld a, b add a, #2 ld (hl), a ; buf[1] += 2 inc hl ld a, b add a, l ld l, a jr nc, bh8_nc inc h bh8_nc: ld a, 4 (ix) rrca rrca rrca rrca and a, #0x0F call _nib2asc ld (hl), a inc hl ld a, 4 (ix) and a, #0x0F call _nib2asc ld (hl), a ld a, #2 jr bh8_r bh8_f: xor a, a bh8_r: pop ix pop hl inc sp jp (hl) ;---------------------------------------------------------------------- ; Shared helper: emit one decimal digit. ; A = digit (0–9) ; B = "added" counter (0 = not-yet-started → suppress leading 0) ; C = remaining buffer space ; IY = destination pointer (advanced on write) ;---------------------------------------------------------------------- _emit_digit: push af or a, b ; z iff digit==0 AND not started jr z, ed_skip ld a, c or a, a jr z, ed_skip ; no space dec c pop af add a, #0x30 ld 0 (iy), a inc iy inc b ret ed_skip: pop af ret ;---------------------------------------------------------------------- ; Shared helper: extract one 32-bit decimal digit via repeated ; subtraction of a 4-byte constant taken from inline data after CALL. ; ; Usage: call _d32_digit ; .db lo, b1, b2, hi ; 32-bit subtrahend (LE) ; ; In: DEHL = 32-bit value, -3(ix) = scratch, ; B = started/added, C = remaining, IY = dest ptr ; Out: DEHL = remainder, digit emitted via _emit_digit ;---------------------------------------------------------------------- _d32_digit: ex (sp), hl ; HL = inline-data ptr, (SP)=val_lo ld a, (hl) ld (d32c0 + 1), a inc hl ld a, (hl) ld (d32c1 + 1), a inc hl ld a, (hl) ld (d32c2 + 1), a inc hl ld a, (hl) ld (d32c3 + 1), a inc hl ; HL past data = true ret addr ex (sp), hl ; (SP)=true ret, HL=val_lo restored ld -3 (ix), #0xFF d32d_lp:inc -3 (ix) push de push hl ld a, l d32c0: sub a, #0x00 ; patched: subtrahend byte 0 ld l, a ld a, h d32c1: sbc a, #0x00 ; patched: byte 1 ld h, a ld a, e d32c2: sbc a, #0x00 ; patched: byte 2 ld e, a ld a, d d32c3: sbc a, #0x00 ; patched: byte 3 ld d, a jr c, d32d_un pop af pop af jr d32d_lp d32d_un:pop hl pop de ld a, -3 (ix) jp _emit_digit ; tail-call, ret goes past inline data ;---------------------------------------------------------------------- ; Shared 16-bit decimal division: 10000/1000/100/10/ones. ; Entry _div_10k_ones: full 16-bit decimal (10000..1). ; Entry _div_1k_ones: skip 10000, do 1000..1. ; In: DE=value, B=added counter, C=remaining, IY=dst. ; Out: B=updated, DE=remainder. A,HL clobbered. ;---------------------------------------------------------------------- _div_10k_ones: ;-- 10000 (neg 0xD8F0) -- ld a, #0xFF _d10k: inc a ld hl, #0xD8F0 add hl, de jr nc, _d10ke ex de, hl jr _d10k _d10ke: call _emit_digit _div_1k_ones: ;-- 1000 (neg 0xFC18) -- ld a, #0xFF _d1k: inc a ld hl, #0xFC18 add hl, de jr nc, _d1ke ex de, hl jr _d1k _d1ke: call _emit_digit ;-- 100 (neg 0xFF9C) -- ld a, #0xFF _d100: inc a ld hl, #0xFF9C add hl, de jr nc, _d100e ex de, hl jr _d100 _d100e: call _emit_digit ;-- 10 (neg 0xFFF6) -- ld a, #0xFF _d10: inc a ld hl, #0xFFF6 add hl, de jr nc, _d10e ex de, hl jr _d10 _d10e: call _emit_digit ;-- ones (always emit) -- ld a, c or a, a ret z ld a, e add a, #0x30 ld 0 (iy), a inc iy inc b ret ;====================================================================== ; uint8_t buf_append_u16_dec(char *buf, uint16_t val) ; HL=buf DE=val → A=added (no stack params) ;====================================================================== _buf_append_u16_dec:: push iy ld c, (hl) inc hl ld a, c sub a, (hl) jr z, d16_z jr c, d16_z ld c, a ; C = remaining ld b, (hl) ; B = curlen (temp) push hl ; save &buf[1] inc hl ld a, b add a, l ld l, a jr nc, d16_nc inc h d16_nc: push hl pop iy ; IY = dst ld b, #0 ; B = added/started call _div_10k_ones pop hl ; &buf[1] ld a, (hl) add a, b ld (hl), a ld a, b pop iy ret d16_z: xor a, a pop iy ret ;====================================================================== ; uint8_t buf_append_u32_dec(char *buf, uint32_t val) ; HL=buf stack: val(4) → A=added callee-cleans 4 ; 4(ix)=byte0(LSB) 5(ix)=byte1 6(ix)=byte2 7(ix)=byte3(MSB) ;====================================================================== _buf_append_u32_dec:: push ix ld ix, #0 add ix, sp push iy ; -2,-1(ix) push af ; -4,-3(ix) scratch push af ; -6,-5(ix) scratch ; -3(ix) used as digit counter per power ld c, (hl) inc hl ld a, c sub a, (hl) jp z, d32_z jp c, d32_z ld c, a ; C = remaining ld b, (hl) ; B = curlen (temp) push hl ; save &buf[1] → -8,-7(ix) inc hl ld a, b add a, l ld l, a jr nc, d32_nc inc h d32_nc: push hl pop iy ; IY = dst ; Load 32-bit val into DEHL ld l, 4 (ix) ld h, 5 (ix) ld e, 6 (ix) ld d, 7 (ix) ld b, #0 ; B = added/started ;-- 1,000,000 / 100,000 / 10,000 via shared subroutine -- call _d32_digit .db 0x40, 0x42, 0x0F, 0x00 ; 1,000,000 call _d32_digit .db 0xA0, 0x86, 0x01, 0x00 ; 100,000 call _d32_digit .db 0x10, 0x27, 0x00, 0x00 ; 10,000 ;-- remainder < 10000, fits in HL → move to DE -- ex de, hl call _div_1k_ones pop hl ; &buf[1] ld a, (hl) add a, b ld (hl), a ld a, b ; return value d32_exit: ; clean locals, restore, callee-clean 4 pop de ; eat -6,-5 pop de ; eat -4,-3 pop iy pop ix pop hl ; ret addr pop de ; skip val lo pop de ; skip val hi jp (hl) d32_z: xor a, a jr d32_exit ;====================================================================== ; void print_line(wc_window_t *win, uint8_t y, const char *buf, ; uint8_t attr) ; HL=win stack: y(1) buf(2) attr(1) callee-cleans 4 ; Prints buf content + spaces padding to fill content width. ;====================================================================== _print_line:: push ix ld ix, #0 add ix, sp ; 4(ix)=y 5,6(ix)=buf 7(ix)=attr push af ; -2,-1(ix): cw at -1, slen at -2 push hl ; -4,-3(ix): win ;-- cw = get_content_width(win) -- ld de, #4 add hl, de ld a, (hl) or a, a jr nz, pl_nz ld a, #96 pl_nz: sub a, #4 ld -1 (ix), a ;-- slen = buf[1] -- ld l, 5 (ix) ld h, 6 (ix) inc hl ld a, (hl) ld -2 (ix), a ;-- print_len = min(slen, cw) -- cp a, -1 (ix) jr c, pl_min ld a, -1 (ix) pl_min: ld c, a ; C = print_len ;-- 1st wc_prsrw_attr(win, data, y, 2, print_len, attr) -- ld a, 7 (ix) push af inc sp ld b, #0 push bc ld h, #2 ld l, 4 (ix) push hl ; DE = buf + 2 ld a, 5 (ix) add a, #2 ld e, a ld a, 6 (ix) adc a, #0 ld d, a ; HL = win ld l, -4 (ix) ld h, -3 (ix) call _wc_prsrw_attr ;-- padding? -- ld a, -2 (ix) cp a, -1 (ix) jr nc, pl_done ; pad = cw − slen ld a, -1 (ix) sub a, -2 (ix) ld c, a ; Fill buf[2..2+pad-1] with 0x20 (runtime space fill) ld l, 5 (ix) ld h, 6 (ix) inc hl inc hl ; HL = &buf[2] ld b, c ; B = pad count ld a, #0x20 pf_fill:ld (hl), a inc hl djnz pf_fill ; B=0 after loop ;-- 2nd wc_prsrw_attr(win, &buf[2], y, 2+slen, pad, attr) -- ld a, 7 (ix) push af inc sp push bc ; len=pad (B=0, C=pad) ld a, -2 (ix) add a, #2 ld h, a ; x = 2 + slen ld l, 4 (ix) ; y push hl ld e, 5 (ix) ld d, 6 (ix) inc de inc de ; DE = &buf[2] ld l, -4 (ix) ld h, -3 (ix) call _wc_prsrw_attr pl_done: ld sp, ix pop ix pop hl pop af pop af ; callee-clean 4 jp (hl) ;---------------------------------------------------------------------- ; buf_append_mmss(buf, sec) — append "MM:SS" (5 chars) to buffer ; ; sdcccall(1): HL = buf, DE = sec (uint16_t, 0..5999) ; No division — subtraction loops only (~30 bytes code). ;---------------------------------------------------------------------- _buf_append_mmss: push hl pop ix ; IX = buf ; DE = total seconds. Divide by 60 via subtraction → B=min, E=sec ld b, #0 mmss_d60: ld a, e sub a, #60 ld c, a ld a, d sbc a, #0 jr c, mmss_d60_done ld d, a ld e, c inc b jr mmss_d60 mmss_d60_done: ; B = minutes, E = seconds push de ; save seconds (mmss_2dig clobbers DE) ld a, b call mmss_2dig ; appends 2 digits of A ; Append ':' ld a, 1 (ix) ld c, a ld b, #0 inc a ld 1 (ix), a push ix pop hl add hl, bc inc hl ; skip buf[0] (max) inc hl ; skip buf[1] (len) ld (hl), #0x3A ; ':' ; Append seconds pop de ; restore E = seconds ld a, e ; fall through to mmss_2dig — but IX is still buf ; call mmss_2dig call mmss_2dig ret ; Subroutine: append 2 ASCII digits of A (0..99) to buf at IX ; Destroys A, C, B, HL mmss_2dig: ld c, #0x30 ; tens digit = '0' mmss_t10: sub a, #10 jr c, mmss_t10_done inc c jr mmss_t10 mmss_t10_done: add a, #10 ; undo last sub add a, #0x30 ; units → ASCII ld b, a ; save units ; Append tens (C) then units (B) ld a, 1 (ix) ; len ld l, a ld h, #0 add a, #2 ld 1 (ix), a ; len += 2 push ix pop de ; DE = buf base add hl, de inc hl ; skip buf[0] (max) inc hl ; skip buf[1] (len) ld (hl), c ; tens inc hl ld (hl), b ; units ret