]> git.sur5r.net Git - cc65/blobdiff - src/common/xsprintf.c
Merge branch 'master' into master
[cc65] / src / common / xsprintf.c
index 4d891ad8bcce90ae4f6133853eeedd85f4b0adb3..d50ad7a68f1738151c9183730437310d6a35f176 100644 (file)
@@ -1,15 +1,15 @@
 /*****************************************************************************/
 /*                                                                           */
-/*                               xsprintf.c                                 */
+/*                                xsprintf.c                                 */
 /*                                                                           */
-/*                      Replacement sprintf function                        */
+/*                       Replacement sprintf function                        */
 /*                                                                           */
 /*                                                                           */
 /*                                                                           */
-/* (C) 2000     Ullrich von Bassewitz                                        */
-/*              Wacholderweg 14                                              */
-/*              D-70597 Stuttgart                                            */
-/* EMail:       uz@musoftware.de                                             */
+/* (C) 2000-2004 Ullrich von Bassewitz                                       */
+/*               Roemerstrasse 52                                            */
+/*               D-70794 Filderstadt                                         */
+/* EMail:        uz@cc65.org                                                 */
 /*                                                                           */
 /*                                                                           */
 /* This software is provided 'as-is', without any expressed or implied       */
 
 
 #include <stdio.h>
-#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <limits.h>
 
+/* common */
+#include "chartype.h"
+#include "check.h"
+#include "inttypes.h"
+#include "strbuf.h"
+#include "va_copy.h"
 #include "xsprintf.h"
 
 
 
 /*****************************************************************************/
-/*                                          Code                                    */
+/*                                  vsnprintf                                */
+/*****************************************************************************/
+
+
+
+/* The following is a very basic vsnprintf like function called xvsnprintf. It
+** features only the basic format specifiers (especially the floating point
+** stuff is missing), but may be extended if required. Reason for supplying
+** my own implementation is that vsnprintf is standard but not implemented by
+** older compilers, and some that implement it, don't adhere to the standard
+** (for example Microsoft with its _vsnprintf).
+*/
+
+typedef struct {
+
+    /* Variable argument list pointer */
+    va_list     ap;
+
+    /* Output buffer */
+    char*       Buf;
+    size_t      BufSize;
+    size_t      BufFill;
+
+    /* Argument string buffer and string buffer pointer. The string buffer
+    ** must be big enough to hold a converted integer of the largest type
+    ** including an optional sign and terminating zero.
+    */
+    char        ArgBuf[256];
+    int         ArgLen;
+
+    /* Flags */
+    enum {
+        fNone     = 0x0000,
+        fMinus    = 0x0001,
+        fPlus     = 0x0002,
+        fSpace    = 0x0004,
+        fHash     = 0x0008,
+        fZero     = 0x0010,
+        fWidth    = 0x0020,
+        fPrec     = 0x0040,
+        fUnsigned = 0x0080,
+        fUpcase   = 0x0100
+    } Flags;
+
+    /* Conversion base and table */
+    unsigned    Base;
+    const char* CharTable;
+
+    /* Field width */
+    int         Width;
+
+    /* Precision */
+    int         Prec;
+
+    /* Length modifier */
+    enum {
+        lmChar,
+        lmShort,
+        lmInt,
+        lmLong,
+        lmIntMax,
+        lmSizeT,
+        lmPtrDiffT,
+        lmLongDouble,
+
+        /* Unsupported modifiers */
+        lmLongLong = lmLong,
+
+        /* Default length is integer */
+        lmDefault = lmInt
+    } LengthMod;
+
+} PrintfCtrl;
+
+
+
+static void AddChar (PrintfCtrl* P, char C)
+/* Store one character in the output buffer if there's enough room. */
+{
+    if (++P->BufFill <= P->BufSize) {
+        *P->Buf++ = C;
+    }
+}
+
+
+
+static void AddPadding (PrintfCtrl* P, char C, unsigned Count)
+/* Add some amount of padding */
+{
+    while (Count--) {
+        AddChar (P, C);
+    }
+}
+
+
+
+static intmax_t NextIVal (PrintfCtrl*P)
+/* Read the next integer value from the variable argument list */
+{
+    switch (P->LengthMod) {
+        case lmChar:        return (char) va_arg (P->ap, int);
+        case lmShort:       return (short) va_arg (P->ap, int);
+        case lmInt:         return (int) va_arg (P->ap, int);
+        case lmLong:        return (long) va_arg (P->ap, long);
+        case lmIntMax:      return va_arg (P->ap, intmax_t);
+        case lmSizeT:       return (uintmax_t) va_arg (P->ap, size_t);
+        case lmPtrDiffT:    return (long) va_arg (P->ap, ptrdiff_t);
+        default:
+            FAIL ("Invalid type size in NextIVal");
+            return 0;
+    }
+}
+
+
+
+static uintmax_t NextUVal (PrintfCtrl*P)
+/* Read the next unsigned integer value from the variable argument list */
+{
+    switch (P->LengthMod) {
+        case lmChar:        return (unsigned char) va_arg (P->ap, unsigned);
+        case lmShort:       return (unsigned short) va_arg (P->ap, unsigned);
+        case lmInt:         return (unsigned int) va_arg (P->ap, unsigned int);
+        case lmLong:        return (unsigned long) va_arg (P->ap, unsigned long);
+        case lmIntMax:      return va_arg (P->ap, uintmax_t);
+        case lmSizeT:       return va_arg (P->ap, size_t);
+        case lmPtrDiffT:    return (intmax_t) va_arg (P->ap, ptrdiff_t);
+        default:
+            FAIL ("Invalid type size in NextUVal");
+            return 0;
+    }
+}
+
+
+
+static void ToStr (PrintfCtrl* P, uintmax_t Val)
+/* Convert the given value to a (reversed) string */
+{
+    char* S = P->ArgBuf;
+    while (Val) {
+        *S++ = P->CharTable[Val % P->Base];
+        Val /= P->Base;
+    }
+    P->ArgLen = S - P->ArgBuf;
+}
+
+
+
+static void FormatInt (PrintfCtrl* P, uintmax_t Val)
+/* Convert the integer value */
+{
+    char Lead[5];
+    unsigned LeadCount = 0;
+    unsigned PrecPadding;
+    unsigned WidthPadding;
+    unsigned I;
+
+
+    /* Determine the translation table */
+    P->CharTable = (P->Flags & fUpcase)? "0123456789ABCDEF" : "0123456789abcdef";
+
+    /* Check if the value is negative */
+    if ((P->Flags & fUnsigned) == 0 && ((intmax_t) Val) < 0) {
+        Val = -((intmax_t) Val);
+        Lead[LeadCount++] = '-';
+    } else if ((P->Flags & fPlus) != 0) {
+        Lead[LeadCount++] = '+';
+    } else if ((P->Flags & fSpace) != 0) {
+        Lead[LeadCount++] = ' ';
+    }
+
+    /* Convert the value into a (reversed string). */
+    ToStr (P, Val);
+
+    /* The default precision for all integer conversions is one. This means
+    ** that the fPrec flag is always set and does not need to be checked
+    ** later on.
+    */
+    if ((P->Flags & fPrec) == 0) {
+        P->Flags |= fPrec;
+        P->Prec = 1;
+    }
+
+    /* Determine the leaders for alternative forms */
+    if ((P->Flags & fHash) != 0) {
+        if (P->Base == 16) {
+            /* Start with 0x */
+            Lead[LeadCount++] = '0';
+            Lead[LeadCount++] = (P->Flags & fUpcase)? 'X' : 'x';
+        } else if (P->Base == 8) {
+            /* Alternative form for 'o': always add a leading zero. */
+            if (P->Prec <= P->ArgLen) {
+                Lead[LeadCount++] = '0';
+            }
+        }
+    }
+
+    /* Determine the amount of precision padding needed */
+    if (P->ArgLen < P->Prec) {
+        PrecPadding = P->Prec - P->ArgLen;
+    } else {
+        PrecPadding = 0;
+    }
+
+    /* Determine the width padding needed */
+    if ((P->Flags & fWidth) != 0) {
+        int CurWidth = LeadCount + PrecPadding + P->ArgLen;
+        if (CurWidth < P->Width) {
+            WidthPadding = P->Width - CurWidth;
+        } else {
+            WidthPadding = 0;
+        }
+    } else {
+        WidthPadding = 0;
+    }
+
+    /* Output left space padding if any */
+    if ((P->Flags & (fMinus | fZero)) == 0 && WidthPadding > 0) {
+        AddPadding (P, ' ', WidthPadding);
+        WidthPadding = 0;
+    }
+
+    /* Leader */
+    for (I = 0; I < LeadCount; ++I) {
+        AddChar (P, Lead[I]);
+    }
+
+    /* Left zero padding if any */
+    if ((P->Flags & fZero) != 0 && WidthPadding > 0) {
+        AddPadding (P, '0', WidthPadding);
+        WidthPadding = 0;
+    }
+
+    /* Precision padding */
+    if (PrecPadding > 0) {
+        AddPadding (P, '0', PrecPadding);
+    }
+
+    /* The number itself. Beware: It's reversed! */
+    while (P->ArgLen > 0) {
+        AddChar (P, P->ArgBuf[--P->ArgLen]);
+    }
+
+    /* Right width padding if any */
+    if (WidthPadding > 0) {
+        AddPadding (P, ' ', WidthPadding);
+    }
+}
+
+
+
+static void FormatStr (PrintfCtrl* P, const char* Val)
+/* Convert the string */
+{
+    unsigned WidthPadding;
+
+    /* Get the string length limited to the precision. Beware: We cannot use
+    ** strlen here, because if a precision is given, the string may not be
+    ** zero terminated.
+    */
+    int Len;
+    if ((P->Flags & fPrec) != 0) {
+        const char* S = memchr (Val, '\0', P->Prec);
+        if (S == 0) {
+            /* Not zero terminated */
+            Len = P->Prec;
+        } else {
+            /* Terminating zero found */
+            Len = S - Val;
+        }
+    } else {
+        Len = strlen (Val);
+    }
+
+    /* Determine the width padding needed */
+    if ((P->Flags & fWidth) != 0 && P->Width > Len) {
+        WidthPadding = P->Width - Len;
+    } else {
+        WidthPadding = 0;
+    }
+
+    /* Output left padding */
+    if ((P->Flags & fMinus) != 0 && WidthPadding > 0) {
+        AddPadding (P, ' ', WidthPadding);
+        WidthPadding = 0;
+    }
+
+    /* Output the string */
+    while (Len--) {
+        AddChar (P, *Val++);
+    }
+
+    /* Output right padding if any */
+    if (WidthPadding > 0) {
+        AddPadding (P, ' ', WidthPadding);
+    }
+}
+
+
+
+static void StoreOffset (PrintfCtrl* P)
+/* Store the current output offset (%n format spec) */
+{
+    switch (P->LengthMod) {
+        case lmChar:     *va_arg (P->ap, int*)       = P->BufFill;
+        case lmShort:    *va_arg (P->ap, int*)       = P->BufFill;
+        case lmInt:      *va_arg (P->ap, int*)       = P->BufFill;
+        case lmLong:     *va_arg (P->ap, long*)      = P->BufFill;
+        case lmIntMax:   *va_arg (P->ap, intmax_t*)  = P->BufFill;
+        case lmSizeT:    *va_arg (P->ap, size_t*)    = P->BufFill;
+        case lmPtrDiffT: *va_arg (P->ap, ptrdiff_t*) = P->BufFill;
+        default: FAIL ("Invalid size modifier for %n format spec in xvsnprintf");
+    }
+}
+
+
+
+int xvsnprintf (char* Buf, size_t Size, const char* Format, va_list ap)
+/* A basic vsnprintf implementation. Does currently only support integer
+** formats.
+*/
+{
+    PrintfCtrl P;
+    int Done;
+    char F;
+    char SBuf[2];
+    const char* SPtr;
+    int UseStrBuf = 0;
+
+
+    /* Initialize the control structure */
+    va_copy (P.ap, ap);
+    P.Buf       = Buf;
+    P.BufSize   = Size;
+    P.BufFill   = 0;
+
+    /* Parse the format string */
+    while ((F = *Format++) != '\0') {
+
+        if (F != '%') {
+            /* Not a format specifier, just copy */
+            AddChar (&P, F);
+            continue;
+        }
+
+        /* Check for %% */
+        if (*Format == '%') {
+            ++Format;
+            AddChar (&P, '%');
+            continue;
+        }
+
+        /* It's a format specifier. Check for flags. */
+        F = *Format++;
+        P.Flags = fNone;
+        Done = 0;
+        while (F != '\0' && !Done) {
+            switch (F) {
+                case '-': P.Flags |= fMinus; F = *Format++; break;
+                case '+': P.Flags |= fPlus;  F = *Format++; break;
+                case ' ': P.Flags |= fSpace; F = *Format++; break;
+                case '#': P.Flags |= fHash;  F = *Format++; break;
+                case '0': P.Flags |= fZero;  F = *Format++; break;
+                default:  Done     = 1;                     break;
+            }
+        }
+        /* Optional field width */
+        if (F == '*') {
+            P.Width = va_arg (P.ap, int);
+            /* A negative field width argument is taken as a - flag followed
+            ** by a positive field width.
+            */
+            if (P.Width < 0) {
+                P.Flags |= fMinus;
+                P.Width = -P.Width;
+            }
+            F = *Format++;
+            P.Flags |= fWidth;
+        } else if (IsDigit (F)) {
+            P.Width = F - '0';
+            while (1) {
+                F = *Format++;
+                if (!IsDigit (F)) {
+                    break;
+                }
+                P.Width = P.Width * 10 + (F - '0');
+            }
+            P.Flags |= fWidth;
+        }
+
+        /* Optional precision */
+        if (F == '.') {
+            F = *Format++;
+            P.Flags |= fPrec;
+            if (F == '*') {
+                P.Prec = va_arg (P.ap, int);
+                /* A negative precision argument is taken as if the precision
+                ** were omitted.
+                */
+                if (P.Prec < 0) {
+                    P.Flags &= ~fPrec;
+                }
+                F = *Format++;          /* Skip the '*' */
+            } else if (IsDigit (F)) {
+                P.Prec = F - '0';
+                while (1) {
+                    F = *Format++;
+                    if (!IsDigit (F)) {
+                        break;
+                    }
+                    P.Prec = P.Prec * 10 + (F - '0');
+                }
+            } else if (F == '-') {
+                /* A negative precision argument is taken as if the precision
+                ** were omitted.
+                */
+                F = *Format++;          /* Skip the minus */
+                while (IsDigit (F = *Format++)) ;
+                P.Flags &= ~fPrec;
+            } else {
+                P.Prec = 0;
+            }
+        }
+
+        /* Optional length modifier */
+        P.LengthMod = lmDefault;
+        switch (F) {
+
+            case 'h':
+                F = *Format++;
+                if (F == 'h') {
+                    F = *Format++;
+                    P.LengthMod = lmChar;
+                } else {
+                    P.LengthMod = lmShort;
+                }
+                break;
+
+            case 'l':
+                F = *Format++;
+                if (F == 'l') {
+                    F = *Format++;
+                    P.LengthMod = lmLongLong;
+                } else {
+                    P.LengthMod = lmLong;
+                }
+                break;
+
+            case 'j':
+                P.LengthMod = lmIntMax;
+                F = *Format++;
+                break;
+
+            case 'z':
+                P.LengthMod = lmSizeT;
+                F = *Format++;
+                break;
+
+            case 't':
+                P.LengthMod = lmPtrDiffT;
+                F = *Format++;
+                break;
+
+            case 'L':
+                P.LengthMod = lmLongDouble;
+                F = *Format++;
+                break;
+
+        }
+
+        /* If the space and + flags both appear, the space flag is ignored */
+        if ((P.Flags & (fSpace | fPlus)) == (fSpace | fPlus)) {
+            P.Flags &= ~fSpace;
+        }
+        /* If the 0 and - flags both appear, the 0 flag is ignored */
+        if ((P.Flags & (fZero | fMinus)) == (fZero | fMinus)) {
+            P.Flags &= ~fZero;
+        }
+        /* If a precision is specified, the 0 flag is ignored */
+        if (P.Flags & fPrec) {
+            P.Flags &= ~fZero;
+        }
+
+        /* Conversion specifier */
+        switch (F) {
+
+            case 'd':
+            case 'i':
+                P.Base = 10;
+                FormatInt (&P, NextIVal (&P));
+                break;
+
+            case 'o':
+                P.Flags |= fUnsigned;
+                P.Base = 8;
+                FormatInt (&P, NextUVal (&P));
+                break;
+
+            case 'u':
+                P.Flags |= fUnsigned;
+                P.Base = 10;
+                FormatInt (&P, NextUVal (&P));
+                break;
+
+            case 'X':
+                P.Flags |= (fUnsigned | fUpcase);
+                /* FALLTHROUGH */
+            case 'x':
+                P.Base = 16;
+                FormatInt (&P, NextUVal (&P));
+                break;
+
+            case 'c':
+                SBuf[0] = (char) va_arg (P.ap, int);
+                SBuf[1] = '\0';
+                FormatStr (&P, SBuf);
+                break;
+
+            case 's':
+                SPtr = va_arg (P.ap, const char*);
+                CHECK (SPtr != 0);
+                FormatStr (&P, SPtr);
+                break;
+
+            case 'p':
+                /* See comment at top of header file */
+                if (UseStrBuf) {
+                    /* Argument is StrBuf */
+                    const StrBuf* S = va_arg (P.ap, const StrBuf*);
+                    CHECK (S != 0);
+                    /* Handle the length by using a precision */
+                    if ((P.Flags & fPrec) != 0) {
+                        /* Precision already specified, use length of string 
+                        ** if less.
+                        */
+                        if ((unsigned) P.Prec > SB_GetLen (S)) {
+                            P.Prec = SB_GetLen (S);
+                        }
+                    } else {
+                        /* No precision, add it */
+                        P.Flags |= fPrec;
+                        P.Prec  = SB_GetLen (S);
+                    }
+                    FormatStr (&P, SB_GetConstBuf (S));
+                    UseStrBuf = 0;              /* Reset flag */
+                } else {
+                    /* Use hex format for pointers */
+                    P.Flags |= (fUnsigned | fPrec);
+                    P.Prec = ((sizeof (void*) * CHAR_BIT) + 3) / 4;
+                    P.Base = 16;
+                    FormatInt (&P, (uintptr_t) va_arg (P.ap, void*));
+                }
+                break;
+
+            case 'm':
+                /* See comment at top of header file */
+                UseStrBuf = 1;
+                break;
+
+            case 'n':
+                StoreOffset (&P);
+                break;
+
+            default:
+                /* Invalid format spec */
+                FAIL ("Invalid format specifier in xvsnprintf");
+
+        }
+    }
+
+    /* We don't need P.ap any longer */
+    va_end (P.ap);
+
+    /* Terminate the output string and return the number of chars that had
+    ** been written if the buffer was large enough.
+    ** Beware: The terminating zero is not counted for the function result!
+    */
+    AddChar (&P, '\0');
+    return P.BufFill - 1;
+}
+
+
+
+int xsnprintf (char* Buf, size_t Size, const char* Format, ...)
+/* A basic snprintf implementation. Does currently only support integer
+** formats.
+*/
+{
+    int Res;
+    va_list ap;
+
+    va_start (ap, Format);
+    Res = xvsnprintf (Buf, Size, Format, ap);
+    va_end (ap);
+
+    return Res;
+}
+
+
+
+/*****************************************************************************/
+/*                                   Code                                    */
 /*****************************************************************************/
 
 
@@ -64,19 +672,7 @@ int xsprintf (char* Buf, size_t BufSize, const char* Format, ...)
 int xvsprintf (char* Buf, size_t BufSize, const char* Format, va_list ap)
 /* Replacement function for sprintf */
 {
-#if defined(__WATCOMC__)
-    int Res = _vbprintf (Buf, BufSize, Format, ap);
-#elif defined(__GNUC__) && !defined(__GO32__)
-    int Res = vsnprintf (Buf, BufSize, Format, ap);
-#elif defined(_MSC_VER)
-    int Res = _vsnprintf (Buf, BufSize, Format, ap);
-#else
-    /* Unsafe version */
-    int Res = vsprintf (Buf, Format, ap);
-#endif
-    assert (Res >= 0 && (unsigned) Res < BufSize);
+    int Res = xvsnprintf (Buf, BufSize, Format, ap);
+    CHECK (Res >= 0 && (unsigned) (Res+1) < BufSize);
     return Res;
 }
-
-
-