asm_vsnprintf.asm
bitfixer_bitfixer/dg/z88dk/libsrc/_DEVELOPMENT/stdio/z80/asm_vsnprintf.asm

; ===============================================================
; Jan 2014
; ===============================================================
; 
; int vsnprintf(char *s, size_t n, const char *format, void *arg)
;
; As vfprintf but output is directed to a string of max size n
; characters.  The string is always terminated with '\0' so that
; at most n-1 chars are written.  If n or s equal zero, no output
; is written to s.
;
; Returns the number of characters generated by vfprintf or
; a negative number if a conversion error occurred inside vfprintf.
;
; ===============================================================

SECTION code_clib
SECTION code_stdio

PUBLIC asm_vsnprintf

EXTERN STDIO_MSG_PUTC
EXTERN asm0_vfprintf_unlocked, asm_memset, l_minu_bc_hl, l_neg_hl

asm_vsnprintf:

   ; enter : de  = char *format
   ;         bc  = void *stack_param = arg
   ;         de' = char *s
   ;         bc' = size_t n
   ;
   ; exit  : de  = char *format (next unexamined char)
   ;         hl' = char *s (address of terminating '\0')
   ;         bc' = space remaining in s
   ;
   ;         success
   ;
   ;            hl = number of chars output to string not including '\0'
   ;            carry reset
   ;
   ;         fail
   ;
   ;            hl = - (chars output + 1) < 0
   ;            carry set, errno set as below
   ;
   ;            erange = width or precision out of range
   ;            einval = unknown printf conversion
   ;
   ; note  : High level stdio uses hl' to track number of chars
   ;         written to the stream but modifies no other exx registers
   ;            
   ; uses  : all
   
   exx

   ld a,d
   or e
   jr z, buffer_invalid        ; if s == NULL
   
   ld a,b
   or c
   jr z, buffer_zerolen        ; if n == 0
   
   dec bc                      ; make space for terminating '\0'
   exx

   call output_string          ; output to string
   
   exx
   ex de,hl
   ld (hl),0                   ; terminate string
   exx
   
   ret

buffer_invalid:

   ld c,a
   ld b,a                      ; bc = n = 0                      

buffer_zerolen:

   exx
   
   call output_string          ; output to string
   
   exx
   ex de,hl                    ; hl' = char *s, exit condition
   exx
   
   ret

output_string:

   ; create a fake FILE structure on the stack
   
   ld hl,0
   push hl
   ld hl,$8000 + (vsnprintf_outchar / 256)
   push hl
   ld hl,195 + ((vsnprintf_outchar % 256) * 256)
   push hl
   
   ld ix,0
   add ix,sp                   ; ix = vsnprintf_file *
   
   ; print to string
   
   call asm0_vfprintf_unlocked
   
   ; repair stack
   
   pop bc
   pop bc
   pop bc
   
   ret

vsnprintf_outchar:

   ; vfprintf will generate two messages here
   ; STDIO_MSG_PUTC and STDIO_MSG_WRIT

   cp STDIO_MSG_PUTC
   jr z, _putc
   
_writ:

   ; de  = char *s
   ; bc  = size_t n (remaining)
   ; hl  = length > 0
   ; hl' = void *buffer
   ; bc' = length > 0

   push hl                     ; save length
   
   call l_minu_bc_hl           ; hl = number of bytes to write to s
   
   ld a,h
   or l
   jr z, _writ_0               ; if nothing will be written
   
   push hl                     ; save out_length
   
   sbc hl,bc
   call l_neg_hl               ; hl = n - out_length
   
   ld c,l
   ld b,h                      ; bc = size_t n (remaining)
   
   push de
   exx
   pop de                      ; de = char *s
   pop bc                      ; bc = out_length
   
   ldir
   
   push de
   exx
   pop de
   
   or a
   
_writ_0:

   pop hl                      ; hl = original length from stdio
   ret

_putc:

   ; de  = char *s
   ; bc  = size_t n (remaining)
   ; hl  = number > 0
   ;  e' = char
   ; bc' = number > 0

   dec hl
   
   ld a,h
   or l
   
   inc hl
   
   jr nz, _putc_many

   ld a,b
   or c
   ret z
   
   dec bc
   
   exx
   ld a,e
   exx
   
   ld (de),a
   inc de
   
   ret

_putc_many:

   push hl                     ; save length
   
   call l_minu_bc_hl           ; hl = number of bytes to write to s
   
   ld a,h
   or l
   jr z, _putc_0               ; if nothing will be written
   
   push hl                     ; save out_length
   
   sbc hl,bc
   call l_neg_hl               ; hl = n - out_length
   
   ld c,l
   ld b,h                      ; bc = size_t n (remaining)
   
   push de
   exx
   pop hl                      ; hl = char *s
   pop bc                      ; bc = out_length
   
   call asm_memset
   
   push de
   exx
   pop de
   
_putc_0:

   pop hl                      ; hl = original length from stdio
   ret
