diff --git a/src/common/strbuf.c b/src/common/strbuf.c index 66c07b875..12f7239b7 100644 --- a/src/common/strbuf.c +++ b/src/common/strbuf.c @@ -36,8 +36,9 @@ #include /* common */ -#include "xmalloc.h" #include "strbuf.h" +#include "xmalloc.h" +#include "xsprintf.h" @@ -102,7 +103,7 @@ void FreeStrBuf (StrBuf* B) -static void SB_Realloc (StrBuf* B, unsigned NewSize) +void SB_Realloc (StrBuf* B, unsigned NewSize) /* Reallocate the string buffer space, make sure at least NewSize bytes are * available. */ @@ -304,7 +305,7 @@ void SB_Move (StrBuf* Target, StrBuf* Source) int SB_Compare (const StrBuf* S1, const StrBuf* S2) /* Do a lexical compare of S1 and S2. See strcmp for result codes. */ -{ +{ int Result; if (S1->Len < S2->Len) { Result = memcmp (S1->Buf, S2->Buf, S1->Len); @@ -326,3 +327,55 @@ int SB_Compare (const StrBuf* S1, const StrBuf* S2) +void SB_VPrintf (StrBuf* S, const char* Format, va_list ap) +/* printf function with S as target. The function is safe, which means that + * the current contents of S are discarded, and are allocated again with + * a matching size for the output. The function will call FAIL when problems + * are detected (anything that let xsnprintf return -1). + */ +{ + va_list tmp; + int SizeNeeded; + + /* Since we must determine the space needed anyway, we will try with + * the currently allocated memory. If the call succeeds, we've saved + * an allocation. If not, we have to reallocate and try again. + */ + va_copy (tmp, ap); + SizeNeeded = xvsnprintf (S->Buf, S->Allocated, Format, ap); + va_end (tmp); + + /* Check the result, the xvsnprintf function should not fail */ + CHECK (SizeNeeded >= 0); + + /* Check if we must reallocate */ + if ((unsigned) SizeNeeded >= S->Allocated) { + /* Must retry. Don't use Realloc to avoid copying */ + xfree (S->Buf); + S->Allocated = SizeNeeded + 1; /* Account for '\0' */ + S->Buf = xmalloc (S->Allocated); + (void) xvsnprintf (S->Buf, S->Allocated, Format, ap); + } + + /* Update string buffer variables */ + S->Len = SizeNeeded; + S->Index = 0; +} + + + +void SB_Printf (StrBuf* S, const char* Format, ...) +/* vprintf function with S as target. The function is safe, which means that + * the current contents of S are discarded, and are allocated again with + * a matching size for the output. The function will call FAIL when problems + * are detected (anything that let xsnprintf return -1). + */ +{ + va_list ap; + va_start (ap, Format); + SB_VPrintf (S, Format, ap); + va_end (ap); +} + + + diff --git a/src/common/strbuf.h b/src/common/strbuf.h index 35f32266c..1073f2594 100644 --- a/src/common/strbuf.h +++ b/src/common/strbuf.h @@ -38,6 +38,7 @@ +#include #include /* common */ @@ -90,6 +91,11 @@ StrBuf* NewStrBuf (void); void FreeStrBuf (StrBuf* B); /* Free a string buffer */ +void SB_Realloc (StrBuf* B, unsigned NewSize); +/* Reallocate the string buffer space, make sure at least NewSize bytes are + * available. + */ + #if defined(HAVE_INLINE) INLINE unsigned SB_GetLen (const StrBuf* B) /* Return the length of the buffer contents */ @@ -141,7 +147,7 @@ INLINE char SB_At (const StrBuf* B, unsigned Index) char SB_At (const StrBuf* B, unsigned Index); /* Get a character from the buffer */ #endif - + #if defined(HAVE_INLINE) INLINE char SB_AtUnchecked (const StrBuf* B, unsigned Index) /* Get a character from the buffer */ @@ -346,6 +352,20 @@ void SB_Move (StrBuf* Target, StrBuf* Source); int SB_Compare (const StrBuf* S1, const StrBuf* S2); /* Do a lexical compare of S1 and S2. See strcmp for result codes. */ +void SB_VPrintf (StrBuf* S, const char* Format, va_list ap); +/* printf function with S as target. The function is safe, which means that + * the current contents of S are discarded, and are allocated again with + * a matching size for the output. The function will call FAIL when problems + * are detected (anything that let xsnprintf return -1). + */ + +void SB_Printf (StrBuf* S, const char* Format, ...); +/* vprintf function with S as target. The function is safe, which means that + * the current contents of S are discarded, and are allocated again with + * a matching size for the output. The function will call FAIL when problems + * are detected (anything that let xsnprintf return -1). + */ + /* End of strbuf.h */ diff --git a/src/common/xsprintf.c b/src/common/xsprintf.c index 073aff49a..8c7dc33eb 100644 --- a/src/common/xsprintf.c +++ b/src/common/xsprintf.c @@ -6,10 +6,10 @@ /* */ /* */ /* */ -/* (C) 2000-2002 Ullrich von Bassewitz */ -/* Wacholderweg 14 */ -/* D-70597 Stuttgart */ -/* EMail: uz@musoftware.de */ +/* (C) 2000-2004 Ullrich von Bassewitz */ +/* Römerstrasse 52 */ +/* D-70794 Filderstadt */ +/* EMail: uz@cc65.org */ /* */ /* */ /* This software is provided 'as-is', without any expressed or implied */ @@ -34,12 +34,536 @@ #include +#include +#include +#include "chartype.h" #include "check.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 in some way or the other, 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, + lmSizeT, + lmPtrDiffT, + lmLongDouble, + + /* Unsupported modifiers */ + lmLongLong = lmLong, + lmIntMax = 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 long 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 lmSizeT: return (unsigned long) va_arg (P->ap, size_t); + case lmPtrDiffT: return (long) va_arg (P->ap, ptrdiff_t); + default: FAIL ("Invalid type size in NextIVal"); + } +} + + + +static unsigned long 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 va_arg (P->ap, unsigned long); + case lmSizeT: return (unsigned long) va_arg (P->ap, size_t); + case lmPtrDiffT: return (long) va_arg (P->ap, ptrdiff_t); + default: FAIL ("Invalid type size in NextUVal"); + } +} + + + +static void ToStr (PrintfCtrl* P, unsigned long 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, unsigned long 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" : "0123456789abcedf"; + + /* Check if the value is negative */ + if ((P->Flags & fUnsigned) == 0 && ((long) Val) < 0) { + Val = -((long) 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); + + /* 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->Flags & fPrec) == 0 || P->Prec <= P->ArgLen) { + Lead[LeadCount++] = '0'; + } + } + } + + /* Determine the amount of precision padding needed */ + if ((P->Flags & fPrec) != 0 && 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); + } +} + + + +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; + + + /* Initialize the control structure */ + 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 (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 (ap, int); + /* A negative precision argument is taken as if the precision + * were omitted. + */ + if (P.Prec < 0) { + P.Flags &= ~fPrec; + } + } 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. + */ + while (IsDigit (F = *Format++)) ; + P.Flags &= ~fPrec; + } + } + + /* 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 (ap, int); + SBuf[1] = '\0'; + FormatStr (&P, SBuf); + break; + + case 's': + SPtr = va_arg (ap, const char*); + CHECK (SPtr != 0); + FormatStr (&P, SPtr); + break; + + default: + /* Invalid format spec */ + FAIL ("Invalid format specifier in xvsnprintf"); + + } + } + + /* 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 */ /*****************************************************************************/ @@ -52,7 +576,7 @@ int xsprintf (char* Buf, size_t BufSize, const char* Format, ...) int Res; va_list ap; - va_start (ap, Format); + va_start (ap, Format); Res = xvsprintf (Buf, BufSize, Format, ap); va_end (ap); @@ -64,17 +588,8 @@ 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__) && !defined(__MINGW32__) - 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 - CHECK (Res >= 0 && (unsigned) Res < BufSize); + int Res = xvsnprintf (Buf, BufSize, Format, ap); + CHECK (Res >= 0 && (unsigned) (Res+1) < BufSize); return Res; } diff --git a/src/common/xsprintf.h b/src/common/xsprintf.h index 6981842fd..202a8b7b2 100644 --- a/src/common/xsprintf.h +++ b/src/common/xsprintf.h @@ -6,10 +6,10 @@ /* */ /* */ /* */ -/* (C) 2000 Ullrich von Bassewitz */ -/* Wacholderweg 14 */ -/* D-70597 Stuttgart */ -/* EMail: uz@musoftware.de */ +/* (C) 2000-2004 Ullrich von Bassewitz */ +/* Römerstrasse 52 */ +/* D-70794 Filderstadt */ +/* EMail: uz@cc65.org */ /* */ /* */ /* This software is provided 'as-is', without any expressed or implied */ @@ -51,13 +51,25 @@ +int xvsnprintf (char* Buf, size_t Size, const char* Format, va_list ap) + attribute ((format (printf, 3, 0))); +/* A basic vsnprintf implementation. Does currently only support integer + * formats. + */ + +int xsnprintf (char* Buf, size_t Size, const char* Format, ...) + attribute ((format (printf, 3, 4))); +/* A basic snprintf implementation. Does currently only support integer + * formats. + */ + int xsprintf (char* Buf, size_t BufSize, const char* Format, ...) attribute ((format (printf, 3, 4))); -/* Replacement function for sprintf */ +/* Replacement function for sprintf. Will FAIL on errors. */ int xvsprintf (char* Buf, size_t BufSize, const char* Format, va_list ap) attribute ((format (printf, 3, 0))); -/* Replacement function for sprintf */ +/* Replacement function for sprintf. Will FAIL on errors. */