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