]> git.sur5r.net Git - cc65/blob - libsrc/common/_printf.s
d60ebdfe9cb0bce91979446993ed492634db05ad
[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, push1, axlong, axulong
10         .import         __ctype
11         .import         _ltoa, _ultoa
12         .import         _strlower, _strlen
13         .import         jmpvec
14         .importzp       sp, ptr1, tmp1, regbank, sreg
15
16         .macpack        generic
17
18 ; ----------------------------------------------------------------------------
19 ; We will store variables into the register bank in the zeropage. Define
20 ; equates for these variables.
21
22 ArgList         = regbank+0             ; Argument list pointer
23 Format          = regbank+2             ; Format string
24 OutData         = regbank+4             ; Function parameters
25
26 ; ----------------------------------------------------------------------------
27 ; Other zero page cells
28
29 Base            = ptr1
30
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     (OutFunc)       ; 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     (OutFunc)
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         ldx     #5
245 Save:   lda     regbank,x
246         sta     RegSave,x
247         dex
248         bpl     Save
249
250 ; Get the parameters from the stack
251
252         jsr     popax                   ; Argument list pointer
253         sta     ArgList
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     OutFunc
277         iny
278         lda     (OutData),y
279         sta     OutFunc+1
280
281 ; Start parsing the format string
282
283 MainLoop:
284         jsr     GetFormatChar           ; Get one char, zero in Y
285         tax                             ; End of format string reached?
286         bne     NotDone                 ; Continue of end not reached
287
288 ; End of format string reached. Restore the zeropage registers and return.
289
290         ldx     #5
291 Rest:   lda     RegSave,x
292         sta     regbank,x
293         dex
294         bpl     Rest
295         rts
296
297 ; Still a valid format character. Check for '%' and a '%%' sequence. Output
298 ; anything that is not a format specifier. On intro, Y is zero.
299
300 NotDone:
301         cmp     #'%'
302         bne     @L1
303         lda     (Format),y              ; Check for "%%"
304         cmp     #'%'
305         bne     FormatSpec              ; Jump if really a format specifier
306         jsr     IncFormatPtr            ; Skip the second '%'
307 @L1:    jsr     Output1                 ; Output the character...
308         jmp     MainLoop                ; ...and continue
309
310 ; We have a real format specifier
311 ; Format is: %[flags][width][.precision][mod]type
312 ; Y is zero on entry.
313
314 FormatSpec:
315
316 ; Initialize the flags
317
318         lda     #0
319         ldx     #FormatVarSize-1
320 @L1:    sta     FormatVars,x
321         dex
322         bpl     @L1
323
324 ; Start with reading the flags if there are any. X is $FF which is used
325 ; for "true"
326
327 ReadFlags:
328         lda     (Format),y              ; Get next char...
329         cmp     #'-'
330         bne     @L1
331         stx     LeftJust
332         beq     @L4
333
334 @L1:    cmp     #'+'
335         bne     @L2
336         stx     AddSign
337         beq     @L4
338
339 @L2:    cmp     #' '
340         bne     @L3
341         stx     AddBlank
342         beq     @L4
343
344 @L3:    cmp     #'#'
345         bne     ReadPadding
346         stx     AltForm
347
348 @L4:    jsr     IncFormatPtr
349         jmp     ReadFlags               ; ...and start over
350
351 ; Done with flags, read the pad char. Y is still zero if we come here.
352
353 ReadPadding:
354         ldx     #' '                    ; PadChar
355         cmp     #'0'
356         bne     @L1
357         tax                             ; PadChar is '0'
358         jsr     IncFormatPtr
359         lda     (Format),y              ; Read current for later
360 @L1:    stx     PadChar
361
362 ; Read the width. Even here, Y is still zero. A contains the current character
363 ; from the format string
364
365 ReadWidth:
366         cmp     #'*'
367         bne     @L1
368         jsr     IncFormatPtr
369         jsr     GetIntArg               ; Width is an additional argument
370         jmp     @L2
371
372 @L1:    jsr     ReadInt                 ; Read integer from format string...
373 @L2:    sta     Width
374         stx     Width+1                 ; ...and remember in Width
375
376 ; Read the precision. Even here, Y is still zero.
377
378         sty     Prec                    ; Assume Precision is zero
379         sty     Prec+1
380         lda     (Format),y              ; Load next format string char
381         cmp     #'.'                    ; Precision given?
382         bne     ReadMod                 ; Branch if no precision given
383
384 ReadPrec:
385         jsr     IncFormatPtr            ; Skip the '.'
386         lda     (Format),y
387         cmp     #'*'                    ; Variable precision?
388         bne     @L1
389         jsr     IncFormatPtr            ; Skip the '*'
390         jsr     GetIntArg               ; Get integer argument
391         jmp     @L2
392
393 @L1:    jsr     ReadInt                 ; Read integer from format string
394 @L2:    sta     Prec
395         stx     Prec+1
396
397 ; Read the modifiers. Y is still zero.
398
399 ReadMod:
400         lda     (Format),y
401         cmp     #'F'
402         beq     @L1                     ; Read and ignore this one
403         cmp     #'N'
404         beq     @L1                     ; Read and ignore this one
405         cmp     #'h'
406         beq     @L1                     ; Read and ignore this one
407         cmp     #'L'
408         beq     @L1                     ; Read and ignore this one
409         cmp     #'l'
410         bne     DoFormat
411         lda     #$FF
412         sta     IsLong
413 @L1:    jsr     IncFormatPtr
414         jmp     ReadMod
415
416 ; Initialize the argument buffer pointers. We use a static buffer (ArgBuf) to
417 ; assemble strings. A zero page index (BufIdx) is used to keep the current
418 ; write position. A pointer to the buffer (Str) is used to point to the the
419 ; argument in case we will not use the buffer but a user supplied string.
420 ; Y is zero when we come here.
421
422 DoFormat:
423         sty     BufIdx                  ; Clear BufIdx
424         ldx     #<Buf
425         stx     Str
426         ldx     #>Buf
427         stx     Str+1
428
429 ; Skip the current format character, then check it (current char in A)
430
431         jsr     IncFormatPtr
432
433 ; Is it a character?
434
435         cmp     #'c'
436         bne     CheckInt
437
438 ; It is a character
439
440         jsr     GetIntArg               ; Get the argument (promoted to int)
441         sta     Buf                     ; Place it as zero terminated string...
442         lda     #0
443         sta     Buf+1                   ; ...into the buffer
444         jmp     HaveArg                 ; Done
445
446 ; Is it an integer?
447
448 CheckInt:
449         cmp     #'d'
450         beq     @L1
451         cmp     #'i'
452         bne     CheckCount
453
454 ; It is an integer
455
456 @L1:    ldx     #0
457         lda     AddBlank                ; Add a blank for positives?
458         beq     @L2                     ; Jump if no
459         ldx     #' '
460 @L2:    lda     AddSign                 ; Add a plus for positives (precedence)?
461         beq     @L3
462         ldx     #'+'
463 @L3:    stx     Leader
464
465 ; Integer argument
466
467         jsr     GetSignedArg                    ; Get argument as a long
468         ldy     sreg+1                  ; Check sign
469         bmi     @Int1
470         ldy     Leader
471         beq     @Int1
472         sty     Buf
473         inc     BufIdx
474
475 @Int1:  ldy     #10                     ; Base
476         jsr     ltoa                    ; Push arguments, call _ltoa
477         jmp     HaveArg
478
479 ; Is it a count pseudo format?
480
481 CheckCount:
482         cmp     #'n'
483         bne     CheckOctal
484
485 ; It is a count pseudo argument
486
487         jsr     GetIntArg
488         sta     ptr1
489         stx     ptr1+1                  ; Get user supplied pointer
490         ldy     #0
491         lda     (OutData),y             ; Low byte of OutData->ccount
492         sta     (ptr1),y
493         iny
494         lda     (OutData),y             ; High byte of OutData->ccount
495         sta     (ptr1),y
496         jmp     MainLoop                ; Done
497
498 ; Check for an octal digit
499
500 CheckOctal:
501         cmp     #'o'
502         bne     CheckString
503
504 ; Integer in octal representation
505
506         jsr     GetSignedArg            ; Get argument as a long
507         ldy     AltForm                 ; Alternative form?
508         beq     @Oct1                   ; Jump if no
509         pha                             ; Save low byte of value
510         stx     tmp1
511         ora     tmp1
512         ora     sreg
513         ora     sreg+1
514         ora     Prec
515         ora     Prec+1                  ; Check if value or Prec != 0
516         beq     @Oct1
517         lda     #'0'
518         jsr     PutBuf
519         pla                             ; Restore low byte
520
521 @Oct1:  ldy     #8                      ; Load base
522         jsr     ltoa                    ; Push arguments, call _ltoa
523         jmp     HaveArg
524
525 ; Check for a string specifier (%s)
526
527 CheckString:
528         cmp     #'s'
529         bne     CheckUnsigned
530
531 ; It's a string
532
533         jsr     GetIntArg               ; Get 16bit argument
534         sta     Str
535         stx     Str+1
536         jmp     HaveArg
537
538 ; Check for an unsigned integer (%u)
539
540 CheckUnsigned:
541         cmp     #'u'
542         bne     CheckHex
543
544 ; It's an unsigned integer
545
546         jsr     GetUnsignedArg          ; Get argument as unsigned long
547         ldy     #10                     ; Load base
548         jsr     ultoa                   ; Push arguments, call _ultoa
549         jmp     HaveArg
550
551 ; Check for a hexadecimal integer (%x)
552
553 CheckHex:
554         cmp     #'x'
555         beq     @IsHex
556         cmp     #'X'
557         bne     UnknownFormat
558
559 ; Hexadecimal integer
560
561 @IsHex: pha                             ; Save the format spec
562         lda     AltForm
563         beq     @L1
564         lda     #'0'
565         jsr     PutBuf
566         lda     #'X'
567         jsr     PutBuf
568
569 @L1:    jsr     GetUnsignedArg          ; Get argument as an unsigned long
570         ldy     #16                     ; Load base
571         jsr     ultoa                   ; Push arguments, call _ultoa
572
573         pla                             ; Get the format spec
574         cmp     #'x'                    ; Lower case?
575         bne     @L2
576         lda     Str
577         ldx     Str+1
578         jsr     _strlower               ; Make characters lower case
579 @L2:    jmp     HaveArg
580
581 ; Unsigned format character, skip it
582
583 UnknownFormat:
584         jmp     MainLoop
585
586 ; We have the argument, do argument string formatting
587
588 HaveArg:
589
590 ; ArgLen = strlen (Str);
591
592         lda     Str
593         ldx     Str+1
594         jsr     _strlen                 ; Get length of argument
595         sta     ArgLen
596         stx     ArgLen+1
597
598 ; if (Prec && Prec < ArgLen) ArgLen = Prec;
599
600         lda     Prec
601         ora     Prec+1
602         beq     @L1
603         ldx     Prec
604         cpx     ArgLen
605         lda     Prec+1
606         tay
607         sbc     ArgLen+1
608         bcc     @L1
609         stx     ArgLen
610         sty     ArgLen+1
611
612 ;  if (Width > ArgLen) {
613 ;      Width -= ArgLen;                 /* padcount */
614 ;  } else {
615 ;      Width = 0;
616 ;  }
617 ; Since width is used as a counter below, calculate -(width+1)
618
619 @L1:    sec
620         lda     Width
621         sbc     ArgLen
622         tax
623         lda     Width+1
624         sbc     ArgLen+1
625         bcs     @L2
626         lda     #0
627         tax
628 @L2:    eor     #$FF
629         sta     Width+1
630         txa
631         eor     #$FF
632         sta     Width
633
634 ;  /* Do padding on the left side if needed */
635 ;  if (!leftjust) {
636 ;      /* argument right justified */
637 ;      while (width) {
638 ;         fout (d, &padchar, 1);
639 ;         --width;
640 ;      }
641 ;  }
642
643         lda     LeftJust
644         bne     @L3
645         jsr     OutputPadding
646
647 ; Output the argument itself
648
649 @L3:    jsr     OutputArg
650
651 ;  /* Output right padding bytes if needed */
652 ;  if (leftjust) {
653 ;      /* argument left justified */
654 ;      while (width) {
655 ;         fout (d, &padchar, 1);
656 ;         --width;
657 ;      }
658 ;  }
659
660         lda     LeftJust
661         beq     @L4
662         jsr     OutputPadding
663
664 ; Done, parse next chars from format string
665
666 @L4:    jmp     MainLoop
667
668
669 ; ----------------------------------------------------------------------------
670 ; Local data (all static)
671
672 .bss
673
674 ; Save area for the zero page registers
675 RegSave:        .res    6
676
677 ; Stuff from OutData. Is used as a vector and must be aligned
678 OutFunc:        .word   0
679
680 ; One character argument for OutFunc
681 CharArg:        .byte   0
682
683 ; Format variables
684 FormatVars:
685 LeftJust:       .byte   0
686 AddSign:        .byte   0
687 AddBlank:       .byte   0
688 AltForm:        .byte   0
689 PadChar:        .byte   0
690 Width:          .word   0
691 Prec:           .word   0
692 IsLong:         .byte   0
693 Leader:         .byte   0
694 BufIdx:         .byte   0       ; Argument string pointer
695 FormatVarSize   = * - FormatVars
696
697 ; Argument buffer and pointer
698 Buf:            .res    20
699 Str:            .word   0
700 ArgLen:         .res    2
701