2 ; _printf: Basic layer for all printf type functions.
4 ; Ullrich von Bassewitz, 2000-10-21
7 .include "zeropage.inc"
11 .import popax, pushax, pusheax, decsp6, push1, axlong, axulong
13 .import _strlower, _strlen
17 ; ----------------------------------------------------------------------------
18 ; We will store variables into the register bank in the zeropage. Define
19 ; equates for these variables.
21 ArgList = regbank+0 ; Argument list pointer
22 Format = regbank+2 ; Format string
23 OutData = regbank+4 ; Function parameters
25 ; ----------------------------------------------------------------------------
26 ; Other zero page cells
34 ; ----------------------------------------------------------------------------
35 ; Get one character from the format string and increment the pointer. Will
47 ; ----------------------------------------------------------------------------
48 ; Output a pad character: outfunc (d, &padchar, 1)
53 ; ----------------------------------------------------------------------------
54 ; Call the output function with one character in A
63 jmp CallOutFunc ; fout (OutData, &CharArg, 1)
65 ; ----------------------------------------------------------------------------
66 ; Decrement the argument list pointer by 2
76 ; ----------------------------------------------------------------------------
77 ; Get an unsigned int or long argument depending on the IsLong flag.
80 lda IsLong ; Check flag
81 bne GetLongArg ; Long sets all
82 jsr GetIntArg ; Get an integer argument
83 jmp axulong ; Convert to unsigned long
85 ; ----------------------------------------------------------------------------
86 ; Get an signed int or long argument depending on the IsLong flag.
89 lda IsLong ; Check flag
90 bne GetLongArg ; Long sets all
91 jsr GetIntArg ; Get an integer argument
92 jmp axlong ; Convert to long
94 ; ----------------------------------------------------------------------------
95 ; Get a long argument from the argument list. Returns 0 in Y.
98 jsr GetIntArg ; Get high word
102 ; Run into GetIntArg fetching the low word
104 ; ----------------------------------------------------------------------------
105 ; Get an integer argument from the argument list. Returns 0 in Y.
116 ; ----------------------------------------------------------------------------
117 ; Read an integer from the format string. Will return zero in Y.
122 sty ptr1+1 ; Start with zero
123 @Loop: lda (Format),y ; Get format string character
124 sub #'0' ; Make number from ascii digit
125 bcc @L9 ; Jump if done
127 bcs @L9 ; Jump if done
129 ; Skip the digit character
133 ; Add the digit to the value we have in ptr1
135 pha ; Save digit value
141 rol ptr1+1 ; * 4, assume carry clear
148 rol ptr1+1 ; * 10, assume carry clear
150 adc ptr1 ; Add digit value
154 bcs @Loop ; Branch always
156 ; We're done converting
159 ldx ptr1+1 ; Load result
163 ; ----------------------------------------------------------------------------
164 ; Put a character into the argument buffer and increment the buffer index
171 ; ----------------------------------------------------------------------------
172 ; Get a pointer to the current buffer end and push it onto the stack
182 ; ----------------------------------------------------------------------------
183 ; Push OutData onto the software stack
190 ; ----------------------------------------------------------------------------
191 ; Output Width pad characters
203 ; ----------------------------------------------------------------------------
204 ; Output the argument itself: outfunc (d, str, arglen);
217 ; ----------------------------------------------------------------------------
218 ; ltoa: Wrapper for _ltoa that pushes all arguments
220 ltoa: sty Base ; Save base
221 jsr pusheax ; Push value
222 jsr PushBufPtr ; Push the buffer pointer...
223 lda Base ; Restore base
224 jmp _ltoa ; ultoa (l, s, base);
227 ; ----------------------------------------------------------------------------
228 ; ultoa: Wrapper for _ultoa that pushes all arguments
230 ultoa: sty Base ; Save base
231 jsr pusheax ; Push value
232 jsr PushBufPtr ; Push the buffer pointer...
233 lda Base ; Restore base
234 jmp _ultoa ; ultoa (l, s, base);
237 ; ----------------------------------------------------------------------------
242 ; Save the register bank variables into the save area
244 pha ; Save low byte of ap
251 ; Get the parameters from the stack
253 pla ; Restore low byte of ap
254 sta ArgList ; Argument list pointer
257 jsr popax ; Format string
261 jsr popax ; Output descriptor
265 ; Initialize the output counter in the output descriptor to zero
273 ; Get the output function from the output descriptor and remember it
282 ; Start parsing the format string
285 lda Format ; Remember current format pointer
291 @L1: lda (Format),y ; Get next char
292 beq @L2 ; Jump on end of string
293 cmp #'%' ; Format spec?
297 inc Format+1 ; Bump high byte of pointer
298 bne @L1 ; Branch always
300 ; Found a '%' character or end of string. Update the Format pointer so it is
301 ; current (points to this character).
303 @L2: tya ; Low byte of offset
309 ; Calculate, how many characters must be output. Beware: This number may
310 ; be zero. A still contains the low byte of the pointer.
317 ora FCount ; Is the result zero?
318 beq @L4 ; Jump if yes
320 ; Output the characters that we have until now. To make the call to out
321 ; faster, build the stack frame by hand (don't use pushax)
342 jsr CallOutFunc ; Call the output function
344 ; We're back from out(), or we didn't call it. Check for end of string.
346 @L4: jsr GetFormatChar ; Get one char, zero in Y
347 tax ; End of format string reached?
348 bne NotDone ; End not reached
350 ; End of format string reached. Restore the zeropage registers and return.
359 ; Still a valid format character. Check for '%' and a '%%' sequence. Output
360 ; anything that is not a format specifier. On intro, Y is zero.
365 lda (Format),y ; Check for "%%"
367 bne FormatSpec ; Jump if really a format specifier
368 jsr IncFormatPtr ; Skip the second '%'
369 @L1: jsr Output1 ; Output the character...
370 jmp MainLoop ; ...and continue
372 ; We have a real format specifier
373 ; Format is: %[flags][width][.precision][mod]type
374 ; Y is zero on entry.
378 ; Initialize the flags
382 @L1: sta FormatVars,x
386 ; Start with reading the flags if there are any. X is $FF which is used
390 lda (Format),y ; Get next char...
410 @L4: jsr IncFormatPtr
411 jmp ReadFlags ; ...and start over
413 ; Done with flags, read the pad char. Y is still zero if we come here.
421 lda (Format),y ; Read current for later
424 ; Read the width. Even here, Y is still zero. A contains the current character
425 ; from the format string
431 jsr GetIntArg ; Width is an additional argument
434 @L1: jsr ReadInt ; Read integer from format string...
436 stx Width+1 ; ...and remember in Width
438 ; Read the precision. Even here, Y is still zero.
440 sty Prec ; Assume Precision is zero
442 lda (Format),y ; Load next format string char
443 cmp #'.' ; Precision given?
444 bne ReadMod ; Branch if no precision given
447 jsr IncFormatPtr ; Skip the '.'
449 cmp #'*' ; Variable precision?
451 jsr IncFormatPtr ; Skip the '*'
452 jsr GetIntArg ; Get integer argument
455 @L1: jsr ReadInt ; Read integer from format string
459 ; Read the modifiers. Y is still zero.
463 cmp #'z' ; size_t - same as unsigned
465 cmp #'h' ; short - same as int
467 cmp #'t' ; ptrdiff_t - same as int
469 cmp #'j' ; intmax_t/uintmax_t - same as long
471 cmp #'L' ; long double
477 @L2: jsr IncFormatPtr
480 ; Initialize the argument buffer pointers. We use a static buffer (ArgBuf) to
481 ; assemble strings. A zero page index (BufIdx) is used to keep the current
482 ; write position. A pointer to the buffer (Str) is used to point to the the
483 ; argument in case we will not use the buffer but a user supplied string.
484 ; Y is zero when we come here.
487 sty BufIdx ; Clear BufIdx
493 ; Skip the current format character, then check it (current char in A)
504 jsr GetIntArg ; Get the argument (promoted to int)
505 sta Buf ; Place it as zero terminated string...
507 sta Buf+1 ; ...into the buffer
521 lda AddBlank ; Add a blank for positives?
524 @L2: lda AddSign ; Add a plus for positives (precedence)?
531 jsr GetSignedArg ; Get argument as a long
532 ldy sreg+1 ; Check sign
539 @Int1: ldy #10 ; Base
540 jsr ltoa ; Push arguments, call _ltoa
543 ; Is it a count pseudo format?
549 ; It is a count pseudo argument
553 stx ptr1+1 ; Get user supplied pointer
555 lda (OutData),y ; Low byte of OutData->ccount
558 lda (OutData),y ; High byte of OutData->ccount
562 ; Check for an octal digit
568 ; Integer in octal representation
570 jsr GetSignedArg ; Get argument as a long
571 ldy AltForm ; Alternative form?
572 beq @Oct1 ; Jump if no
573 pha ; Save low byte of value
579 ora Prec+1 ; Check if value or Prec != 0
583 pla ; Restore low byte
585 @Oct1: ldy #8 ; Load base
586 jsr ltoa ; Push arguments, call _ltoa
589 ; Check for a pointer specifier (%p)
595 ; It's a pointer. Use %#x conversion
598 stx IsLong ; IsLong = 0;
600 stx AltForm ; AltForm = 1;
602 bne IsHex ; Branch always
604 ; Check for a string specifier (%s)
612 jsr GetIntArg ; Get 16bit argument
617 ; Check for an unsigned integer (%u)
623 ; It's an unsigned integer
625 jsr GetUnsignedArg ; Get argument as unsigned long
627 jsr ultoa ; Push arguments, call _ultoa
630 ; Check for a hexadecimal integer (%x)
638 ; Hexadecimal integer
640 IsHex: pha ; Save the format spec
648 @L1: jsr GetUnsignedArg ; Get argument as an unsigned long
650 jsr ultoa ; Push arguments, call _ultoa
652 pla ; Get the format spec
653 cmp #'x' ; Lower case?
657 jsr _strlower ; Make characters lower case
660 ; Unknown format character, skip it
665 ; We have the argument, do argument string formatting
669 ; ArgLen = strlen (Str);
673 jsr _strlen ; Get length of argument
677 ; if (Prec && Prec < ArgLen) ArgLen = Prec;
691 ; if (Width > ArgLen) {
692 ; Width -= ArgLen; /* padcount */
696 ; Since width is used as a counter below, calculate -(width+1)
713 ; /* Do padding on the left side if needed */
715 ; /* argument right justified */
717 ; fout (d, &padchar, 1);
726 ; Output the argument itself
730 ; /* Output right padding bytes if needed */
732 ; /* argument left justified */
734 ; fout (d, &padchar, 1);
743 ; Done, parse next chars from format string
748 ; ----------------------------------------------------------------------------
749 ; Local data (all static)
753 ; Save area for the zero page registers
754 RegSave: .res regbanksize
756 ; One character argument for OutFunc
770 BufIdx: .byte 0 ; Argument string pointer
771 FormatVarSize = * - FormatVars
773 ; Argument buffer and pointer
780 ; Stuff from OutData. Is used as a vector and must be aligned
781 CallOutFunc: jmp $0000