]> git.sur5r.net Git - cc65/blob - libsrc/common/_printf.s
Added mouse module from C64
[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 CallOutFunc:
64         jmp     (OutFunc)       ; fout (OutData, &CharArg, 1)
65
66 ; ----------------------------------------------------------------------------
67 ; Decrement the argument list pointer by 2
68
69 DecArgList2:
70         lda     ArgList
71         sub     #2
72         sta     ArgList
73         bcs     @L1
74         dec     ArgList+1
75 @L1:    rts
76
77 ; ----------------------------------------------------------------------------
78 ; Get an unsigned int or long argument depending on the IsLong flag.
79
80 GetUnsignedArg:
81         lda     IsLong                  ; Check flag
82         bne     GetLongArg              ; Long sets all
83         jsr     GetIntArg               ; Get an integer argument
84         jmp     axulong                 ; Convert to unsigned long
85
86 ; ----------------------------------------------------------------------------
87 ; Get an signed int or long argument depending on the IsLong flag.
88
89 GetSignedArg:
90         lda     IsLong                  ; Check flag
91         bne     GetLongArg              ; Long sets all
92         jsr     GetIntArg               ; Get an integer argument
93         jmp     axlong                  ; Convert to long
94
95 ; ----------------------------------------------------------------------------
96 ; Get a long argument from the argument list. Returns 0 in Y.
97
98 GetLongArg:
99         jsr     GetIntArg               ; Get high word
100         sta     sreg
101         stx     sreg+1
102
103 ; Run into GetIntArg fetching the low word
104
105 ; ----------------------------------------------------------------------------
106 ; Get an integer argument from the argument list. Returns 0 in Y.
107
108 GetIntArg:
109         jsr     DecArgList2
110         ldy     #1
111         lda     (ArgList),y
112         tax
113         dey
114         lda     (ArgList),y
115         rts
116
117 ; ----------------------------------------------------------------------------
118 ; Read an integer from the format string. Will return zero in Y.
119
120 ReadInt:
121         ldy     #0
122         sty     ptr1
123         sty     ptr1+1                  ; Start with zero
124 @Loop:  lda     (Format),y              ; Get format string character
125         sub     #'0'                    ; Make number from ascii digit
126         bcc     @L9                     ; Jump if done
127         cmp     #9+1
128         bcs     @L9                     ; Jump if done
129
130 ; Skip the digit character
131
132         jsr     IncFormatPtr
133
134 ; Add the digit to the value we have in ptr1
135
136         pha                             ; Save digit value
137         lda     ptr1
138         ldx     ptr1+1
139         asl     ptr1
140         rol     ptr1+1                  ; * 2
141         asl     ptr1
142         rol     ptr1+1                  ; * 4, assume carry clear
143         adc     ptr1
144         sta     ptr1
145         txa
146         adc     ptr1+1
147         sta     ptr1+1                  ; * 5
148         asl     ptr1
149         rol     ptr1+1                  ; * 10, assume carry clear
150         pla
151         adc     ptr1                    ; Add digit value
152         sta     ptr1
153         bcc     @Loop
154         inc     ptr1+1
155         bcs     @Loop                   ; Branch always
156
157 ; We're done converting
158
159 @L9:    lda     ptr1
160         ldx     ptr1+1                  ; Load result
161         rts
162
163
164 ; ----------------------------------------------------------------------------
165 ; Put a character into the argument buffer and increment the buffer index
166
167 PutBuf: ldy     BufIdx
168         inc     BufIdx
169         sta     Buf,y
170         rts
171
172 ; ----------------------------------------------------------------------------
173 ; Get a pointer to the current buffer end and push it onto the stack
174
175 PushBufPtr:
176         lda     #<Buf
177         ldx     #>Buf
178         add     BufIdx
179         bcc     @L1
180         inx
181 @L1:    jmp     pushax
182
183 ; ----------------------------------------------------------------------------
184 ; Push OutData onto the software stack
185
186 PushOutData:
187         lda     OutData
188         ldx     OutData+1
189         jmp     pushax
190
191 ; ----------------------------------------------------------------------------
192 ; Output Width pad characters
193 ;
194
195 PadLoop:
196         jsr     OutputPadChar
197 OutputPadding:
198         inc     Width
199         bne     PadLoop
200         inc     Width+1
201         bne     PadLoop
202         rts
203
204 ; ----------------------------------------------------------------------------
205 ; Output the argument itself: outfunc (d, str, arglen);
206 ;
207
208 OutputArg:
209         jsr     PushOutData
210         lda     Str
211         ldx     Str+1
212         jsr     pushax
213         lda     ArgLen
214         ldx     ArgLen+1
215         jsr     pushax
216         jmp     (OutFunc)
217
218 ; ----------------------------------------------------------------------------
219 ; ltoa: Wrapper for _ltoa that pushes all arguments
220
221 ltoa:   sty     Base                    ; Save base
222         jsr     pusheax                 ; Push value
223         jsr     PushBufPtr              ; Push the buffer pointer...
224         lda     Base                    ; Restore base
225         jmp     _ltoa                   ; ultoa (l, s, base);
226
227
228 ; ----------------------------------------------------------------------------
229 ; ultoa: Wrapper for _ultoa that pushes all arguments
230
231 ultoa:  sty     Base                    ; Save base
232         jsr     pusheax                 ; Push value
233         jsr     PushBufPtr              ; Push the buffer pointer...
234         lda     Base                    ; Restore base
235         jmp     _ultoa                  ; ultoa (l, s, base);
236
237
238 ; ----------------------------------------------------------------------------
239 ;
240
241 __printf:
242
243 ; Save the register bank variables into the save area
244
245         pha                             ; Save low byte of ap
246         ldy     #5
247 Save:   lda     regbank,y
248         sta     RegSave,y
249         dey
250         bpl     Save
251
252 ; Get the parameters from the stack
253
254         pla                             ; Restore low byte of ap
255         sta     ArgList                 ; Argument list pointer
256         stx     ArgList+1
257
258         jsr     popax                   ; Format string
259         sta     Format
260         stx     Format+1
261
262         jsr     popax                   ; Output descriptor
263         sta     OutData
264         stx     OutData+1
265
266 ; Initialize the output counter in the output descriptor to zero
267
268         lda     #0
269         tay
270         sta     (OutData),y
271         iny
272         sta     (OutData),y
273
274 ; Get the output function from the output descriptor and remember it
275
276         iny
277         lda     (OutData),y
278         sta     OutFunc
279         iny
280         lda     (OutData),y
281         sta     OutFunc+1
282
283 ; Start parsing the format string
284
285 MainLoop:
286         lda     Format                  ; Remember current format pointer
287         sta     FSave
288         lda     Format+1
289         sta     FSave+1
290
291         ldy     #0                      ; Index
292 @L1:    lda     (Format),y              ; Get next char
293         beq     @L2                     ; Jump on end of string
294         cmp     #'%'                    ; Format spec?
295         beq     @L2
296         iny                             ; Bump pointer
297         bne     @L1
298         inc     Format+1                ; Bump high byte of pointer
299         bne     @L1                     ; Branch always
300
301 ; Found a '%' character or end of string. Update the Format pointer so it is
302 ; current (points to this character).
303
304 @L2:    tya                             ; Low byte of offset
305         add     Format
306         sta     Format
307         bcc     @L3
308         inc     Format+1
309
310 ; Calculate, how many characters must be output. Beware: This number may
311 ; be zero. A still contains the low byte of the pointer.
312
313 @L3:    sub     FSave
314         sta     FCount
315         lda     Format+1
316         sbc     FSave+1
317         sta     FCount+1
318         ora     FCount                  ; Is the result zero?
319         beq     @L4                     ; Jump if yes
320
321 ; Output the characters that we have until now. To make the call to out
322 ; faster, build the stack frame by hand (don't use pushax)
323
324         jsr     decsp6                  ; 3 args
325         ldy     #5
326         lda     OutData+1
327         sta     (sp),y
328         dey
329         lda     OutData
330         sta     (sp),y
331         dey
332         lda     FSave+1
333         sta     (sp),y
334         dey
335         lda     FSave
336         sta     (sp),y
337         dey
338         lda     FCount+1
339         sta     (sp),y
340         dey
341         lda     FCount
342         sta     (sp),y
343         jsr     CallOutFunc             ; Call the output function
344
345 ; We're back from out(), or we didn't call it. Check for end of string.
346
347 @L4:    jsr     GetFormatChar           ; Get one char, zero in Y
348         tax                             ; End of format string reached?
349         bne     NotDone                 ; End not reached
350
351 ; End of format string reached. Restore the zeropage registers and return.
352
353         ldx     #5
354 Rest:   lda     RegSave,x
355         sta     regbank,x
356         dex
357         bpl     Rest
358         rts
359
360 ; Still a valid format character. Check for '%' and a '%%' sequence. Output
361 ; anything that is not a format specifier. On intro, Y is zero.
362
363 NotDone:
364         cmp     #'%'
365         bne     @L1
366         lda     (Format),y              ; Check for "%%"
367         cmp     #'%'
368         bne     FormatSpec              ; Jump if really a format specifier
369         jsr     IncFormatPtr            ; Skip the second '%'
370 @L1:    jsr     Output1                 ; Output the character...
371         jmp     MainLoop                ; ...and continue
372
373 ; We have a real format specifier
374 ; Format is: %[flags][width][.precision][mod]type
375 ; Y is zero on entry.
376
377 FormatSpec:
378
379 ; Initialize the flags
380
381         lda     #0
382         ldx     #FormatVarSize-1
383 @L1:    sta     FormatVars,x
384         dex
385         bpl     @L1
386
387 ; Start with reading the flags if there are any. X is $FF which is used
388 ; for "true"
389
390 ReadFlags:
391         lda     (Format),y              ; Get next char...
392         cmp     #'-'
393         bne     @L1
394         stx     LeftJust
395         beq     @L4
396
397 @L1:    cmp     #'+'
398         bne     @L2
399         stx     AddSign
400         beq     @L4
401
402 @L2:    cmp     #' '
403         bne     @L3
404         stx     AddBlank
405         beq     @L4
406
407 @L3:    cmp     #'#'
408         bne     ReadPadding
409         stx     AltForm
410
411 @L4:    jsr     IncFormatPtr
412         jmp     ReadFlags               ; ...and start over
413
414 ; Done with flags, read the pad char. Y is still zero if we come here.
415
416 ReadPadding:
417         ldx     #' '                    ; PadChar
418         cmp     #'0'
419         bne     @L1
420         tax                             ; PadChar is '0'
421         jsr     IncFormatPtr
422         lda     (Format),y              ; Read current for later
423 @L1:    stx     PadChar
424
425 ; Read the width. Even here, Y is still zero. A contains the current character
426 ; from the format string
427
428 ReadWidth:
429         cmp     #'*'
430         bne     @L1
431         jsr     IncFormatPtr
432         jsr     GetIntArg               ; Width is an additional argument
433         jmp     @L2
434
435 @L1:    jsr     ReadInt                 ; Read integer from format string...
436 @L2:    sta     Width
437         stx     Width+1                 ; ...and remember in Width
438
439 ; Read the precision. Even here, Y is still zero.
440
441         sty     Prec                    ; Assume Precision is zero
442         sty     Prec+1
443         lda     (Format),y              ; Load next format string char
444         cmp     #'.'                    ; Precision given?
445         bne     ReadMod                 ; Branch if no precision given
446
447 ReadPrec:
448         jsr     IncFormatPtr            ; Skip the '.'
449         lda     (Format),y
450         cmp     #'*'                    ; Variable precision?
451         bne     @L1
452         jsr     IncFormatPtr            ; Skip the '*'
453         jsr     GetIntArg               ; Get integer argument
454         jmp     @L2
455
456 @L1:    jsr     ReadInt                 ; Read integer from format string
457 @L2:    sta     Prec
458         stx     Prec+1
459
460 ; Read the modifiers. Y is still zero.
461
462 ReadMod:
463         lda     (Format),y
464         cmp     #'F'
465         beq     @L1                     ; Read and ignore this one
466         cmp     #'N'
467         beq     @L1                     ; Read and ignore this one
468         cmp     #'h'
469         beq     @L1                     ; Read and ignore this one
470         cmp     #'L'
471         beq     @L1                     ; Read and ignore this one
472         cmp     #'l'
473         bne     DoFormat
474         lda     #$FF
475         sta     IsLong
476 @L1:    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     CheckString
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 string specifier (%s)
589
590 CheckString:
591         cmp     #'s'
592         bne     CheckUnsigned
593
594 ; It's a string
595
596         jsr     GetIntArg               ; Get 16bit argument
597         sta     Str
598         stx     Str+1
599         jmp     HaveArg
600
601 ; Check for an unsigned integer (%u)
602
603 CheckUnsigned:
604         cmp     #'u'
605         bne     CheckHex
606
607 ; It's an unsigned integer
608
609         jsr     GetUnsignedArg          ; Get argument as unsigned long
610         ldy     #10                     ; Load base
611         jsr     ultoa                   ; Push arguments, call _ultoa
612         jmp     HaveArg
613
614 ; Check for a hexadecimal integer (%x)
615
616 CheckHex:
617         cmp     #'x'
618         beq     @IsHex
619         cmp     #'X'
620         bne     UnknownFormat
621
622 ; Hexadecimal integer
623
624 @IsHex: pha                             ; Save the format spec
625         lda     AltForm
626         beq     @L1
627         lda     #'0'
628         jsr     PutBuf
629         lda     #'X'
630         jsr     PutBuf
631
632 @L1:    jsr     GetUnsignedArg          ; Get argument as an unsigned long
633         ldy     #16                     ; Load base
634         jsr     ultoa                   ; Push arguments, call _ultoa
635
636         pla                             ; Get the format spec
637         cmp     #'x'                    ; Lower case?
638         bne     @L2
639         lda     Str
640         ldx     Str+1
641         jsr     _strlower               ; Make characters lower case
642 @L2:    jmp     HaveArg
643
644 ; Unsigned format character, skip it
645
646 UnknownFormat:
647         jmp     MainLoop
648
649 ; We have the argument, do argument string formatting
650
651 HaveArg:
652
653 ; ArgLen = strlen (Str);
654
655         lda     Str
656         ldx     Str+1
657         jsr     _strlen                 ; Get length of argument
658         sta     ArgLen
659         stx     ArgLen+1
660
661 ; if (Prec && Prec < ArgLen) ArgLen = Prec;
662
663         lda     Prec
664         ora     Prec+1
665         beq     @L1
666         ldx     Prec
667         cpx     ArgLen
668         lda     Prec+1
669         tay
670         sbc     ArgLen+1
671         bcc     @L1
672         stx     ArgLen
673         sty     ArgLen+1
674
675 ;  if (Width > ArgLen) {
676 ;      Width -= ArgLen;                 /* padcount */
677 ;  } else {
678 ;      Width = 0;
679 ;  }
680 ; Since width is used as a counter below, calculate -(width+1)
681
682 @L1:    sec
683         lda     Width
684         sbc     ArgLen
685         tax
686         lda     Width+1
687         sbc     ArgLen+1
688         bcs     @L2
689         lda     #0
690         tax
691 @L2:    eor     #$FF
692         sta     Width+1
693         txa
694         eor     #$FF
695         sta     Width
696
697 ;  /* Do padding on the left side if needed */
698 ;  if (!leftjust) {
699 ;      /* argument right justified */
700 ;      while (width) {
701 ;         fout (d, &padchar, 1);
702 ;         --width;
703 ;      }
704 ;  }
705
706         lda     LeftJust
707         bne     @L3
708         jsr     OutputPadding
709
710 ; Output the argument itself
711
712 @L3:    jsr     OutputArg
713
714 ;  /* Output right padding bytes if needed */
715 ;  if (leftjust) {
716 ;      /* argument left justified */
717 ;      while (width) {
718 ;         fout (d, &padchar, 1);
719 ;         --width;
720 ;      }
721 ;  }
722
723         lda     LeftJust
724         beq     @L4
725         jsr     OutputPadding
726
727 ; Done, parse next chars from format string
728
729 @L4:    jmp     MainLoop
730
731
732 ; ----------------------------------------------------------------------------
733 ; Local data (all static)
734
735 .bss
736
737 ; Save area for the zero page registers
738 RegSave:        .res    6
739
740 ; Stuff from OutData. Is used as a vector and must be aligned
741 OutFunc:        .word   0
742
743 ; One character argument for OutFunc
744 CharArg:        .byte   0
745
746 ; Format variables
747 FormatVars:
748 LeftJust:       .byte   0
749 AddSign:        .byte   0
750 AddBlank:       .byte   0
751 AltForm:        .byte   0
752 PadChar:        .byte   0
753 Width:          .word   0
754 Prec:           .word   0
755 IsLong:         .byte   0
756 Leader:         .byte   0
757 BufIdx:         .byte   0       ; Argument string pointer
758 FormatVarSize   = * - FormatVars
759
760 ; Argument buffer and pointer
761 Buf:            .res    20
762 Str:            .word   0
763 ArgLen:         .res    2
764