]> git.sur5r.net Git - cc65/blob - libsrc/common/_printf.s
Removed (pretty inconsistently used) tab chars from source code base.
[cc65] / libsrc / common / _printf.s
1 ;
2 ; _printf: Basic layer for all printf type functions.
3 ;
4 ; Ullrich von Bassewitz, 2000-10-21
5 ;
6                                    
7         .include        "zeropage.inc"
8
9         .export         __printf
10
11         .import         popax, pushax, pusheax, decsp6, push1, axlong, axulong
12         .import         _ltoa, _ultoa
13         .import         _strlower, _strlen
14
15         .macpack        generic
16
17 ; ----------------------------------------------------------------------------
18 ; We will store variables into the register bank in the zeropage. Define
19 ; equates for these variables.
20
21 ArgList         = regbank+0             ; Argument list pointer
22 Format          = regbank+2             ; Format string
23 OutData         = regbank+4             ; Function parameters
24
25 ; ----------------------------------------------------------------------------
26 ; Other zero page cells
27
28 Base            = ptr1
29 FSave           = ptr1
30 FCount          = ptr2
31
32 .code
33
34 ; ----------------------------------------------------------------------------
35 ; Get one character from the format string and increment the pointer. Will
36 ; return zero in Y.
37
38 GetFormatChar:
39         ldy     #0
40         lda     (Format),y
41 IncFormatPtr:
42         inc     Format
43         bne     @L1
44         inc     Format+1
45 @L1:    rts
46
47 ; ----------------------------------------------------------------------------
48 ; Output a pad character: outfunc (d, &padchar, 1)
49
50 OutputPadChar:
51         lda     PadChar
52
53 ; ----------------------------------------------------------------------------
54 ; Call the output function with one character in A
55
56 Output1:
57         sta     CharArg
58         jsr     PushOutData
59         lda     #<CharArg
60         ldx     #>CharArg
61         jsr     pushax
62         jsr     push1
63         jmp     CallOutFunc     ; fout (OutData, &CharArg, 1)
64
65 ; ----------------------------------------------------------------------------
66 ; Decrement the argument list pointer by 2
67
68 DecArgList2:
69         lda     ArgList
70         sub     #2
71         sta     ArgList
72         bcs     @L1
73         dec     ArgList+1
74 @L1:    rts
75
76 ; ----------------------------------------------------------------------------
77 ; Get an unsigned int or long argument depending on the IsLong flag.
78
79 GetUnsignedArg:
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
84
85 ; ----------------------------------------------------------------------------
86 ; Get an signed int or long argument depending on the IsLong flag.
87
88 GetSignedArg:
89         lda     IsLong                  ; Check flag
90         bne     GetLongArg              ; Long sets all
91         jsr     GetIntArg               ; Get an integer argument
92         jmp     axlong                  ; Convert to long
93
94 ; ----------------------------------------------------------------------------
95 ; Get a long argument from the argument list. Returns 0 in Y.
96
97 GetLongArg:
98         jsr     GetIntArg               ; Get high word
99         sta     sreg
100         stx     sreg+1
101
102 ; Run into GetIntArg fetching the low word
103
104 ; ----------------------------------------------------------------------------
105 ; Get an integer argument from the argument list. Returns 0 in Y.
106
107 GetIntArg:
108         jsr     DecArgList2
109         ldy     #1
110         lda     (ArgList),y
111         tax
112         dey
113         lda     (ArgList),y
114         rts
115
116 ; ----------------------------------------------------------------------------
117 ; Read an integer from the format string. Will return zero in Y.
118
119 ReadInt:
120         ldy     #0
121         sty     ptr1
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
126         cmp     #9+1
127         bcs     @L9                     ; Jump if done
128
129 ; Skip the digit character
130
131         jsr     IncFormatPtr
132
133 ; Add the digit to the value we have in ptr1
134
135         pha                             ; Save digit value
136         lda     ptr1
137         ldx     ptr1+1
138         asl     ptr1
139         rol     ptr1+1                  ; * 2
140         asl     ptr1
141         rol     ptr1+1                  ; * 4, assume carry clear
142         adc     ptr1
143         sta     ptr1
144         txa
145         adc     ptr1+1
146         sta     ptr1+1                  ; * 5
147         asl     ptr1
148         rol     ptr1+1                  ; * 10, assume carry clear
149         pla
150         adc     ptr1                    ; Add digit value
151         sta     ptr1
152         bcc     @Loop
153         inc     ptr1+1
154         bcs     @Loop                   ; Branch always
155
156 ; We're done converting
157
158 @L9:    lda     ptr1
159         ldx     ptr1+1                  ; Load result
160         rts
161
162
163 ; ----------------------------------------------------------------------------
164 ; Put a character into the argument buffer and increment the buffer index
165
166 PutBuf: ldy     BufIdx
167         inc     BufIdx
168         sta     Buf,y
169         rts
170
171 ; ----------------------------------------------------------------------------
172 ; Get a pointer to the current buffer end and push it onto the stack
173
174 PushBufPtr:
175         lda     #<Buf
176         ldx     #>Buf
177         add     BufIdx
178         bcc     @L1
179         inx
180 @L1:    jmp     pushax
181
182 ; ----------------------------------------------------------------------------
183 ; Push OutData onto the software stack
184
185 PushOutData:
186         lda     OutData
187         ldx     OutData+1
188         jmp     pushax
189
190 ; ----------------------------------------------------------------------------
191 ; Output Width pad characters
192 ;
193
194 PadLoop:
195         jsr     OutputPadChar
196 OutputPadding:
197         inc     Width
198         bne     PadLoop
199         inc     Width+1
200         bne     PadLoop
201         rts
202
203 ; ----------------------------------------------------------------------------
204 ; Output the argument itself: outfunc (d, str, arglen);
205 ;
206
207 OutputArg:
208         jsr     PushOutData
209         lda     Str
210         ldx     Str+1
211         jsr     pushax
212         lda     ArgLen
213         ldx     ArgLen+1
214         jsr     pushax
215         jmp     CallOutFunc
216
217 ; ----------------------------------------------------------------------------
218 ; ltoa: Wrapper for _ltoa that pushes all arguments
219
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);
225
226
227 ; ----------------------------------------------------------------------------
228 ; ultoa: Wrapper for _ultoa that pushes all arguments
229
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);
235
236
237 ; ----------------------------------------------------------------------------
238 ;
239
240 __printf:
241
242 ; Save the register bank variables into the save area
243
244         pha                             ; Save low byte of ap
245         ldy     #5
246 Save:   lda     regbank,y
247         sta     RegSave,y
248         dey
249         bpl     Save
250
251 ; Get the parameters from the stack
252
253         pla                             ; Restore low byte of ap
254         sta     ArgList                 ; Argument list pointer
255         stx     ArgList+1
256
257         jsr     popax                   ; Format string
258         sta     Format
259         stx     Format+1
260
261         jsr     popax                   ; Output descriptor
262         sta     OutData
263         stx     OutData+1
264
265 ; Initialize the output counter in the output descriptor to zero
266
267         lda     #0
268         tay
269         sta     (OutData),y
270         iny
271         sta     (OutData),y
272
273 ; Get the output function from the output descriptor and remember it
274
275         iny
276         lda     (OutData),y
277         sta     CallOutFunc+1
278         iny
279         lda     (OutData),y
280         sta     CallOutFunc+2
281
282 ; Start parsing the format string
283
284 MainLoop:
285         lda     Format                  ; Remember current format pointer
286         sta     FSave
287         lda     Format+1
288         sta     FSave+1
289
290         ldy     #0                      ; Index
291 @L1:    lda     (Format),y              ; Get next char
292         beq     @L2                     ; Jump on end of string
293         cmp     #'%'                    ; Format spec?
294         beq     @L2
295         iny                             ; Bump pointer
296         bne     @L1
297         inc     Format+1                ; Bump high byte of pointer
298         bne     @L1                     ; Branch always
299
300 ; Found a '%' character or end of string. Update the Format pointer so it is
301 ; current (points to this character).
302
303 @L2:    tya                             ; Low byte of offset
304         add     Format
305         sta     Format
306         bcc     @L3
307         inc     Format+1
308
309 ; Calculate, how many characters must be output. Beware: This number may
310 ; be zero. A still contains the low byte of the pointer.
311
312 @L3:    sub     FSave
313         sta     FCount
314         lda     Format+1
315         sbc     FSave+1
316         sta     FCount+1
317         ora     FCount                  ; Is the result zero?
318         beq     @L4                     ; Jump if yes
319
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)
322
323         jsr     decsp6                  ; 3 args
324         ldy     #5
325         lda     OutData+1
326         sta     (sp),y
327         dey
328         lda     OutData
329         sta     (sp),y
330         dey
331         lda     FSave+1
332         sta     (sp),y
333         dey
334         lda     FSave
335         sta     (sp),y
336         dey
337         lda     FCount+1
338         sta     (sp),y
339         dey
340         lda     FCount
341         sta     (sp),y
342         jsr     CallOutFunc             ; Call the output function
343
344 ; We're back from out(), or we didn't call it. Check for end of string.
345
346 @L4:    jsr     GetFormatChar           ; Get one char, zero in Y
347         tax                             ; End of format string reached?
348         bne     NotDone                 ; End not reached
349
350 ; End of format string reached. Restore the zeropage registers and return.
351
352         ldx     #5
353 Rest:   lda     RegSave,x
354         sta     regbank,x
355         dex
356         bpl     Rest
357         rts
358
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.
361
362 NotDone:
363         cmp     #'%'
364         bne     @L1
365         lda     (Format),y              ; Check for "%%"
366         cmp     #'%'
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
371
372 ; We have a real format specifier
373 ; Format is: %[flags][width][.precision][mod]type
374 ; Y is zero on entry.
375
376 FormatSpec:
377
378 ; Initialize the flags
379
380         lda     #0
381         ldx     #FormatVarSize-1
382 @L1:    sta     FormatVars,x
383         dex
384         bpl     @L1
385
386 ; Start with reading the flags if there are any. X is $FF which is used
387 ; for "true"
388
389 ReadFlags:
390         lda     (Format),y              ; Get next char...
391         cmp     #'-'
392         bne     @L1
393         stx     LeftJust
394         beq     @L4
395
396 @L1:    cmp     #'+'
397         bne     @L2
398         stx     AddSign
399         beq     @L4
400
401 @L2:    cmp     #' '
402         bne     @L3
403         stx     AddBlank
404         beq     @L4
405
406 @L3:    cmp     #'#'
407         bne     ReadPadding
408         stx     AltForm
409
410 @L4:    jsr     IncFormatPtr
411         jmp     ReadFlags               ; ...and start over
412
413 ; Done with flags, read the pad char. Y is still zero if we come here.
414
415 ReadPadding:
416         ldx     #' '                    ; PadChar
417         cmp     #'0'
418         bne     @L1
419         tax                             ; PadChar is '0'
420         jsr     IncFormatPtr
421         lda     (Format),y              ; Read current for later
422 @L1:    stx     PadChar
423
424 ; Read the width. Even here, Y is still zero. A contains the current character
425 ; from the format string
426
427 ReadWidth:
428         cmp     #'*'
429         bne     @L1
430         jsr     IncFormatPtr
431         jsr     GetIntArg               ; Width is an additional argument
432         jmp     @L2
433
434 @L1:    jsr     ReadInt                 ; Read integer from format string...
435 @L2:    sta     Width
436         stx     Width+1                 ; ...and remember in Width
437
438 ; Read the precision. Even here, Y is still zero.
439
440         sty     Prec                    ; Assume Precision is zero
441         sty     Prec+1
442         lda     (Format),y              ; Load next format string char
443         cmp     #'.'                    ; Precision given?
444         bne     ReadMod                 ; Branch if no precision given
445
446 ReadPrec:
447         jsr     IncFormatPtr            ; Skip the '.'
448         lda     (Format),y
449         cmp     #'*'                    ; Variable precision?
450         bne     @L1
451         jsr     IncFormatPtr            ; Skip the '*'
452         jsr     GetIntArg               ; Get integer argument
453         jmp     @L2
454
455 @L1:    jsr     ReadInt                 ; Read integer from format string
456 @L2:    sta     Prec
457         stx     Prec+1
458
459 ; Read the modifiers. Y is still zero.
460
461 ReadMod:
462         lda     (Format),y
463         cmp     #'z'                    ; size_t - same as unsigned
464         beq     @L2
465         cmp     #'h'                    ; short - same as int
466         beq     @L2
467         cmp     #'t'                    ; ptrdiff_t - same as int
468         beq     @L2
469         cmp     #'j'                    ; intmax_t/uintmax_t - same as long
470         beq     @L1
471         cmp     #'L'                    ; long double
472         beq     @L1
473         cmp     #'l'                    ; long int
474         bne     DoFormat
475 @L1:    lda     #$FF
476         sta     IsLong
477 @L2:    jsr     IncFormatPtr
478         jmp     ReadMod
479
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.
485
486 DoFormat:
487         sty     BufIdx                  ; Clear BufIdx
488         ldx     #<Buf
489         stx     Str
490         ldx     #>Buf
491         stx     Str+1
492
493 ; Skip the current format character, then check it (current char in A)
494
495         jsr     IncFormatPtr
496
497 ; Is it a character?
498
499         cmp     #'c'
500         bne     CheckInt
501
502 ; It is a character
503
504         jsr     GetIntArg               ; Get the argument (promoted to int)
505         sta     Buf                     ; Place it as zero terminated string...
506         lda     #0
507         sta     Buf+1                   ; ...into the buffer
508         jmp     HaveArg                 ; Done
509
510 ; Is it an integer?
511
512 CheckInt:
513         cmp     #'d'
514         beq     @L1
515         cmp     #'i'
516         bne     CheckCount
517
518 ; It is an integer
519
520 @L1:    ldx     #0
521         lda     AddBlank                ; Add a blank for positives?
522         beq     @L2                     ; Jump if no
523         ldx     #' '
524 @L2:    lda     AddSign                 ; Add a plus for positives (precedence)?
525         beq     @L3
526         ldx     #'+'
527 @L3:    stx     Leader
528
529 ; Integer argument
530
531         jsr     GetSignedArg            ; Get argument as a long
532         ldy     sreg+1                  ; Check sign
533         bmi     @Int1
534         ldy     Leader
535         beq     @Int1
536         sty     Buf
537         inc     BufIdx
538
539 @Int1:  ldy     #10                     ; Base
540         jsr     ltoa                    ; Push arguments, call _ltoa
541         jmp     HaveArg
542
543 ; Is it a count pseudo format?
544
545 CheckCount:
546         cmp     #'n'
547         bne     CheckOctal
548
549 ; It is a count pseudo argument
550
551         jsr     GetIntArg
552         sta     ptr1
553         stx     ptr1+1                  ; Get user supplied pointer
554         ldy     #0
555         lda     (OutData),y             ; Low byte of OutData->ccount
556         sta     (ptr1),y
557         iny
558         lda     (OutData),y             ; High byte of OutData->ccount
559         sta     (ptr1),y
560         jmp     MainLoop                ; Done
561
562 ; Check for an octal digit
563
564 CheckOctal:
565         cmp     #'o'
566         bne     CheckPointer
567
568 ; Integer in octal representation
569
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
574         stx     tmp1
575         ora     tmp1
576         ora     sreg
577         ora     sreg+1
578         ora     Prec
579         ora     Prec+1                  ; Check if value or Prec != 0
580         beq     @Oct1
581         lda     #'0'
582         jsr     PutBuf
583         pla                             ; Restore low byte
584
585 @Oct1:  ldy     #8                      ; Load base
586         jsr     ltoa                    ; Push arguments, call _ltoa
587         jmp     HaveArg
588
589 ; Check for a pointer specifier (%p)
590
591 CheckPointer:
592         cmp     #'p'
593         bne     CheckString
594
595 ; It's a pointer. Use %#x conversion
596
597         ldx     #0
598         stx     IsLong                  ; IsLong = 0;
599         inx
600         stx     AltForm                 ; AltForm = 1;
601         lda     #'x'
602         bne     IsHex                   ; Branch always
603
604 ; Check for a string specifier (%s)
605
606 CheckString:
607         cmp     #'s'
608         bne     CheckUnsigned
609
610 ; It's a string
611
612         jsr     GetIntArg               ; Get 16bit argument
613         sta     Str
614         stx     Str+1
615         jmp     HaveArg
616
617 ; Check for an unsigned integer (%u)
618
619 CheckUnsigned:
620         cmp     #'u'
621         bne     CheckHex
622
623 ; It's an unsigned integer
624
625         jsr     GetUnsignedArg          ; Get argument as unsigned long
626         ldy     #10                     ; Load base
627         jsr     ultoa                   ; Push arguments, call _ultoa
628         jmp     HaveArg
629
630 ; Check for a hexadecimal integer (%x)
631
632 CheckHex:
633         cmp     #'x'
634         beq     IsHex
635         cmp     #'X'
636         bne     UnknownFormat
637
638 ; Hexadecimal integer
639
640 IsHex:  pha                             ; Save the format spec
641         lda     AltForm
642         beq     @L1
643         lda     #'0'
644         jsr     PutBuf
645         lda     #'X'
646         jsr     PutBuf
647
648 @L1:    jsr     GetUnsignedArg          ; Get argument as an unsigned long
649         ldy     #16                     ; Load base
650         jsr     ultoa                   ; Push arguments, call _ultoa
651
652         pla                             ; Get the format spec
653         cmp     #'x'                    ; Lower case?
654         bne     @L2
655         lda     Str
656         ldx     Str+1
657         jsr     _strlower               ; Make characters lower case
658 @L2:    jmp     HaveArg
659
660 ; Unknown format character, skip it
661
662 UnknownFormat:
663         jmp     MainLoop
664
665 ; We have the argument, do argument string formatting
666
667 HaveArg:
668
669 ; ArgLen = strlen (Str);
670
671         lda     Str
672         ldx     Str+1
673         jsr     _strlen                 ; Get length of argument
674         sta     ArgLen
675         stx     ArgLen+1
676
677 ; if (Prec && Prec < ArgLen) ArgLen = Prec;
678
679         lda     Prec
680         ora     Prec+1
681         beq     @L1
682         ldx     Prec
683         cpx     ArgLen
684         lda     Prec+1
685         tay
686         sbc     ArgLen+1
687         bcs     @L1
688         stx     ArgLen
689         sty     ArgLen+1
690
691 ;  if (Width > ArgLen) {
692 ;      Width -= ArgLen;                 /* padcount */
693 ;  } else {
694 ;      Width = 0;
695 ;  }
696 ; Since width is used as a counter below, calculate -(width+1)
697
698 @L1:    sec
699         lda     Width
700         sbc     ArgLen
701         tax
702         lda     Width+1
703         sbc     ArgLen+1
704         bcs     @L2
705         lda     #0
706         tax
707 @L2:    eor     #$FF
708         sta     Width+1
709         txa
710         eor     #$FF
711         sta     Width
712
713 ;  /* Do padding on the left side if needed */
714 ;  if (!leftjust) {
715 ;      /* argument right justified */
716 ;      while (width) {
717 ;         fout (d, &padchar, 1);
718 ;         --width;
719 ;      }
720 ;  }
721
722         lda     LeftJust
723         bne     @L3
724         jsr     OutputPadding
725
726 ; Output the argument itself
727
728 @L3:    jsr     OutputArg
729
730 ;  /* Output right padding bytes if needed */
731 ;  if (leftjust) {
732 ;      /* argument left justified */
733 ;      while (width) {
734 ;         fout (d, &padchar, 1);
735 ;         --width;
736 ;      }
737 ;  }
738
739         lda     LeftJust
740         beq     @L4
741         jsr     OutputPadding
742
743 ; Done, parse next chars from format string
744
745 @L4:    jmp     MainLoop
746
747
748 ; ----------------------------------------------------------------------------
749 ; Local data (all static)
750
751 .bss
752
753 ; Save area for the zero page registers
754 RegSave:        .res    regbanksize
755
756 ; One character argument for OutFunc
757 CharArg:        .byte   0
758
759 ; Format variables
760 FormatVars:
761 LeftJust:       .byte   0
762 AddSign:        .byte   0
763 AddBlank:       .byte   0
764 AltForm:        .byte   0
765 PadChar:        .byte   0
766 Width:          .word   0
767 Prec:           .word   0
768 IsLong:         .byte   0
769 Leader:         .byte   0
770 BufIdx:         .byte   0       ; Argument string pointer
771 FormatVarSize   = * - FormatVars
772
773 ; Argument buffer and pointer
774 Buf:            .res    20
775 Str:            .word   0
776 ArgLen:         .res    2
777
778 .data
779
780 ; Stuff from OutData. Is used as a vector and must be aligned
781 CallOutFunc:    jmp     $0000
782
783
784