CrystalSpace

Public API Reference

csutil/formatter.h

Go to the documentation of this file.
00001 /*
00002     Copyright (C) 2005 by Frank Richter
00003 
00004     This library is free software; you can redistribute it and/or
00005     modify it under the terms of the GNU Library General Public
00006     License as published by the Free Software Foundation; either
00007     version 2 of the License, or (at your option) any later version.
00008 
00009     This library is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     Library General Public License for more details.
00013 
00014     You should have received a copy of the GNU Library General Public
00015     License along with this library; if not, write to the Free
00016     Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00017 */
00018 
00019 #ifndef __CS_CSUTIL_FORMATTER_H__
00020 #define __CS_CSUTIL_FORMATTER_H__
00021 
00027 #include "cssysdef.h"
00028 #include "csgeom/math.h"
00029 #include "csutil/csuctransform.h"
00030 #include "csutil/dirtyaccessarray.h"
00031 #include "csutil/util.h"
00032 
00033 // MinGW uses MS CRT, but it can't grok long double.  VC doesn't have long
00034 // double and CRT printf() doesn't know %Lf, %Lg, or %Le.
00035 #if defined(__MINGW32__) || defined(CS_COMPILER_MSVC)
00036 #define CS_FORMATTER_NO_LONG_DOUBLE_FORMAT
00037 #endif
00038 // MinGWs <inttypes.h> uses the MS-specific 'I64' format specifier in its
00039 // PR?64 macros. Enable support for I64 so the PR?64 macros can be used.
00040 #if defined(__MINGW32__) || defined(CS_COMPILER_MSVC)
00041 #define CS_FORMATTER_PROVIDE_I64
00042 #endif
00043 
00071 template <class T>
00072 class csFmtDefaultReader
00073 {
00074   const T* str;
00075   const T* const startStr;
00076   size_t len;
00077   const size_t startLen;
00078 public:
00080   csFmtDefaultReader (const T* string, size_t length) : startStr (string), 
00081     startLen (length) { Reset(); }
00083   bool GetNext (utf32_char& ch) 
00084   {
00085     int n = csUnicodeTransform::Decode (str, len, ch);
00086     if (n == 0) return false;
00087     str += (size_t)n;
00088     len -= (size_t)n;
00089     return true;
00090   }
00092   void Reset() { str = startStr; len = startLen; }
00094   size_t GetPosition() const { return str - startStr; }
00095 };
00096 
00097 
00103 template <class T>
00104 class csFmtDefaultWriter
00105 {
00106   T* dest;
00107   size_t size;
00108   size_t total;
00109 public:
00111   csFmtDefaultWriter (T* dest, size_t size) : dest (dest), size (size), 
00112     total (0) {}
00114   void Put (utf32_char ch) 
00115   { 
00116     size_t n = (size_t)csUnicodeTransform::Encode (ch, dest, size);
00117     total += n;
00118     n = csMin (size, n);
00119     dest += n;
00120     size -= n;
00121   }
00126   size_t GetTotal() const { return total; }
00127 };
00128 
00134 template <class Twriter, class Treader>
00135 class csPrintfFormatter
00136 {
00137   class Scratch : public csDirtyAccessArray<utf32_char>
00138   {
00139   public:
00140     void WriteTo (Twriter& writer, size_t offset = 0, size_t len = (size_t)~0)
00141     {
00142       const size_t n = MIN (len, Length());
00143       for (size_t i = offset; i < n; i++) writer.Put (Get (i));
00144     }
00145   };
00146   Scratch scratch;
00147 
00149   struct FmtParam
00150   {
00151     union
00152     {
00153       int vInt;
00154       void* vPtr;
00155       long vLong;
00156       longlong vLL;
00157       double vDbl;
00158       long double vLongDbl;
00159       size_t vSzT;
00160       ptrdiff_t vPDT;
00161       intmax_t vIMT;
00162     };
00163   };
00164   enum Conversion
00165   {
00166     convBogus = 0,
00167     convNone,
00168     convInt,
00169     convOctal,
00170     convUint,
00171     convHex,
00172     convFloatFix,
00173     convFloatExp,
00174     convFloatGeneral,
00175     convFloatHex,
00176     convChar,
00177     convStr,
00178     convPtr,
00179     convGetNum,
00180     convErrno
00181   };
00182   enum Type
00183   {
00184     typeNone = 0,
00185     typeLongLong = 3, // The reason for that: see I64 support
00186     typeChar,
00187     typeShort,
00188     typeIntmax,
00189     typeLong,
00190     typePtrDiffT,
00191     typeSizeT
00192   };
00194   struct FormatSpec
00195   {
00196     size_t copyRun;
00197     size_t fmtSkip;
00198 
00199     int paramIdx;
00200     bool leftJustify;
00201     bool plusSign;
00202     bool spacePrefix;
00203     bool basePrefix;
00204     bool padZero;
00205     int width;
00206     int precision;
00207     Conversion conversion;
00208     bool uppercase;
00209     Type type;
00210 
00211     FormatSpec() { Reset(); }
00212     void Reset () 
00213     { 
00214       memset (this, 0, sizeof (*this)); 
00215       precision = -1;
00216     }
00217   };
00218   csArray<FormatSpec> formatSpecs;
00219   csArray<FmtParam> params;
00220   Treader& reader;
00221 
00222   struct SpecParseState
00223   {
00224     utf32_char ch;
00225     FormatSpec currentFormat;
00226     size_t charRun;
00227     int paramIdx;
00228     size_t fmtBegin;
00229 
00230     SpecParseState() : paramIdx(0) {}
00231     void Reset()
00232     {
00233       charRun = 0;
00234       currentFormat.Reset();
00235     }
00236   };
00237 
00238   bool ParseFlag (SpecParseState& state)
00239   {
00240     switch (state.ch)
00241     {
00242       case '-':
00243         {
00244           state.currentFormat.leftJustify = true;
00245           return true;
00246         }
00247       case '+':
00248         {
00249           state.currentFormat.plusSign = true;
00250           return true;
00251         }
00252       case ' ':
00253         {
00254           state.currentFormat.spacePrefix = true;
00255           return true;
00256         }
00257       case '#':
00258         {
00259           state.currentFormat.basePrefix = true;
00260           return true;
00261         }
00262       case '0':
00263         {
00264           state.currentFormat.padZero = true;
00265           return true;
00266         }
00267       case '\'':
00268         {
00269           return true;
00270         }
00271     }
00272     return false;
00273   }
00274   
00275   bool ParseType (SpecParseState& state)
00276   {
00277     switch (state.ch)
00278     {
00279       case 'h':
00280         {
00281           if (state.currentFormat.type == typeNone)
00282             state.currentFormat.type = typeShort;
00283           else if (state.currentFormat.type == typeShort)
00284             state.currentFormat.type = typeChar;
00285           else
00286             return false;
00287           return true;
00288         }
00289       case 'j':
00290         {
00291           if (state.currentFormat.type == typeNone)
00292             state.currentFormat.type = typeIntmax;
00293           else
00294             return false;
00295           return true;
00296         }
00297       case 'l':
00298         {
00299           if (state.currentFormat.type == typeNone)
00300             state.currentFormat.type = typeLong;
00301           else if (state.currentFormat.type == typeLong)
00302             state.currentFormat.type = typeLongLong;
00303           else
00304             return false;
00305           return true;
00306         }
00307       case 'L':
00308       case 'q':
00309         {
00310           if (state.currentFormat.type == typeNone)
00311             state.currentFormat.type = typeLongLong;
00312           else
00313             return false;
00314           return true;
00315         }
00316       case 't':
00317         {
00318           if (state.currentFormat.type == typeNone)
00319             state.currentFormat.type = typePtrDiffT;
00320           else
00321             return false;
00322           return true;
00323         }
00324       case 'z':
00325         {
00326           if (state.currentFormat.type == typeNone)
00327             state.currentFormat.type = typeSizeT;
00328           else
00329             return false;
00330           return true;
00331         }
00332 #ifdef CS_FORMATTER_PROVIDE_I64
00333       case 'I':
00334       case '6':
00335       case '4':
00336         {
00337           static const utf32_char I64spec[3] = {'I', '6', '4'};
00338           const int I64specStartType = typeLongLong - 2;
00339           if (state.ch == I64spec[0])
00340             state.currentFormat.type = (Type)I64specStartType;
00341           else
00342           {
00343             state.currentFormat.type = (Type)(state.currentFormat.type + 1);
00344             if (state.ch != 
00345               I64spec[state.currentFormat.type - I64specStartType])
00346               return false;
00347           }
00348           return true;
00349         }
00350         break;
00351 #endif
00352     }
00353     return false;
00354   }
00355 
00356   bool ParseConversion (SpecParseState& state)
00357   {
00358 #ifdef CS_FORMATTER_PROVIDE_I64
00359     // Check to detect incomplete I64 specifiers
00360     const int I64specStartType = typeLongLong - 2;
00361     if ((state.currentFormat.type >= I64specStartType)
00362       && (state.currentFormat.type < typeLongLong))
00363       return false;
00364 #endif
00365     switch (state.ch)
00366     {
00367       case '%':
00368         {
00369           const size_t fmtLen = (reader.GetPosition() - 1) - state.fmtBegin;
00370           if (fmtLen == 1)
00371           {
00372             state.currentFormat.conversion = convNone;
00373             state.fmtBegin++;
00374             state.currentFormat.copyRun++;
00375             return true;
00376           }
00377           break;
00378         }
00379       case 'd':
00380       case 'i':
00381         {
00382           state.currentFormat.conversion = convInt;
00383           return true;
00384         }
00385       case 'o':
00386         {
00387           state.currentFormat.conversion = convOctal;
00388           return true;
00389         }
00390       case 'u':
00391         {
00392           state.currentFormat.conversion = convUint;
00393           return true;
00394         }
00395       case 'x':
00396       case 'X':
00397         {
00398           state.currentFormat.conversion = convHex;
00399           state.currentFormat.uppercase = (state.ch == 'X');
00400           return true;
00401         }
00402       case 'f':
00403         {
00404           state.currentFormat.conversion = convFloatFix;
00405           return true;
00406         }
00407       case 'e':
00408       case 'E':
00409         {
00410           state.currentFormat.conversion = convFloatExp;
00411           state.currentFormat.uppercase = (state.ch == 'E');
00412           return true;
00413         }
00414       case 'g':
00415       case 'G':
00416         {
00417           state.currentFormat.conversion = convFloatGeneral;
00418           state.currentFormat.uppercase = (state.ch == 'G');
00419           return true;
00420         }
00421       case 'a':
00422       case 'A':
00423         {
00424           state.currentFormat.conversion = convFloatHex;
00425           state.currentFormat.uppercase = (state.ch == 'A');
00426           return true;
00427         }
00428       case 'c':
00429         {
00430           state.currentFormat.conversion = convChar;
00431           return true;
00432         }
00433       case 'C':
00434         {
00435           state.currentFormat.conversion = convChar;
00436           state.currentFormat.type = typeLong;
00437           return true;
00438         }
00439       case 's':
00440         {
00441           state.currentFormat.conversion = convStr;
00442           return true;
00443         }
00444       case 'S':
00445         {
00446           state.currentFormat.conversion = convStr;
00447           state.currentFormat.type = typeLong;
00448           return true;
00449         }
00450       case 'p':
00451         {
00452           state.currentFormat.conversion = convPtr;
00453           return true;
00454         }
00455       case 'n':
00456         {
00457           state.currentFormat.conversion = convGetNum;
00458           return true;
00459         }
00460       case 'm':
00461         {
00462           state.currentFormat.conversion = convErrno;
00463           return true;
00464         }
00465     }
00466     return false;
00467   }
00468 
00469   void ParseSpec ()
00470   {
00471     enum {
00472       scanFormat,
00473       formatParamFlagsWidthPrecTypeConversion,
00474       formatFlagsWidthPrecTypeConversion,
00475       formatParamWidth,
00476       formatDotPrecTypeConversion,
00477       formatPrecTypeConversion,
00478       formatTypeConversion
00479     } parseState = scanFormat;
00480 
00481     // Collect positions of state specifiers from format string
00482     SpecParseState state;
00483     state.Reset();
00484     while (reader.GetNext (state.ch))
00485     {
00486       switch (parseState)
00487       {
00488         // Note: all falling through in this switch() is intentional.
00489         case scanFormat:
00490           {
00491             // Check for a % sign
00492             if (state.ch == '%')
00493             {
00494               parseState = formatParamFlagsWidthPrecTypeConversion;
00495               state.fmtBegin = reader.GetPosition() - 1;
00496               state.currentFormat.copyRun = state.charRun;
00497             }
00498             else
00499               state.charRun++;
00500           }
00501           break;
00502         case formatParamFlagsWidthPrecTypeConversion:
00503           // Check for start of width or param index
00504           if ((state.ch >= '1') && (state.ch <= '9'))
00505           {
00506             state.currentFormat.width = state.ch - '0';
00507             parseState = formatParamWidth;
00508             break;
00509           }
00510           // Check for '*' (fetch width from args)
00511           else if (state.ch == '*')
00512           {
00513             state.currentFormat.width = -2;
00514             parseState = formatDotPrecTypeConversion;
00515             break;
00516           }
00517           // Param delimiter
00518           else if (state.ch == '$')
00519           {
00520             // \todo fix for empty param
00521             parseState = formatFlagsWidthPrecTypeConversion;
00522             break;
00523           }
00524         case formatParamWidth:
00525           if (parseState == formatParamWidth) // != can occur due fallthrough
00526           {
00527             // Subsequent digits width or param index
00528             if ((state.ch >= '0') && (state.ch <= '9'))
00529             {
00530               state.currentFormat.width *= 10;
00531               state.currentFormat.width += state.ch - '0';
00532               break;
00533             }
00534             // Param delimiter
00535             else if (state.ch == '$')
00536             {
00537               state.paramIdx = state.currentFormat.width - 1;
00538               state.currentFormat.width = 0;
00539               parseState = formatFlagsWidthPrecTypeConversion;
00540               break;
00541             }
00542           }
00543         case formatFlagsWidthPrecTypeConversion:
00544           // Check for start of width
00545           if ((state.ch >= '1') && (state.ch <= '9'))
00546           {
00547             state.currentFormat.width *= 10;
00548             state.currentFormat.width += state.ch - '0';
00549             parseState = formatParamWidth;
00550             break;
00551           }
00552           // Check for '*' (fetch width from args)
00553           else if (state.ch == '*')
00554           {
00555             state.currentFormat.width = -2;
00556             parseState = formatDotPrecTypeConversion;
00557             break;
00558           }
00559           // Check for flags (0, -, ...)
00560           else if (ParseFlag (state))
00561           {
00562             parseState = formatFlagsWidthPrecTypeConversion;
00563             break;
00564           }
00565         case formatDotPrecTypeConversion:
00566           // Check for precision delimiter
00567           if (state.ch == '.')
00568           {
00569             parseState = formatPrecTypeConversion;
00570             state.currentFormat.precision = 0;
00571             break;
00572           }
00573         case formatPrecTypeConversion:
00574           // Precision digits
00575           if ((state.ch >= '0') && (state.ch <= '9'))
00576           {
00577             state.currentFormat.precision *= 10;
00578             state.currentFormat.precision += state.ch - '0';
00579             break;
00580           }
00581           // Check for '*' (fetch precision from args)
00582           else if (state.ch == '*')
00583           {
00584             state.currentFormat.precision = -2;
00585             parseState = formatTypeConversion;
00586             break;
00587           }
00588           // Check for param type modifier (l, h, ...)
00589         case formatTypeConversion:
00590           if (ParseType (state))
00591           {
00592             parseState = formatTypeConversion;
00593             break;
00594           }
00595           // Check actual conversion (s, d, ...)
00596           else if (ParseConversion (state))
00597           {
00598             state.currentFormat.fmtSkip =
00599               reader.GetPosition() - state.fmtBegin;
00600             if (state.currentFormat.conversion != convNone)
00601               state.currentFormat.paramIdx = state.paramIdx++;
00602             formatSpecs.Push (state.currentFormat);
00603 
00604             state.Reset();
00605           }
00606           else
00607           {
00608             state.charRun += reader.GetPosition() - state.fmtBegin;
00609             state.currentFormat.Reset();
00610           }
00611           parseState = scanFormat;
00612           break;
00613       }
00614     }
00615   }
00616 
00618   void FetchArgs (va_list args)
00619   {
00620     size_t i;
00621     // Determine order of params
00622     csArray<FormatSpec*> paramOrder;
00623     paramOrder.SetCapacity (formatSpecs.Length());
00624     for (i = 0; i < formatSpecs.Length(); i++)
00625     {
00626       FormatSpec& currentFormat = formatSpecs[i];
00627       if (currentFormat.conversion == convNone) continue;
00628       if (paramOrder.Length() <= (size_t)currentFormat.paramIdx)
00629         paramOrder.SetLength (currentFormat.paramIdx + 1, 0);
00630       paramOrder[currentFormat.paramIdx] = &currentFormat;
00631     }
00632     // Fetch params from stack in order, store at correct place in params array
00633     for (i = 0; i < paramOrder.Length(); i++)
00634     {
00635       FmtParam& param = params.GetExtend (i);
00636       FormatSpec* fmtPtr = paramOrder[i];
00637       if (fmtPtr == 0) 
00638       {
00639         // Can just guess here...
00640         param.vInt = va_arg (args, int);
00641         continue;
00642       }
00643       FormatSpec& currentFormat = *fmtPtr;
00644 
00645       if (currentFormat.width == -2)
00646       {
00647         currentFormat.width = va_arg (args, int);
00648         if (currentFormat.width < 0)
00649         {
00650           currentFormat.width = -currentFormat.width;
00651           currentFormat.leftJustify = true;
00652         }
00653       }
00654       if (currentFormat.precision == -2)
00655       {
00656         int v = va_arg (args, int);
00657         if (v >= 0) 
00658           currentFormat.precision = v;
00659         else
00660           currentFormat.precision = -1;
00661       }
00662       switch (currentFormat.conversion)
00663       {
00664         case convInt:
00665         case convOctal:
00666         case convUint:
00667         case convHex:
00668         default:
00669           {
00670             switch (currentFormat.type)
00671             {
00672               case typeIntmax:
00673                 param.vIMT = va_arg (args, intmax_t);
00674                 break;
00675               case typeLong:
00676                 param.vLong = va_arg (args, long);
00677                 break;
00678               case typeLongLong:
00679                 param.vLL = va_arg (args, longlong);
00680                 break;
00681               case typePtrDiffT:
00682                 param.vPDT = va_arg (args, ptrdiff_t);
00683                 break;
00684               case typeSizeT:
00685                 param.vSzT = va_arg (args, size_t);
00686                 break;
00687               case typeShort:
00688                 param.vInt = (short)(va_arg (args, int));
00689                 break;
00690               case typeChar:
00691                 param.vInt = (char)(va_arg (args, int));
00692                 break;
00693               default:
00694                 param.vInt = va_arg (args, int);
00695                 break;
00696             }
00697           }
00698           break;
00699         case convErrno:
00700           param.vInt = errno;
00701           break;
00702         case convChar:
00703           if (currentFormat.type == typeLong)
00704           {
00705             param.vInt = (wint_t)(va_arg (args, int));
00706           }
00707           else
00708           {
00709             param.vInt = (unsigned char)(va_arg (args, int));
00710           }
00711           break;
00712         case convFloatFix:
00713         case convFloatExp:
00714         case convFloatGeneral:
00715         case convFloatHex:
00716           if (currentFormat.type == typeLongLong)
00717           {
00718             param.vLongDbl = va_arg (args, long double);
00719           }
00720           else
00721           {
00722             param.vDbl = va_arg (args, double);
00723           }
00724           break;
00725         case convStr:
00726         case convPtr:
00727         case convGetNum:
00728           param.vPtr = va_arg (args, void*);
00729           break;
00730         case convNone:
00731           break;
00732       }
00733     }
00734   }
00735 
00736   void Init (va_list args)
00737   {
00738     ParseSpec ();
00739     FetchArgs (args);
00740   }
00741 
00743   template<class T>
00744   void OutputString (Twriter& writer, const FormatSpec& currentFormat,
00745     const T* stringPtr)
00746   {
00747     if (stringPtr == 0)
00748     {
00749       OutputString (writer, currentFormat, (utf8_char*)"(null)");
00750       return;
00751     }
00752 
00753     size_t scratchOffs = scratch.Length();
00754     size_t len = 0;
00755     {
00756       const T* ptr = stringPtr;
00757       while (*ptr++ != 0) len++;
00758     }
00759     if (currentFormat.precision > -1)
00760       len = MIN(len, (size_t)currentFormat.precision);
00761     while (len > 0)
00762     {
00763       utf32_char ch;
00764       int n = csUnicodeTransform::Decode (stringPtr, len, ch);
00765       scratch.Push (ch);
00766       stringPtr += n;
00767       len -= (size_t)n;
00768     }
00769     if (!currentFormat.leftJustify 
00770       && ((size_t)currentFormat.width > (scratch.Length() - scratchOffs)))
00771     {
00772       size_t d = (size_t)currentFormat.width - scratch.Length() + scratchOffs;
00773       while (d-- > 0) writer.Put (' ');
00774     }
00775     scratch.WriteTo (writer, scratchOffs);
00776     if (currentFormat.leftJustify 
00777       && ((size_t)currentFormat.width > (scratch.Length() - scratchOffs)))
00778     {
00779       size_t d = (size_t)currentFormat.width - scratch.Length() + scratchOffs;
00780       while (d-- > 0) writer.Put (' ');
00781     }
00782     scratch.Truncate (scratchOffs);
00783   }
00784 
00786   void DoPadding (const FormatSpec& currentFormat, const size_t scratchOffs,
00787     const size_t insert0offs)
00788   {
00789     if (currentFormat.leftJustify)
00790     {
00791       while ((size_t)currentFormat.width > (scratch.Length() - scratchOffs))
00792       {
00793         scratch.Push (' ');
00794       }
00795     }
00796     else
00797     {
00798       if (currentFormat.padZero)
00799       {
00800         while ((size_t)currentFormat.width > (scratch.Length() - scratchOffs))
00801         {
00802           scratch.Insert (insert0offs, '0');
00803         }
00804       }
00805       else
00806       {
00807         while ((size_t)currentFormat.width > (scratch.Length() - scratchOffs))
00808         {
00809           scratch.Insert (scratchOffs, ' ');
00810         }
00811       }
00812     }
00813   }
00814 
00816   template<class T>
00817   void OutputInt (Twriter& writer, const FormatSpec& currentFormat, T value)
00818   {
00819     const size_t scratchOffs = scratch.Length();
00820     size_t insertOffs = scratchOffs;
00821 
00822     if (value < 0)
00823     {
00824       scratch.Push ('-');
00825       insertOffs++;
00826       value = -value;
00827     }
00828     else if (currentFormat.plusSign)
00829     {
00830       scratch.Push ('+');
00831       insertOffs++;
00832     }
00833     else if (currentFormat.spacePrefix)
00834     {
00835       scratch.Push (' ');
00836       insertOffs++;
00837     }
00838 
00839     int width = 0;
00840     int numDigits = currentFormat.precision;
00841     if (!((value == 0) && (numDigits == 0)))
00842     {
00843       do
00844       {
00845         int d = (int)(value % 10);
00846         scratch.Insert (insertOffs, d + '0');
00847         width++;
00848         value = value / 10;
00849       }
00850       while ((value != 0) || (width < numDigits));
00851     }
00852     DoPadding (currentFormat, scratchOffs, insertOffs);
00853     scratch.WriteTo (writer, scratchOffs);
00854     scratch.Truncate (scratchOffs);
00855   }
00856 
00858   template<class T>
00859   void OutputUint (Twriter& writer, const FormatSpec& currentFormat,
00860     T value, uint radix = 10, const char* prefix = 0)
00861   {
00862     const utf32_char letterFirst = currentFormat.uppercase ? 'A' : 'a';
00863     const size_t scratchOffs = scratch.Length();
00864     size_t insertOffs = scratchOffs;
00865 
00866     if (prefix != 0)
00867     {
00868       while (*prefix != 0)
00869       {
00870         utf32_char ch = (value != 0) ? *prefix : ' ';
00871         scratch.Push (ch);
00872         insertOffs++;
00873         prefix++;
00874       }
00875     }
00876 
00877     int width = 0;
00878     int numDigits = currentFormat.precision;
00879     if (!((value == 0) && (numDigits == 0)))
00880     {
00881       do
00882       {
00883         uint d = (uint)(value % radix);
00884         utf32_char ch;
00885         if (d <= 9)
00886           ch = d + '0';
00887         else
00888           ch = d - 10 + letterFirst;
00889         scratch.Insert (insertOffs, ch);
00890         width++;
00891         value = value / radix;
00892       }
00893       while ((value != 0) || (width < numDigits));
00894     }
00895     DoPadding (currentFormat, scratchOffs, insertOffs);
00896     scratch.WriteTo (writer, scratchOffs);
00897     scratch.Truncate (scratchOffs);
00898   }
00899 
00901   template<class T>
00902   void OutputFloat (Twriter& writer, const FormatSpec& currentFormat,
00903     const T& value, const char* type)
00904   {
00905     char flags[5] = "";
00906     if (currentFormat.plusSign)
00907       strcat (flags, "+");
00908     if (currentFormat.spacePrefix)
00909       strcat (flags, " ");
00910     if (currentFormat.basePrefix)
00911       strcat (flags, "#");
00912     if (currentFormat.padZero)
00913       strcat (flags, "0");
00914     /* (sizeof(x)*25)/10+1 is an approximation of the number of characters
00915      * needed to display x in decimal system. (x can be at most 256^sizeof(x).
00916      * You need log10(256^sizeof(x)) characters, becoming
00917      * sizeof(x)*log10(256). 25/10 is an (over-)approximation of log10(256).
00918      * Add 1 for sign.) */
00919     CS_ALLOC_STACK_ARRAY(char, precStr, 
00920       (sizeof(currentFormat.precision) * 25) / 10 + 2);
00921     if (currentFormat.precision >= 0)
00922       sprintf (precStr, ".%d", currentFormat.precision);
00923     else
00924       precStr[0] = 0;
00925     CS_ALLOC_STACK_ARRAY(char, formatStr, 1 + strlen (flags)
00926       + (sizeof(currentFormat.width) * 25) / 10 + 1 + strlen (precStr) + 2);
00927     sprintf (formatStr, "%%%s%d%s%s", flags, currentFormat.width, precStr,
00928       type);
00929     // Make sure *any* number thrown at us fits
00930     char formattedStr[LDBL_MAX_10_EXP+3]; 
00931     sprintf (formattedStr, formatStr, value);
00932 
00933     char* p = formattedStr;
00934     while (*p != 0)
00935       writer.Put (*p++);
00936   }
00937 
00941   template<class T, class Tbase>
00942   struct IEEEFloatMantissa
00943   {
00944     Tbase mantissa[sizeof(T)/sizeof(Tbase)];
00945 
00946     Tbase& operator[] (int index)
00947     { return mantissa[index]; }
00948     bool Eq0 ()
00949     {
00950       for (uint n = 0; n < sizeof(T)/sizeof(Tbase); n++)
00951       {
00952         if (mantissa[n] != 0) return false;
00953       }
00954       return true;
00955     }
00956     const Tbase operator& (Tbase other) const
00957     { return mantissa[0] & other; }
00958     IEEEFloatMantissa& operator<<= (int shift)
00959     { 
00960       const int ovShift = sizeof(Tbase) * 8 - shift;
00961       Tbase overflow = 0;
00962       for (uint n = 0; n < sizeof(T)/sizeof(Tbase); n++)
00963       {
00964         Tbase newOverflow = mantissa[n] >> ovShift;
00965         mantissa[n] = (mantissa[n] << shift) | overflow;
00966         overflow = newOverflow;
00967       }
00968       return *this;
00969     }
00970     Tbase& Leftmost ()
00971     { return mantissa[sizeof(T)/sizeof(Tbase)-1]; }
00972   };
00973 
00975   template<class T, class Tbase>
00976   struct IEEEFloatSplitter
00977   {
00978     bool sign;
00979     Tbase exp;
00980 
00981     typename csPrintfFormatter<Twriter,Treader>::
00982       template IEEEFloatMantissa<T, Tbase> mantissa;
00983 
00984     IEEEFloatSplitter (const T& val, const int mantissaBits,
00985       const int expBits) 
00986     {
00987       const int baseBits = sizeof(Tbase) * 8;
00988       const int signBit = mantissaBits + expBits;
00989 
00990       union
00991       {
00992         T v;
00993         Tbase vB[sizeof(T)/sizeof(Tbase)];
00994       } toBase;
00995       toBase.v = val;
00996   #ifdef CS_LITTLE_ENDIAN
00997       const int hi = (sizeof (T) / sizeof (Tbase)) - 1;
00998       const int lo = 0;
00999       const int d = 1;
01000   #else
01001       const int hi = 0;
01002       const int lo = (sizeof (T) / sizeof (Tbase)) - 1;
01003       const int d = -1;
01004   #endif
01005       sign = ((toBase.vB[lo + (signBit / baseBits) * d]
01006         & (1 << (signBit % baseBits))) != 0);
01007       exp = (toBase.vB[hi] >> (mantissaBits % (baseBits)))
01008         & ((1 << expBits) - 1);
01009       for (int n = lo, p = 0; n != hi + d; n += d, p++)
01010       {
01011         const int bit = p * baseBits;
01012         const Tbase mask = ((bit + baseBits) <= mantissaBits) ? ~0 
01013           : ((1 << (mantissaBits % baseBits)) - 1);
01014         mantissa[p] = toBase.vB[n] & mask;
01015       }
01016     }
01017   };
01019   template <class T>
01020   void OutputFloatHex (Twriter& writer, const FormatSpec& currentFormat,
01021     const T& value, const int vMantissaBits, const int expBits, const int bias)
01022   {
01023 #ifdef CS_IEEE_DOUBLE_FORMAT
01024     const utf32_char letterFirst = currentFormat.uppercase ? 'A' : 'a';
01025 
01026 #ifdef CS_PROCESSOR_X86
01027     // @@@ x86 long double uses explicit mantissa MSB
01028     const bool hiddenBit = !(vMantissaBits >= 63);
01029 #else
01030     const bool hiddenBit = false;
01031 #endif
01032     const int mantissaBits = vMantissaBits - (hiddenBit ? 1 : 0);
01033     IEEEFloatSplitter<T, uint> vSplit (value, mantissaBits, expBits);
01034     const uint expMax = (1 << (sizeof(T) * 8 - mantissaBits - 1)) - 1;
01035 
01036     if ((vSplit.exp == expMax) && vSplit.mantissa.Eq0())
01037     {
01038       char infStr[5];
01039       if (vSplit.sign)
01040       {
01041         strcpy (infStr, "-");
01042       }
01043       else
01044       {
01045         if (currentFormat.plusSign)
01046           strcpy (infStr, "+");
01047         else if (currentFormat.spacePrefix)
01048           strcpy (infStr, " ");
01049         else
01050           strcpy (infStr, "");
01051       }
01052       strcat (infStr, currentFormat.uppercase ? "INF" : "inf");
01053       OutputString (writer, currentFormat, 
01054         (utf8_char*)infStr);
01055       return;
01056     }
01057     else if ((vSplit.exp == expMax) && !vSplit.mantissa.Eq0())
01058     {
01059       char nanStr[5];
01060       if (vSplit.sign)
01061       {
01062         strcpy (nanStr, "-");
01063       }
01064       else
01065       {
01066         if (currentFormat.plusSign)
01067           strcpy (nanStr, "+");
01068         else if (currentFormat.spacePrefix)
01069           strcpy (nanStr, " ");
01070         else
01071           strcpy (nanStr, "");
01072       }
01073       strcat (nanStr, currentFormat.uppercase ? "NAN" : "nan");
01074       OutputString (writer, currentFormat, 
01075         (utf8_char*)nanStr);
01076       return;
01077     }
01078 
01079     const size_t scratchOffs = scratch.Length();
01080     if (vSplit.sign)
01081     {
01082       scratch.Push ('-');
01083     }
01084     scratch.Push ('0');
01085     scratch.Push (currentFormat.uppercase ? 'X' : 'x');
01086     if (hiddenBit)
01087     {
01088       if (vSplit.exp == 0)
01089         scratch.Push ('0');
01090       else
01091         scratch.Push ('1');
01092     }
01093     else
01094     {
01095       const int bitNum = mantissaBits - 1;
01096       const int baseBits = sizeof (uint) * 8;
01097       const int bitIndex = bitNum / baseBits;
01098       scratch.Push ('0' + ((vSplit.mantissa[bitIndex] 
01099         >> (bitNum % baseBits)) & 1));
01100       vSplit.mantissa <<= 1;
01101     }
01102     if ((currentFormat.precision > 0) || (!vSplit.mantissa.Eq0()))
01103     {
01104       scratch.Push ('.');
01105       
01106       IEEEFloatMantissa<T, uint> m (vSplit.mantissa);
01107       m <<= sizeof(T)*8 - mantissaBits;
01108       int w = 0;
01109       do
01110       {
01111         uint d = m.Leftmost() >> ((sizeof(uint)*8)-4);
01112         utf32_char ch;
01113         if (d <= 9)
01114           ch = d + '0';
01115         else
01116           ch = d - 10 + letterFirst;
01117         scratch.Push (ch);
01118         m <<= 4;
01119         w++;
01120       }
01121       while ((w < currentFormat.precision) 
01122         || ((currentFormat.precision <= 0) && !m.Eq0()));
01123     }
01124     scratch.Push (currentFormat.uppercase ? 'P' : 'p');
01125     int e;
01126     if ((vSplit.exp == 0) && vSplit.mantissa.Eq0())
01127       e = 0;
01128     else
01129       e = (int)vSplit.exp + bias;
01130     if (e < 0)
01131     {
01132       scratch.Push ('-');
01133       e = -e;
01134     }
01135     else
01136       scratch.Push ('+');
01137     const size_t insertOffs = scratch.Length();;
01138     do
01139     {
01140       uint d = e % 10;
01141       scratch.Insert (insertOffs, d + '0');
01142       e = e / 10;
01143     }
01144     while (e != 0);
01145 
01146     DoPadding (currentFormat, scratchOffs, 
01147       vSplit.sign ? scratchOffs + 1 : scratchOffs);
01148     scratch.WriteTo (writer, scratchOffs);
01149     scratch.Truncate (scratchOffs);
01150 #else
01151   #if defined(CS_COMPILER_GCC)
01152     #warning Do not know how to hex-format floats
01153   #elif defined(CS_COMPILER_MSVC)
01154     #pragma message("Do not know how to hex-format floats")
01155   #endif
01156 #endif
01157   }
01158 public:
01160   csPrintfFormatter (Treader* reader, va_list args) : reader (*reader)
01161   {
01162     Init (args);
01163   }
01165   csPrintfFormatter (Treader* reader, ...) : reader (*reader)
01166   {
01167     va_list ap;
01168     va_start(ap, reader);
01169     Init (ap);
01170     va_end(ap);
01171   }
01173   void Format (Twriter& writer)
01174   {
01175     reader.Reset();
01176     size_t i = 0;
01177     utf32_char ch;
01178     while (i < formatSpecs.Length())
01179     {
01180       const FormatSpec& currentFormat = formatSpecs[i];
01181       size_t n;
01182       for (n = 0; n < currentFormat.copyRun; n++)
01183       {
01184         if (!reader.GetNext (ch)) break;
01185         writer.Put (ch);
01186       }
01187 
01188       switch (currentFormat.conversion)
01189       {
01190         case convStr:
01191           {
01192             if (currentFormat.type == typeLong)
01193               OutputString (writer, currentFormat, 
01194               (wchar_t*)(params[currentFormat.paramIdx].vPtr));
01195             else
01196               OutputString (writer, currentFormat, 
01197               (utf8_char*)(params[currentFormat.paramIdx].vPtr));
01198           }
01199           break;
01200         case convChar:
01201           {
01202             writer.Put (params[currentFormat.paramIdx].vInt);
01203           }
01204           break;
01205         case convInt:
01206           {
01207             const FmtParam& param = params[currentFormat.paramIdx];
01208             switch (currentFormat.type)
01209             {
01210               case typeIntmax:
01211                 {
01212                   intmax_t v = param.vIMT;
01213                   OutputInt (writer, currentFormat, v);
01214                 }
01215                 break;
01216               case typeLong:
01217                 {
01218                   long v = param.vLong;
01219                   OutputInt (writer, currentFormat, v);
01220                 }
01221                 break;
01222               case typeLongLong:
01223                 {
01224                   longlong v = param.vLL;
01225                   OutputInt (writer, currentFormat, v);
01226                 }
01227                 break;
01228               case typePtrDiffT:
01229                 {
01230                   ptrdiff_t v = param.vPDT;
01231                   OutputInt (writer, currentFormat, v);
01232                 }
01233                 break;
01234               case typeSizeT:
01235                 {
01236                   size_t v = param.vSzT;
01237                   OutputUint (writer, currentFormat, v);
01238                 }
01239                 break;
01240               default:
01241                 {
01242                   int v = param.vInt;
01243                   OutputInt (writer, currentFormat, v);
01244                 }
01245                 break;
01246             }
01247           }
01248           break;
01249         case convHex:
01250         case convUint:
01251         case convOctal:
01252           {
01253             uint uiradix;
01254             const char* prefix;
01255             if (currentFormat.conversion == convHex)
01256             {
01257               uiradix = 16;
01258               prefix = currentFormat.basePrefix 
01259                 ? (currentFormat.uppercase ? "0X" : "0x") : 0;
01260             }
01261             else if (currentFormat.conversion == convOctal)
01262             {
01263               uiradix = 8;
01264               prefix = currentFormat.basePrefix ? "0" : 0;
01265             }
01266             else
01267             {
01268               uiradix = 10;
01269               prefix = 0;
01270             }
01271             const FmtParam& param = params[currentFormat.paramIdx];
01272             switch (currentFormat.type)
01273             {
01274               case typeIntmax:
01275                 {
01276                   intmax_t v = param.vIMT;
01277                   OutputUint (writer, currentFormat, v, uiradix, prefix);
01278                 }
01279                 break;
01280               case typeLong:
01281                 {
01282                   unsigned long v = param.vLong;
01283                   OutputUint (writer, currentFormat, v, uiradix, prefix);
01284                 }
01285                 break;
01286               case typeLongLong:
01287                 {
01288                   ulonglong v = param.vLL;
01289                   OutputUint (writer, currentFormat, v, uiradix, prefix);
01290                 }
01291                 break;
01292               case typePtrDiffT:
01293                 {
01294                   ptrdiff_t v = param.vPDT;
01295                   OutputUint (writer, currentFormat, v, uiradix, prefix);
01296                 }
01297                 break;
01298               case typeSizeT:
01299                 {
01300                   size_t v = param.vSzT;
01301                   OutputUint (writer, currentFormat, v, uiradix, prefix);
01302                 }
01303                 break;
01304               default:
01305                 {
01306                   uint v = param.vInt;
01307                   OutputUint (writer, currentFormat, v, uiradix, prefix);
01308                 }
01309                 break;
01310             }
01311           }
01312           break;
01313         case convGetNum:
01314           *((int*)(params[currentFormat.paramIdx].vPtr))
01315                 = (int)writer.GetTotal();
01316           break;
01317         case convErrno:
01318           OutputString (writer, currentFormat, 
01319             (utf8_char*)strerror (params[currentFormat.paramIdx].vInt));
01320           break;
01321         case convPtr:
01322           {
01323             FormatSpec fakeFormat;
01324             fakeFormat.leftJustify = currentFormat.leftJustify;
01325             fakeFormat.precision = sizeof (uintptr_t) * 2;
01326             if (params[currentFormat.paramIdx].vPtr == 0)
01327             {
01328               OutputString (writer, fakeFormat, (utf8_char*)"(nil)");
01329             }
01330             else
01331             {
01332               OutputUint (writer, fakeFormat, 
01333                 (uintptr_t)params[currentFormat.paramIdx].vPtr, 16, "0x");
01334             }
01335           }
01336           break;
01337         case convFloatFix:
01338           {
01339             if (currentFormat.type == typeLongLong)
01340             {
01341 #ifdef CS_FORMATTER_NO_LONG_DOUBLE_FORMAT
01342               OutputFloat (writer, currentFormat, 
01343               (double)params[currentFormat.paramIdx].vLongDbl, "f");
01344 #else
01345               OutputFloat (writer, currentFormat, 
01346               params[currentFormat.paramIdx].vLongDbl, "Lf");
01347 #endif
01348             }
01349             else
01350               OutputFloat (writer, currentFormat, 
01351               params[currentFormat.paramIdx].vDbl, "f");
01352           }
01353           break;
01354         case convFloatExp:
01355           {
01356             if (currentFormat.type == typeLongLong)
01357             {
01358 #ifdef CS_FORMATTER_NO_LONG_DOUBLE_FORMAT
01359               OutputFloat (writer, currentFormat, 
01360               (double)params[currentFormat.paramIdx].vLongDbl, 
01361               currentFormat.uppercase ? "E" : "e");
01362 #else
01363               OutputFloat (writer, currentFormat, 
01364               params[currentFormat.paramIdx].vLongDbl, 
01365               currentFormat.uppercase ? "LE" : "Le");
01366 #endif
01367             }
01368             else
01369               OutputFloat (writer, currentFormat, 
01370               params[currentFormat.paramIdx].vDbl, 
01371               currentFormat.uppercase ? "E" : "e");
01372           }
01373           break;
01374         case convFloatGeneral:
01375           {
01376             if (currentFormat.type == typeLongLong)
01377             {
01378 #ifdef CS_FORMATTER_NO_LONG_DOUBLE_FORMAT
01379               OutputFloat (writer, currentFormat, 
01380               (double)params[currentFormat.paramIdx].vLongDbl, 
01381               currentFormat.uppercase ? "G" : "g");
01382 #else
01383               OutputFloat (writer, currentFormat, 
01384               params[currentFormat.paramIdx].vLongDbl, 
01385               currentFormat.uppercase ? "LG" : "Lg");
01386 #endif
01387             }
01388             else
01389               OutputFloat (writer, currentFormat, 
01390               params[currentFormat.paramIdx].vDbl, 
01391               currentFormat.uppercase ? "G" : "g");
01392           }
01393           break;
01394         case convFloatHex:
01395           {
01396             if (currentFormat.type == typeLongLong)
01397               OutputFloatHex (writer, currentFormat, 
01398               params[currentFormat.paramIdx].vLongDbl, LDBL_MANT_DIG, 
01399               csLog2 (LDBL_MAX_EXP) + 1, -(LDBL_MAX_EXP - 1));
01400             else
01401               OutputFloatHex (writer, currentFormat, 
01402               params[currentFormat.paramIdx].vDbl, DBL_MANT_DIG, 
01403               csLog2 (DBL_MAX_EXP) + 1, -(DBL_MAX_EXP - 1));
01404           }
01405           break;
01406         default:
01407           break;
01408       }
01409 
01410       for (n = 0; n < currentFormat.fmtSkip; n++)
01411       {
01412         if (!reader.GetNext (ch)) break;
01413       }
01414       i++;
01415     }
01416     while (reader.GetNext (ch))
01417       writer.Put (ch);
01418     writer.Put (0);
01419   }
01420 };
01421 
01424 #endif // __CS_CSUTIL_FORMATTER_H__

Generated for Crystal Space by doxygen 1.4.7