2 ; _printf: Basic layer for all printf type functions.
4 ; Ullrich von Bassewitz, 21.10.2000
9 .import popax, pushax, pusheax, decsp6, push1, axlong, axulong
11 .import _strlower, _strlen
12 .importzp sp, ptr1, ptr2, tmp1, regbank, sreg
16 ; ----------------------------------------------------------------------------
17 ; We will store variables into the register bank in the zeropage. Define
18 ; equates for these variables.
20 ArgList = regbank+0 ; Argument list pointer
21 Format = regbank+2 ; Format string
22 OutData = regbank+4 ; Function parameters
24 ; ----------------------------------------------------------------------------
25 ; Other zero page cells
33 ; ----------------------------------------------------------------------------
34 ; Get one character from the format string and increment the pointer. Will
46 ; ----------------------------------------------------------------------------
47 ; Output a pad character: outfunc (d, &padchar, 1)
52 ; ----------------------------------------------------------------------------
53 ; Call the output function with one character in A
62 jmp CallOutFunc ; fout (OutData, &CharArg, 1)
64 ; ----------------------------------------------------------------------------
65 ; Decrement the argument list pointer by 2
75 ; ----------------------------------------------------------------------------
76 ; Get an unsigned int or long argument depending on the IsLong flag.
79 lda IsLong ; Check flag
80 bne GetLongArg ; Long sets all
81 jsr GetIntArg ; Get an integer argument
82 jmp axulong ; Convert to unsigned long
84 ; ----------------------------------------------------------------------------
85 ; Get an signed int or long argument depending on the IsLong flag.
88 lda IsLong ; Check flag
89 bne GetLongArg ; Long sets all
90 jsr GetIntArg ; Get an integer argument
91 jmp axlong ; Convert to long
93 ; ----------------------------------------------------------------------------
94 ; Get a long argument from the argument list. Returns 0 in Y.
97 jsr GetIntArg ; Get high word
101 ; Run into GetIntArg fetching the low word
103 ; ----------------------------------------------------------------------------
104 ; Get an integer argument from the argument list. Returns 0 in Y.
115 ; ----------------------------------------------------------------------------
116 ; Read an integer from the format string. Will return zero in Y.
121 sty ptr1+1 ; Start with zero
122 @Loop: lda (Format),y ; Get format string character
123 sub #'0' ; Make number from ascii digit
124 bcc @L9 ; Jump if done
126 bcs @L9 ; Jump if done
128 ; Skip the digit character
132 ; Add the digit to the value we have in ptr1
134 pha ; Save digit value
140 rol ptr1+1 ; * 4, assume carry clear
147 rol ptr1+1 ; * 10, assume carry clear
149 adc ptr1 ; Add digit value
153 bcs @Loop ; Branch always
155 ; We're done converting
158 ldx ptr1+1 ; Load result
162 ; ----------------------------------------------------------------------------
163 ; Put a character into the argument buffer and increment the buffer index
170 ; ----------------------------------------------------------------------------
171 ; Get a pointer to the current buffer end and push it onto the stack
181 ; ----------------------------------------------------------------------------
182 ; Push OutData onto the software stack
189 ; ----------------------------------------------------------------------------
190 ; Output Width pad characters
202 ; ----------------------------------------------------------------------------
203 ; Output the argument itself: outfunc (d, str, arglen);
216 ; ----------------------------------------------------------------------------
217 ; ltoa: Wrapper for _ltoa that pushes all arguments
219 ltoa: sty Base ; Save base
220 jsr pusheax ; Push value
221 jsr PushBufPtr ; Push the buffer pointer...
222 lda Base ; Restore base
223 jmp _ltoa ; ultoa (l, s, base);
226 ; ----------------------------------------------------------------------------
227 ; ultoa: Wrapper for _ultoa that pushes all arguments
229 ultoa: sty Base ; Save base
230 jsr pusheax ; Push value
231 jsr PushBufPtr ; Push the buffer pointer...
232 lda Base ; Restore base
233 jmp _ultoa ; ultoa (l, s, base);
236 ; ----------------------------------------------------------------------------
241 ; Save the register bank variables into the save area
243 pha ; Save low byte of ap
250 ; Get the parameters from the stack
252 pla ; Restore low byte of ap
253 sta ArgList ; Argument list pointer
256 jsr popax ; Format string
260 jsr popax ; Output descriptor
264 ; Initialize the output counter in the output descriptor to zero
272 ; Get the output function from the output descriptor and remember it
281 ; Start parsing the format string
284 lda Format ; Remember current format pointer
290 @L1: lda (Format),y ; Get next char
291 beq @L2 ; Jump on end of string
292 cmp #'%' ; Format spec?
296 inc Format+1 ; Bump high byte of pointer
297 bne @L1 ; Branch always
299 ; Found a '%' character or end of string. Update the Format pointer so it is
300 ; current (points to this character).
302 @L2: tya ; Low byte of offset
308 ; Calculate, how many characters must be output. Beware: This number may
309 ; be zero. A still contains the low byte of the pointer.
316 ora FCount ; Is the result zero?
317 beq @L4 ; Jump if yes
319 ; Output the characters that we have until now. To make the call to out
320 ; faster, build the stack frame by hand (don't use pushax)
341 jsr CallOutFunc ; Call the output function
343 ; We're back from out(), or we didn't call it. Check for end of string.
345 @L4: jsr GetFormatChar ; Get one char, zero in Y
346 tax ; End of format string reached?
347 bne NotDone ; End not reached
349 ; End of format string reached. Restore the zeropage registers and return.
358 ; Still a valid format character. Check for '%' and a '%%' sequence. Output
359 ; anything that is not a format specifier. On intro, Y is zero.
364 lda (Format),y ; Check for "%%"
366 bne FormatSpec ; Jump if really a format specifier
367 jsr IncFormatPtr ; Skip the second '%'
368 @L1: jsr Output1 ; Output the character...
369 jmp MainLoop ; ...and continue
371 ; We have a real format specifier
372 ; Format is: %[flags][width][.precision][mod]type
373 ; Y is zero on entry.
377 ; Initialize the flags
381 @L1: sta FormatVars,x
385 ; Start with reading the flags if there are any. X is $FF which is used
389 lda (Format),y ; Get next char...
409 @L4: jsr IncFormatPtr
410 jmp ReadFlags ; ...and start over
412 ; Done with flags, read the pad char. Y is still zero if we come here.
420 lda (Format),y ; Read current for later
423 ; Read the width. Even here, Y is still zero. A contains the current character
424 ; from the format string
430 jsr GetIntArg ; Width is an additional argument
433 @L1: jsr ReadInt ; Read integer from format string...
435 stx Width+1 ; ...and remember in Width
437 ; Read the precision. Even here, Y is still zero.
439 sty Prec ; Assume Precision is zero
441 lda (Format),y ; Load next format string char
442 cmp #'.' ; Precision given?
443 bne ReadMod ; Branch if no precision given
446 jsr IncFormatPtr ; Skip the '.'
448 cmp #'*' ; Variable precision?
450 jsr IncFormatPtr ; Skip the '*'
451 jsr GetIntArg ; Get integer argument
454 @L1: jsr ReadInt ; Read integer from format string
458 ; Read the modifiers. Y is still zero.
462 cmp #'z' ; size_t - same as unsigned
464 cmp #'h' ; short - same as int
466 cmp #'t' ; ptrdiff_t - same as int
468 cmp #'j' ; intmax_t/uintmax_t - same as long
470 cmp #'L' ; long double
476 @L2: jsr IncFormatPtr
479 ; Initialize the argument buffer pointers. We use a static buffer (ArgBuf) to
480 ; assemble strings. A zero page index (BufIdx) is used to keep the current
481 ; write position. A pointer to the buffer (Str) is used to point to the the
482 ; argument in case we will not use the buffer but a user supplied string.
483 ; Y is zero when we come here.
486 sty BufIdx ; Clear BufIdx
492 ; Skip the current format character, then check it (current char in A)
503 jsr GetIntArg ; Get the argument (promoted to int)
504 sta Buf ; Place it as zero terminated string...
506 sta Buf+1 ; ...into the buffer
520 lda AddBlank ; Add a blank for positives?
523 @L2: lda AddSign ; Add a plus for positives (precedence)?
530 jsr GetSignedArg ; Get argument as a long
531 ldy sreg+1 ; Check sign
538 @Int1: ldy #10 ; Base
539 jsr ltoa ; Push arguments, call _ltoa
542 ; Is it a count pseudo format?
548 ; It is a count pseudo argument
552 stx ptr1+1 ; Get user supplied pointer
554 lda (OutData),y ; Low byte of OutData->ccount
557 lda (OutData),y ; High byte of OutData->ccount
561 ; Check for an octal digit
567 ; Integer in octal representation
569 jsr GetSignedArg ; Get argument as a long
570 ldy AltForm ; Alternative form?
571 beq @Oct1 ; Jump if no
572 pha ; Save low byte of value
578 ora Prec+1 ; Check if value or Prec != 0
582 pla ; Restore low byte
584 @Oct1: ldy #8 ; Load base
585 jsr ltoa ; Push arguments, call _ltoa
588 ; Check for a pointer specifier (%p)
594 ; It's a pointer. Use %#x conversion
597 stx IsLong ; IsLong = 0;
599 stx AltForm ; AltForm = 1;
601 bne IsHex ; Branch always
603 ; Check for a string specifier (%s)
611 jsr GetIntArg ; Get 16bit argument
616 ; Check for an unsigned integer (%u)
622 ; It's an unsigned integer
624 jsr GetUnsignedArg ; Get argument as unsigned long
626 jsr ultoa ; Push arguments, call _ultoa
629 ; Check for a hexadecimal integer (%x)
637 ; Hexadecimal integer
639 IsHex: pha ; Save the format spec
647 @L1: jsr GetUnsignedArg ; Get argument as an unsigned long
649 jsr ultoa ; Push arguments, call _ultoa
651 pla ; Get the format spec
652 cmp #'x' ; Lower case?
656 jsr _strlower ; Make characters lower case
659 ; Unknown format character, skip it
664 ; We have the argument, do argument string formatting
668 ; ArgLen = strlen (Str);
672 jsr _strlen ; Get length of argument
676 ; if (Prec && Prec < ArgLen) ArgLen = Prec;
690 ; if (Width > ArgLen) {
691 ; Width -= ArgLen; /* padcount */
695 ; Since width is used as a counter below, calculate -(width+1)
712 ; /* Do padding on the left side if needed */
714 ; /* argument right justified */
716 ; fout (d, &padchar, 1);
725 ; Output the argument itself
729 ; /* Output right padding bytes if needed */
731 ; /* argument left justified */
733 ; fout (d, &padchar, 1);
742 ; Done, parse next chars from format string
747 ; ----------------------------------------------------------------------------
748 ; Local data (all static)
752 ; Save area for the zero page registers
755 ; One character argument for OutFunc
769 BufIdx: .byte 0 ; Argument string pointer
770 FormatVarSize = * - FormatVars
772 ; Argument buffer and pointer
779 ; Stuff from OutData. Is used as a vector and must be aligned
780 CallOutFunc: jmp $0000