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