]> git.sur5r.net Git - cc65/commitdiff
_printf rewritten in assembler - basic tests ok, needs some more tests and
authorcuz <cuz@b7a2c559-68d2-44c3-8de9-860c34a00d81>
Thu, 30 Nov 2000 23:04:24 +0000 (23:04 +0000)
committercuz <cuz@b7a2c559-68d2-44c3-8de9-860c34a00d81>
Thu, 30 Nov 2000 23:04:24 +0000 (23:04 +0000)
optimizations.

git-svn-id: svn://svn.cc65.org/cc65/trunk@502 b7a2c559-68d2-44c3-8de9-860c34a00d81

libsrc/common/.cvsignore
libsrc/common/Makefile
libsrc/common/_printf.c [deleted file]
libsrc/common/_printf.h
libsrc/common/_printf.s [new file with mode: 0644]

index 2314f56cbe22f1f703313a73e0b8c1b4d7d7a023..24e4f8b797ca19be9f902bff69c7dbb045e28f66 100644 (file)
@@ -1,7 +1,6 @@
 _afailed.s
 _fopen.s
 _hextab.s
-_printf.s
 abort.s
 bsearch.s
 calloc.s
index 48db0cdbfc8d0d06172b08683f7e28d05568f688..9bdb80e3bdbb9beddbf326ffd09090999a643fdd 100644 (file)
@@ -15,16 +15,17 @@ C_OBJS = fclose.o fgets.o fprintf.o calloc.o _fopen.o\
         fputs.o fread.o fwrite.o gets.o realloc.o bsearch.o strxfrm.o\
         printf.o _hextab.o vfprintf.o fdopen.o strtok.o\
         _afailed.o fopen.o fgetc.o fputc.o puts.o gets.o perror.o getchar.o\
-        _printf.o vprintf.o vsprintf.o sprintf.o abort.o qsort.o putchar.o\
+        vprintf.o vsprintf.o sprintf.o abort.o qsort.o putchar.o\
         errormsg.o cprintf.o vcprintf.o freopen.o locale.o fsetpos.o\
         fgetpos.o rewind.o fseek.o ftell.o
 
-S_OBJS =       _fdesc.o        \
-               _file.o         \
-               _hadd.o         \
-               _heap.o         \
-               _oserror.o      \
-               _stksize.o      \
+S_OBJS =       _fdesc.o        \
+               _file.o         \
+               _hadd.o         \
+               _heap.o         \
+               _oserror.o      \
+               _printf.o       \
+               _stksize.o      \
                _swap.o         \
                _sys.o          \
                abs.o           \
@@ -58,7 +59,7 @@ S_OBJS =      _fdesc.o        \
                memcpy.o        \
                memset.o        \
                rand.o          \
-               setjmp.o        \
+               setjmp.o        \
                stkcheck.o      \
                strcat.o        \
                strchr.o        \
diff --git a/libsrc/common/_printf.c b/libsrc/common/_printf.c
deleted file mode 100644 (file)
index 40f2aac..0000000
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Helper function for the printf family.
- */
-
-
-
-#include <stdio.h>
-#include <string.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include "_printf.h"
-
-
-
-/* Use static variables for locals */
-#pragma staticlocals (1);
-
-
-
-int _printf (struct outdesc* d, const char* f, va_list ap)
-{
-    outfunc fout;              /* Output function */
-    unsigned char type;                /* variable argument type */
-    char str [20];             /* string buffer */
-    char c;                            /* Current format char */
-    char leftjust;             /* left justify string */
-    char addsign;                      /* always add + or - */
-    char addblank;             /* add blank instead of + */
-    char altform;                      /* alternate form? */
-    char padchar;              /* pad with space or zeroes? */
-    char islong;                       /* l modifier found */
-    unsigned arglen;           /* length of argument string */
-    unsigned prec;             /* Precision */
-    unsigned width;            /* Width of output field */
-    int  i;                    /* Integer value */
-    long l;                    /* Long value */
-    char* sptr;                        /* pointer to argument string */
-    register char* s;                  /* work pointer to argument string */
-
-    /* Remember the format string in a register variable for shorter code */
-    register const char* format = f;
-
-    /* Remember the output function in a local variable for speed and size */
-    fout = d->fout;
-
-    /* */
-    d->ccount = 0;
-    while (c = *format++) {
-
-       if (c != '%') {
-                   fout (d, &c, 1);
-           continue;
-       }
-
-       /* %%? */
-       if (*format == '%') {
-                   fout (d, format, 1);
-           ++format;
-           continue;
-       }
-
-       /* format is: %[flags][width][.precision][mod]type */
-
-       /* flags */
-       leftjust = addsign = addblank = altform = 0;
-               do {
-           switch (c = *format) {
-
-                       case '-':
-                   leftjust = 1;
-                   break;
-
-               case '+':
-                   addsign = 1;
-                   break;
-
-               case '#':
-                   altform = 1;
-                   break;
-
-               case ' ':
-                   addblank = 1;
-                   break;
-
-               default:
-                   goto flags_done;
-
-           }
-           ++format;
-       } while (1);
-flags_done:
-
-       /* width */
-       padchar = ' ';
-       if (*format == '0') {
-           padchar = '0';
-           ++format;
-       }
-       if (*format == '*') {
-           width = va_arg (ap, int);
-           ++format;
-       } else {
-            width = 0;
-           while (isdigit (c = *format)) {
-               width = width * 10 + (c - '0');
-               ++format;
-           }
-       }
-
-       /* precision */
-        prec = 0;
-       if (*format == '.') {
-           ++format;
-           if (*format == '*') {
-               prec = va_arg (ap, int);
-               ++format;
-           } else {
-               while (isdigit (c = *format)) {
-                   prec = prec * 10 + (c - '0');
-                   ++format;
-               }
-           }
-       }
-
-       /* modifiers */
-       islong = 0;
-               while (strchr ("FNhlL", c = *format)) {
-           switch (c) {
-
-               case 'l':
-                   islong = 1;
-                   break;
-
-           }
-           ++format;
-       }
-
-       /* Check the format specifier */
-       sptr = s = str;
-       type = *format++;
-               switch (type) {
-
-                   case 'c':
-               str [0] = va_arg (ap, char);
-               str [1] = 0;
-               break;
-
-                   case 'd':
-                   case 'i':
-               if (islong) {
-                   l = va_arg (ap, long);
-                   if (l >= 0) {
-                       if (addsign) {
-                           *s++ = '+';
-                       } else if (addblank) {
-                           *s++ = ' ';
-                       }
-                   }
-                   ltoa (l, s, 10);
-               } else {
-                   i = va_arg (ap, int);
-                   if (i >= 0) {
-                       if (addsign) {
-                           *s++ = '+';
-                       } else if (addblank) {
-                           *s++ = ' ';
-                       }
-                   }
-                   itoa (i, s, 10);
-               }
-               break;
-
-            case 'n':
-               *va_arg (ap, int*) = d->ccount;
-               continue;
-
-                   case 'o':
-               if (islong) {
-                   l = va_arg (ap, unsigned long);
-                   if (altform && (l || prec)) {
-                       *s++ = '0';
-                   }
-                           ultoa (l, s, 8);
-               } else {
-                   i = va_arg (ap, unsigned);
-                   if (altform && (i || prec)) {
-                       *s++ = '0';
-                   }
-                   utoa (i, s, 8);
-               }
-               break;
-
-                   case 's':
-               sptr = va_arg (ap, char*);
-               break;
-
-           case 'u':
-               if (islong) {
-                   ultoa (va_arg (ap, unsigned long), str, 10);
-               } else {
-                   utoa (va_arg (ap, unsigned), str, 10);
-               }
-               break;
-
-           case 'x':
-           case 'X':
-               if (altform) {
-                   *s++ = '0';
-                   *s++ = 'X';
-               }
-               if (islong) {
-                           ultoa (va_arg (ap, unsigned long), s, 16);
-               } else {
-                   utoa (va_arg (ap, unsigned), s, 16);
-               }
-               if (type == 'x') {
-                   strlower (str);
-               }
-               break;
-
-           default:
-               /* Unknown type char - skip it */
-               continue;
-
-       }
-
-       /* Do argument string formatting */
-       arglen = strlen (sptr);
-       if (prec && prec < arglen) {
-           arglen = prec;
-       }
-               if (width > arglen) {
-           width -= arglen;            /* padcount */
-       } else {
-           width = 0;
-       }
-
-       /* Do padding on the left side if needed */
-       if (!leftjust) {
-           /* argument right justified */
-           while (width) {
-               fout (d, &padchar, 1);
-               --width;
-           }
-               }
-
-       /* Output the argument string */
-       fout (d, sptr, arglen);
-
-       /* Output right padding bytes if needed */
-       if (leftjust) {
-           /* argument left justified */
-           while (width) {
-                       fout (d, &padchar, 1);
-               --width;
-           }
-       }
-
-    }
-}
-
-
-
index aa5802f03d2c3bbf529db9e61092a4aa9907352c..03e8f7307be596bd83b8a9c020ce02caa54f9fc0 100644 (file)
@@ -20,9 +20,13 @@ typedef void (*outfunc) (struct outdesc* desc, const char* buf, unsigned count);
 
 
 
+/* Control structure passed to the low level worker function.
+ * Beware: This function will access the structure on the assembly level,
+ * so check this when altering the structure.
+ */
 struct outdesc {
-    outfunc            fout;           /* Routine used to output data */
     int                ccount;         /* Character counter */
+    outfunc            fout;           /* Routine used to output data */
     void*      ptr;            /* Data internal to print routine */
     unsigned   uns;            /* Data internal to print routine */
 };
diff --git a/libsrc/common/_printf.s b/libsrc/common/_printf.s
new file mode 100644 (file)
index 0000000..f0013c7
--- /dev/null
@@ -0,0 +1,691 @@
+;
+; _printf: Basic layer for all printf type functions.
+;
+; Ullrich von Bassewitz, 21.10.2000
+;
+
+       .export         __printf
+
+       .import         popax, pushax, pusheax, push1, axlong, axulong
+       .import         __ctype
+       .import         _ltoa, _ultoa
+       .import         _strlower, _strlen
+       .import         jmpvec
+       .importzp       sp, ptr1, ptr2, tmp1, regbank, sreg
+
+       .macpack        generic
+
+; ----------------------------------------------------------------------------
+; We will store variables into the register bank in the zeropage. Define
+; equates for these variables.
+
+ArgList                = regbank+0             ; Argument list pointer
+Format         = regbank+2             ; Format string
+OutData                = regbank+4             ; Function parameters
+
+
+.code
+
+; ----------------------------------------------------------------------------
+; Get one character from the format string and increment the pointer. Will
+; return zero in Y.
+
+GetFormatChar:
+       ldy     #0
+       lda     (Format),y
+IncFormatPtr:
+       inc     Format
+       bne     @L1
+       inc     Format+1
+@L1:   rts
+
+; ----------------------------------------------------------------------------
+; Output a pad character: outfunc (d, &padchar, 1)
+
+OutputPadChar:
+       lda     PadChar
+
+; ----------------------------------------------------------------------------
+; Call the output function with one character in A
+
+Output1:
+       sta     CharArg
+       jsr     PushOutData
+       lda     #<CharArg
+       ldx     #>CharArg
+       jsr     pushax
+       jsr     push1
+       jmp     (OutFunc)       ; fout (OutData, &CharArg, 1)
+
+; ----------------------------------------------------------------------------
+; Decrement the argument list pointer by 2
+
+DecArgList2:
+       lda     ArgList
+       sub     #2
+       sta     ArgList
+       bcs     @L1
+       dec     ArgList+1
+@L1:   rts
+
+; ----------------------------------------------------------------------------
+; Get an unsigned int or long argument depending on the IsLong flag.
+
+GetUnsignedArg:
+       lda     IsLong                  ; Check flag
+       bne     GetLongArg              ; Long sets all
+       jsr     GetIntArg               ; Get an integer argument
+       jmp     axulong                 ; Convert to unsigned long
+
+; ----------------------------------------------------------------------------
+; Get an signed int or long argument depending on the IsLong flag.
+
+GetSignedArg:
+       lda     IsLong                  ; Check flag
+       bne     GetLongArg              ; Long sets all
+       jsr     GetIntArg               ; Get an integer argument
+       jmp     axlong                  ; Convert to long
+
+; ----------------------------------------------------------------------------
+; Get a long argument from the argument list. Returns 0 in Y.
+
+GetLongArg:
+       jsr     GetIntArg               ; Get high word
+       sta     sreg
+       stx     sreg+1
+
+; Run into GetIntArg fetching the low word
+
+; ----------------------------------------------------------------------------
+; Get an integer argument from the argument list. Returns 0 in Y.
+
+GetIntArg:
+       jsr     DecArgList2
+       ldy     #1
+       lda     (ArgList),y
+       tax
+       dey
+       lda     (ArgList),y
+       rts
+
+; ----------------------------------------------------------------------------
+; Add the a new digit in X to the value in ptr1. Does leave Y alone.
+
+AddDigit:
+       txa                             ; Move digit into A
+       sub     #'0'                    ; Make number from ascii digit
+       pha
+       lda     ptr1
+       ldx     ptr1+1
+       asl     ptr1
+       rol     ptr1+1                  ; * 2
+       asl     ptr1
+       rol     ptr1+1                  ; * 4
+       add     ptr1
+       sta     ptr1
+       txa
+       adc     ptr1+1
+       sta     ptr1+1                  ; * 5
+       asl     ptr1
+       rol     ptr1+1                  ; * 10
+       pla
+       add     ptr1                    ; Add digit value
+       sta     ptr1
+       bcc     @L1
+       inc     ptr1+1
+@L1:   rts
+
+; ----------------------------------------------------------------------------
+; Read an integer from the format string. Will return zero in Y.
+
+ReadInt:
+       ldy     #0
+       sty     ptr1
+       sty     ptr1+1                  ; Start with zero
+@L1:   lda     (Format),y              ; Get format string character
+       tax                             ; Format character --> X
+       lda     __ctype,x               ; Get character classification
+       and     #$04                    ; Digit?
+               beq     @L9                     ; Jump if done
+       jsr     AddDigit                ; Add the digit to ptr1
+       jsr     IncFormatPtr            ; Skip the character
+       jmp     @L1
+
+@L9:   lda     ptr1
+       ldx     ptr1+1                  ; Load result
+       rts
+
+; ----------------------------------------------------------------------------
+; Put a character into the argument buffer and increment the buffer index
+
+PutBuf:        ldy     BufIdx
+       inc     BufIdx
+       sta     Buf,y
+       rts
+
+; ----------------------------------------------------------------------------
+; Get a pointer to the current buffer end
+
+GetBufPtr:
+       lda     #<Buf
+       ldx     #>Buf
+       add     BufIdx
+       bcc     @L1
+       inx
+@L1:   rts
+
+; ----------------------------------------------------------------------------
+; Push OutData onto the software stack
+
+PushOutData:
+       lda     OutData
+       ldx     OutData+1
+       jmp     pushax
+
+; ----------------------------------------------------------------------------
+; Output Width pad characters
+;
+
+PadLoop:
+               jsr     OutputPadChar
+OutputPadding:
+       inc     Width
+       bne     PadLoop
+       inc     Width+1
+       bne     PadLoop
+       rts
+
+; ----------------------------------------------------------------------------
+; Output the argument itself: outfunc (d, str, arglen);
+;
+
+OutputArg:
+       jsr     PushOutData
+       lda     Str
+       ldx     Str+1
+       jsr     pushax
+       lda     ArgLen
+       ldx     ArgLen+1
+       jsr     pushax
+       jmp     (OutFunc)
+
+; ----------------------------------------------------------------------------
+;
+
+__printf:
+
+; Save the register bank variables into the save area
+
+       ldx     #5
+Save:  lda     regbank,x
+       sta     RegSave,x
+       dex
+               bpl     Save
+
+; Get the parameters from the stack
+
+       jsr     popax                   ; Argument list pointer
+       sta     ArgList
+       stx     ArgList+1
+
+       jsr     popax                   ; Format string
+       sta     Format
+       stx     Format+1
+
+       jsr     popax                   ; Output descriptor
+       sta     OutData
+       stx     OutData+1
+
+; Initialize the output counter in the output descriptor to zero
+
+       lda     #0
+       tay
+       sta     (OutData),y
+       iny
+       sta     (OutData),y
+
+; Get the output function from the output descriptor and remember it
+
+       iny
+       lda     (OutData),y
+       sta     OutFunc
+       iny
+       lda     (OutData),y
+       sta     OutFunc+1
+
+; Start parsing the format string
+
+MainLoop:
+       jsr     GetFormatChar           ; Get one char, zero in Y
+       tax                             ; End of format string reached?
+               bne     NotDone                 ; Continue of end not reached
+
+; End of format string reached. Restore the zeropage registers and return.
+
+       ldx     #5
+Rest:  lda     RegSave,x
+       sta     regbank,x
+       dex
+       bpl     Rest
+       rts
+
+; Still a valid format character. Check for '%' and a '%%' sequence. Output
+; anything that is not a format specifier. On intro, Y is zero.
+
+NotDone:
+       cmp     #'%'
+       bne     @L1
+       lda     (Format),y              ; Check for "%%"
+       cmp     #'%'
+       bne     FormatSpec              ; Jump if really a format specifier
+       jsr     IncFormatPtr            ; Skip the second '%'
+@L1:   jsr     Output1                 ; Output the character...
+       jmp     MainLoop                ; ...and continue
+
+; We have a real format specifier
+; Format is: %[flags][width][.precision][mod]type
+; Y is zero on entry.
+
+FormatSpec:
+
+; Initialize the flags
+
+       lda     #0
+               ldx     #FormatVarSize-1
+@L1:   sta     FormatVars,x
+       dex
+       bpl     @L1
+
+; Start with reading the flags if there are any
+
+       ldx     #$FF                    ; "true" flag
+
+ReadFlags:
+       lda     (Format),y              ; Get next char...
+       cmp     #'-'
+       bne     @L1
+       stx     LeftJust
+       beq     @L4
+
+@L1:   cmp     #'+'
+       bne     @L2
+       stx     AddSign
+       beq     @L4
+
+@L2:   cmp     #' '
+       bne     @L3
+       stx     AddBlank
+       beq     @L4
+
+@L3:   cmp     #'#'
+       bne     ReadPadding
+       stx     AltForm
+
+@L4:   jsr     IncFormatPtr
+       jmp     ReadFlags               ; ...and start over
+
+; Done with flags, read the pad char. Y is still zero if we come here.
+
+ReadPadding:
+       ldx     #' '                    ; PadChar
+       cmp     #'0'
+       bne     @L1
+       tax                             ; PadChar is '0'
+       jsr     IncFormatPtr
+       lda     (Format),y              ; Read current for later
+@L1:   stx     PadChar
+
+; Read the width. Even here, Y is still zero. A contains the current character
+; from the format string
+
+ReadWidth:
+       cmp     #'*'
+       bne     @L1
+       jsr     IncFormatPtr
+       jsr     GetIntArg               ; Width is an additional argument
+       jmp     @L2
+
+@L1:   jsr     ReadInt                 ; Read integer from format string...
+@L2:   sta     Width
+       stx     Width+1                 ; ...and remember in Width
+
+; Read the precision. Even here, Y is still zero.
+
+       sty     Prec                    ; Assume Precision is zero
+       sty     Prec+1
+       lda     (Format),y              ; Load next format string char
+       cmp     #'.'                    ; Precision given?
+       bne     ReadMod                 ; Branch if no precision given
+
+ReadPrec:
+       jsr     IncFormatPtr            ; Skip the '.'
+       lda     (Format),y
+       cmp     #'*'                    ; Variable precision?
+       bne     @L1
+       jsr     IncFormatPtr            ; Skip the '*'
+       jsr     GetIntArg               ; Get integer argument
+       jmp     @L2
+
+@L1:   jsr     ReadInt                 ; Read integer from format string
+@L2:   sta     Prec
+       stx     Prec+1
+
+; Read the modifiers. Y is still zero.
+
+ReadMod:
+       lda     (Format),y
+       cmp     #'F'
+       beq     @L1                     ; Read and ignore this one
+       cmp     #'N'
+       beq     @L1                     ; Read and ignore this one
+       cmp     #'h'
+       beq     @L1                     ; Read and ignore this one
+       cmp     #'L'
+       beq     @L1                     ; Read and ignore this one
+       cmp     #'l'
+       bne     DoFormat
+       lda     #$FF
+       sta     IsLong
+@L1:   jsr     IncFormatPtr
+       jmp     ReadMod
+
+; Initialize the argument buffer pointers. We use a static buffer (ArgBuf) to
+; assemble strings. A zero page index (BufIdx) is used to keep the current
+; write position. A pointer to the buffer (Str) is used to point to the the
+; argument in case we will not use the buffer but a user supplied string.
+; Y is zero when we come here.
+
+DoFormat:
+       sty     BufIdx                  ; Clear BufIdx
+       ldx     #<Buf
+               stx     Str
+       ldx     #>Buf
+       stx     Str+1
+
+; Skip the current format character, then check it (current char in A)
+
+       jsr     IncFormatPtr
+
+; Is it a character?
+
+       cmp     #'c'
+       bne     CheckInt
+
+; It is a character
+
+       jsr     GetIntArg               ; Get the argument (promoted to int)
+       jsr     PutBuf
+       lda     #0                      ; Place it as zero terminated string...
+       jsr     PutBuf                  ; ...into the buffer
+       jmp     HaveArg                 ; Done
+
+; Is it an integer?
+
+CheckInt:
+       cmp     #'d'
+       beq     @L1
+       cmp     #'i'
+       bne     CheckCount
+
+; It is an integer
+
+@L1:   ldx     #0
+       lda     AddBlank                ; Add a blank for positives?
+       beq     @L2                     ; Jump if no
+       ldx     #' '
+@L2:   lda     AddSign                 ; Add a plus for positives (precedence)?
+       beq     @L3
+       ldx     #'+'
+@L3:   stx     Leader
+
+; Integer argument
+
+       jsr     GetSignedArg                    ; Get argument as a long
+       ldy     sreg+1                  ; Check sign
+       bmi     @Int1
+       ldy     Leader
+       beq     @Int1
+       sty     Buf
+       inc     BufIdx
+
+@Int1: jsr     pusheax
+       jsr     GetBufPtr
+       jsr     pushax
+       lda     #10
+       jsr     _ltoa                   ; ltoa (va_arg (ap, long), s, 10);
+       jmp     HaveArg
+
+; Is it a count pseudo format?
+
+CheckCount:
+       cmp     #'n'
+       bne     CheckOctal
+
+; It is a count pseudo argument
+
+       jsr     GetIntArg
+       sta     ptr1
+       stx     ptr1+1                  ; Get user supplied pointer
+       ldy     #0
+       lda     (OutData),y             ; Low byte of OutData->ccount
+       sta     (ptr1),y
+       iny
+       lda     (OutData),y             ; High byte of OutData->ccount
+       sta     (ptr1),y
+       jmp     MainLoop                ; Done
+
+; Check for an octal digit
+
+CheckOctal:
+       cmp     #'o'
+       bne     CheckString
+
+; Integer in octal representation
+
+       jsr     GetSignedArg            ; Get argument as a long
+               ldy     AltForm                 ; Alternative form?
+       beq     @Oct1                   ; Jump if no
+       tay                             ; Save low byte of value
+       stx     tmp1
+       ora     tmp1
+       ora     sreg
+       ora     sreg+1
+       ora     Prec
+       ora     Prec+1                  ; Check if value or Prec != 0
+       beq     @Oct1
+       lda     #'0'
+       sta     Buf
+       inc     BufIdx
+       tya                             ; Restore low byte
+
+@Oct1: jsr     pusheax                 ; Push value
+       jsr     GetBufPtr               ; Get buffer pointer...
+       jsr     pushax                  ; ...and push it
+       lda     #8                      ; Load base
+       jsr     _ltoa                   ; ltoa (l, s, 8);
+       jmp     HaveArg
+
+; Check for a string specifier (%s)
+
+CheckString:
+       cmp     #'s'
+       bne     CheckUnsigned
+
+; It's a string
+
+       jsr     GetIntArg               ; Get 16bit argument
+       sta     Str
+       stx     Str+1
+       jmp     HaveArg
+
+; Check for an unsigned integer (%u)
+
+CheckUnsigned:
+       cmp     #'u'
+       bne     CheckHex
+
+; It's an unsigned integer
+
+       jsr     GetUnsignedArg          ; Get argument as unsigned long
+       jsr     pusheax
+       jsr     GetBufPtr               ; Get buffer pointer...
+       jsr     pushax                  ; ...and push it
+               lda     #10                     ; Load base
+       jsr     _ultoa                  ; ultoa (l, s, 10);
+       jmp     HaveArg
+
+; Check for a hexadecimal integer (%x)
+
+CheckHex:
+       cmp     #'x'
+       beq     @IsHex
+       cmp     #'X'
+       bne     UnknownFormat
+
+; Hexadecimal integer
+
+@IsHex:        pha                             ; Save the format spec
+       lda     AltForm
+       beq     @L1
+       lda     #'0'
+       jsr     PutBuf
+       lda     #'X'
+       jsr     PutBuf
+
+@L1:   jsr     GetUnsignedArg          ; Get argument as an unsigned long
+       jsr     pusheax
+       jsr     GetBufPtr               ; Get buffer pointer...
+       jsr     pushax                  ; ...and push it
+               lda     #16                     ; Load base
+       jsr     _ultoa                  ; ultoa (l, s, 16);
+
+       pla                             ; Get the format spec
+       cmp     #'x'                    ; Lower case?
+       bne     @L2
+       lda     Str
+       ldx     Str+1
+       jsr     _strlower               ; Make characters lower case
+@L2:   jmp     HaveArg
+
+; Unsigned format character, skip it
+
+UnknownFormat:
+       jmp     MainLoop
+
+; We have the argument, do argument string formatting
+
+HaveArg:
+
+; ArgLen = strlen (Str);
+
+       lda     Str
+       ldx     Str+1
+       jsr     _strlen                 ; Get length of argument
+       sta     ArgLen
+       stx     ArgLen+1
+
+; if (Prec && Prec < ArgLen) ArgLen = Prec;
+
+       lda     Prec
+       ora     Prec+1
+       beq     @L1
+       ldx     Prec
+       cpx     ArgLen
+       lda     Prec+1
+       tay
+       sbc     ArgLen+1
+       bcc     @L1
+       stx     ArgLen
+       sty     ArgLen+1
+
+;  if (Width > ArgLen) {
+;      Width -= ArgLen;                /* padcount */
+;  } else {
+;      Width = 0;
+;  }
+; Since width is used as a counter below, calculate -(width+1)
+
+@L1:   sec
+       lda     Width
+       sbc     ArgLen
+       tax
+       lda     Width+1
+       sbc     ArgLen+1
+       bcs     @L2
+       lda     #0
+       tax
+@L2:   eor     #$FF
+       sta     Width+1
+       txa
+       eor     #$FF
+       sta     Width
+
+;  /* Do padding on the left side if needed */
+;  if (!leftjust) {
+;      /* argument right justified */
+;      while (width) {
+;        fout (d, &padchar, 1);
+;        --width;
+;      }
+;  }
+
+       lda     LeftJust
+       bne     @L3
+       jsr     OutputPadding
+
+; Output the argument itself
+
+@L3:   jsr     OutputArg
+
+;  /* Output right padding bytes if needed */
+;  if (leftjust) {
+;      /* argument left justified */
+;      while (width) {
+;        fout (d, &padchar, 1);
+;        --width;
+;      }
+;  }
+
+       lda     LeftJust
+       beq     @L4
+       jsr     OutputPadding
+
+; Done, parse next chars from format string
+
+@L4:   jmp     MainLoop
+
+
+; ----------------------------------------------------------------------------
+; Local data (all static)
+
+.bss
+
+; Save area for the zero page registers
+RegSave:       .res    6
+
+; Stuff from OutData. Is used as a vector and must be aligned
+.align 2
+OutFunc:       .word   0
+
+; One character argument for OutFunc
+CharArg:       .byte   0
+
+; Format variables
+FormatVars:
+LeftJust:      .byte   0
+AddSign:       .byte   0
+AddBlank:      .byte   0
+AltForm:       .byte   0
+PadChar:       .byte   0
+Width:         .word   0
+Prec:          .word   0
+IsLong:                .byte   0
+Leader:                .byte   0
+BufIdx:                .byte   0       ; Argument string pointer
+FormatVarSize  = * - FormatVars
+
+; Argument buffer and pointer
+Buf:           .res    20
+Str:           .word   0
+ArgLen:                .res    2
+