/*****************************************************************************/ /* */ /* xsprintf.c */ /* */ /* Replacement sprintf function */ /* */ /* */ /* */ /* (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 */ /* warranty. In no event will the authors be held liable for any damages */ /* arising from the use of this software. */ /* */ /* Permission is granted to anyone to use this software for any purpose, */ /* including commercial applications, and to alter it and redistribute it */ /* freely, subject to the following restrictions: */ /* */ /* 1. The origin of this software must not be misrepresented; you must not */ /* claim that you wrote the original software. If you use this software */ /* in a product, an acknowledgment in the product documentation would be */ /* appreciated but is not required. */ /* 2. Altered source versions must be plainly marked as such, and must not */ /* be misrepresented as being the original software. */ /* 3. This notice may not be removed or altered from any source */ /* distribution. */ /* */ /*****************************************************************************/ #include #include #include #include /* common */ #include "chartype.h" #include "check.h" #include "inttypes.h" #include "strbuf.h" #include "va_copy.h" #include "xsprintf.h" /*****************************************************************************/ /* 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; break; case lmShort: *va_arg (P->ap, int*) = P->BufFill; break; case lmInt: *va_arg (P->ap, int*) = P->BufFill; break; case lmLong: *va_arg (P->ap, long*) = P->BufFill; break; case lmIntMax: *va_arg (P->ap, intmax_t*) = P->BufFill; break; case lmSizeT: *va_arg (P->ap, size_t*) = P->BufFill; break; case lmPtrDiffT: *va_arg (P->ap, ptrdiff_t*) = P->BufFill; break; 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 */ /*****************************************************************************/ int xsprintf (char* Buf, size_t BufSize, const char* Format, ...) /* Replacement function for sprintf */ { int Res; va_list ap; va_start (ap, Format); Res = xvsprintf (Buf, BufSize, Format, ap); va_end (ap); return Res; } int xvsprintf (char* Buf, size_t BufSize, const char* Format, va_list ap) /* Replacement function for sprintf */ { int Res = xvsnprintf (Buf, BufSize, Format, ap); CHECK (Res >= 0 && (unsigned) (Res+1) < BufSize); return Res; }