562 lines
17 KiB
ArmAsm
562 lines
17 KiB
ArmAsm
;==============================================================================
|
||
; 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
|