]> git.sur5r.net Git - cc65/blob - libsrc/common/_printf.s
removed some duplicated GEOS conio stuff
[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         __ctype
11         .import         _ltoa, _ultoa
12         .import         _strlower, _strlen
13         .importzp       sp, ptr1, ptr2, tmp1, regbank, sreg
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     #'F'
464         beq     @L1                     ; Read and ignore this one
465         cmp     #'N'
466         beq     @L1                     ; Read and ignore this one
467         cmp     #'h'
468         beq     @L1                     ; Read and ignore this one
469         cmp     #'L'
470         beq     @L1                     ; Read and ignore this one
471         cmp     #'l'
472         bne     DoFormat
473         lda     #$FF
474         sta     IsLong
475 @L1:    jsr     IncFormatPtr
476         jmp     ReadMod
477
478 ; Initialize the argument buffer pointers. We use a static buffer (ArgBuf) to
479 ; assemble strings. A zero page index (BufIdx) is used to keep the current
480 ; write position. A pointer to the buffer (Str) is used to point to the the
481 ; argument in case we will not use the buffer but a user supplied string.
482 ; Y is zero when we come here.
483
484 DoFormat:
485         sty     BufIdx                  ; Clear BufIdx
486         ldx     #<Buf
487         stx     Str
488         ldx     #>Buf
489         stx     Str+1
490
491 ; Skip the current format character, then check it (current char in A)
492
493         jsr     IncFormatPtr
494
495 ; Is it a character?
496
497         cmp     #'c'
498         bne     CheckInt
499
500 ; It is a character
501
502         jsr     GetIntArg               ; Get the argument (promoted to int)
503         sta     Buf                     ; Place it as zero terminated string...
504         lda     #0
505         sta     Buf+1                   ; ...into the buffer
506         jmp     HaveArg                 ; Done
507
508 ; Is it an integer?
509
510 CheckInt:
511         cmp     #'d'
512         beq     @L1
513         cmp     #'i'
514         bne     CheckCount
515
516 ; It is an integer
517
518 @L1:    ldx     #0
519         lda     AddBlank                ; Add a blank for positives?
520         beq     @L2                     ; Jump if no
521         ldx     #' '
522 @L2:    lda     AddSign                 ; Add a plus for positives (precedence)?
523         beq     @L3
524         ldx     #'+'
525 @L3:    stx     Leader
526
527 ; Integer argument
528
529         jsr     GetSignedArg            ; Get argument as a long
530         ldy     sreg+1                  ; Check sign
531         bmi     @Int1
532         ldy     Leader
533         beq     @Int1
534         sty     Buf
535         inc     BufIdx
536
537 @Int1:  ldy     #10                     ; Base
538         jsr     ltoa                    ; Push arguments, call _ltoa
539         jmp     HaveArg
540
541 ; Is it a count pseudo format?
542
543 CheckCount:
544         cmp     #'n'
545         bne     CheckOctal
546
547 ; It is a count pseudo argument
548
549         jsr     GetIntArg
550         sta     ptr1
551         stx     ptr1+1                  ; Get user supplied pointer
552         ldy     #0
553         lda     (OutData),y             ; Low byte of OutData->ccount
554         sta     (ptr1),y
555         iny
556         lda     (OutData),y             ; High byte of OutData->ccount
557         sta     (ptr1),y
558         jmp     MainLoop                ; Done
559
560 ; Check for an octal digit
561
562 CheckOctal:
563         cmp     #'o'
564         bne     CheckPointer
565
566 ; Integer in octal representation
567
568         jsr     GetSignedArg            ; Get argument as a long
569         ldy     AltForm                 ; Alternative form?
570         beq     @Oct1                   ; Jump if no
571         pha                             ; Save low byte of value
572         stx     tmp1
573         ora     tmp1
574         ora     sreg
575         ora     sreg+1
576         ora     Prec
577         ora     Prec+1                  ; Check if value or Prec != 0
578         beq     @Oct1
579         lda     #'0'
580         jsr     PutBuf
581         pla                             ; Restore low byte
582
583 @Oct1:  ldy     #8                      ; Load base
584         jsr     ltoa                    ; Push arguments, call _ltoa
585         jmp     HaveArg
586
587 ; Check for a pointer specifier (%p)
588
589 CheckPointer:
590         cmp     #'p'
591         bne     CheckString
592
593 ; It's a pointer. Use %#x conversion
594
595         ldx     #0
596         stx     IsLong                  ; IsLong = 0;
597         inx
598         stx     AltForm                 ; AltForm = 1;
599         lda     #'x'
600         bne     IsHex                   ; Branch always
601
602 ; Check for a string specifier (%s)
603
604 CheckString:
605         cmp     #'s'
606         bne     CheckUnsigned
607
608 ; It's a string
609
610         jsr     GetIntArg               ; Get 16bit argument
611         sta     Str
612         stx     Str+1
613         jmp     HaveArg
614
615 ; Check for an unsigned integer (%u)
616
617 CheckUnsigned:
618         cmp     #'u'
619         bne     CheckHex
620
621 ; It's an unsigned integer
622
623         jsr     GetUnsignedArg          ; Get argument as unsigned long
624         ldy     #10                     ; Load base
625         jsr     ultoa                   ; Push arguments, call _ultoa
626         jmp     HaveArg
627
628 ; Check for a hexadecimal integer (%x)
629
630 CheckHex:
631         cmp     #'x'
632         beq     IsHex
633         cmp     #'X'
634         bne     UnknownFormat
635
636 ; Hexadecimal integer
637
638 IsHex:  pha                             ; Save the format spec
639         lda     AltForm
640         beq     @L1
641         lda     #'0'
642         jsr     PutBuf
643         lda     #'X'
644         jsr     PutBuf
645
646 @L1:    jsr     GetUnsignedArg          ; Get argument as an unsigned long
647         ldy     #16                     ; Load base
648         jsr     ultoa                   ; Push arguments, call _ultoa
649
650         pla                             ; Get the format spec
651         cmp     #'x'                    ; Lower case?
652         bne     @L2
653         lda     Str
654         ldx     Str+1
655         jsr     _strlower               ; Make characters lower case
656 @L2:    jmp     HaveArg
657
658 ; Unknown format character, skip it
659
660 UnknownFormat:
661         jmp     MainLoop
662
663 ; We have the argument, do argument string formatting
664
665 HaveArg:
666
667 ; ArgLen = strlen (Str);
668
669         lda     Str
670         ldx     Str+1
671         jsr     _strlen                 ; Get length of argument
672         sta     ArgLen
673         stx     ArgLen+1
674
675 ; if (Prec && Prec < ArgLen) ArgLen = Prec;
676
677         lda     Prec
678         ora     Prec+1
679         beq     @L1
680         ldx     Prec
681         cpx     ArgLen
682         lda     Prec+1
683         tay
684         sbc     ArgLen+1
685         bcs     @L1
686         stx     ArgLen
687         sty     ArgLen+1
688
689 ;  if (Width > ArgLen) {
690 ;      Width -= ArgLen;                 /* padcount */
691 ;  } else {
692 ;      Width = 0;
693 ;  }
694 ; Since width is used as a counter below, calculate -(width+1)
695
696 @L1:    sec
697         lda     Width
698         sbc     ArgLen
699         tax
700         lda     Width+1
701         sbc     ArgLen+1
702         bcs     @L2
703         lda     #0
704         tax
705 @L2:    eor     #$FF
706         sta     Width+1
707         txa
708         eor     #$FF
709         sta     Width
710
711 ;  /* Do padding on the left side if needed */
712 ;  if (!leftjust) {
713 ;      /* argument right justified */
714 ;      while (width) {
715 ;         fout (d, &padchar, 1);
716 ;         --width;
717 ;      }
718 ;  }
719
720         lda     LeftJust
721         bne     @L3
722         jsr     OutputPadding
723
724 ; Output the argument itself
725
726 @L3:    jsr     OutputArg
727
728 ;  /* Output right padding bytes if needed */
729 ;  if (leftjust) {
730 ;      /* argument left justified */
731 ;      while (width) {
732 ;         fout (d, &padchar, 1);
733 ;         --width;
734 ;      }
735 ;  }
736
737         lda     LeftJust
738         beq     @L4
739         jsr     OutputPadding
740
741 ; Done, parse next chars from format string
742
743 @L4:    jmp     MainLoop
744
745
746 ; ----------------------------------------------------------------------------
747 ; Local data (all static)
748
749 .bss
750
751 ; Save area for the zero page registers
752 RegSave:        .res    6
753
754 ; One character argument for OutFunc
755 CharArg:        .byte   0
756
757 ; Format variables
758 FormatVars:
759 LeftJust:       .byte   0
760 AddSign:        .byte   0
761 AddBlank:       .byte   0
762 AltForm:        .byte   0
763 PadChar:        .byte   0
764 Width:          .word   0
765 Prec:           .word   0
766 IsLong:         .byte   0
767 Leader:         .byte   0
768 BufIdx:         .byte   0       ; Argument string pointer
769 FormatVarSize   = * - FormatVars
770
771 ; Argument buffer and pointer
772 Buf:            .res    20
773 Str:            .word   0
774 ArgLen:         .res    2
775
776 .data
777
778 ; Stuff from OutData. Is used as a vector and must be aligned
779 CallOutFunc:    jmp     $0000
780
781
782