#define _CRT_SECURE_NO_WARNINGS #define _CRT_NONSTDC_NO_DEPRECATE #include "apf.h" #include #include #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN #include // for OutputDebugString #endif #include #include #include #include using namespace std; #include #include namespace apf { /** When there's an error the run-time message looks like this : safeprintf type error; wanted (charptr), found (float) safeprintf err in fmt "%s/%d = %s" safeprintf err at : " = %s" ---- ARG : one really annoying things is that enums count as "classes" so you have to cast them to int to make them printf normally :( **/ //START_CB const char * c_safeprintftypenames[] = { "none", "unknown", "charptr", "wcharptr", "float", "ptrint", "ptrvoid", "int32", "__int64", "uint32", "u__int64" }; ESafePrintfType safeprintf_fmttype(const char fmt, bool wide) { /** c int or wint_t When used with printf functions, specifies a single-byte character; when used with wprintf functions, specifies a wide character. C int or wint_t When used with printf functions, specifies a wide character; when used with wprintf functions, specifies a single-byte character. d int Signed decimal integer. i int Signed decimal integer. o int Unsigned octal integer. u int Unsigned decimal integer. x int Unsigned hexadecimal integer, using "abcdef." X int Unsigned hexadecimal integer, using "ABCDEF." e double Signed value having the form [ – ]d.dddd e [sign]ddd where d is a single decimal digit, dddd is one or more decimal digits, ddd is exactly three decimal digits, and sign is + or –. E double Identical to the e format except that E rather than e introduces the exponent. f double Signed value having the form [ – ]dddd.dddd, where dddd is one or more decimal digits. The number of digits before the decimal point depends on the magnitude of the number, and the number of digits after the decimal point depends on the requested precision. g double Signed value printed in f or e format, whichever is more compact for the given value and precision. The e format is used only when the exponent of the value is less than –4 or greater than or equal to the precision argument. Trailing zeros are truncated, and the decimal point appears only if one or more digits follow it. G double Identical to the g format, except that E, rather than e, introduces the exponent (where appropriate). n Pointer to integer Number of characters successfully written so far to the stream or buffer; this value is stored in the integer whose address is given as the argument. p Pointer to void Prints the address of the argument in hexadecimal digits. s String When used with printf functions, specifies a single-byte–character string; when used with wprintf functions, specifies a wide-character string. Characters are printed up to the first null character or until the precision value is reached. S */ switch( tolower(fmt) ) { case 'c': case 'd': case 'i': case 'o': if ( wide ) return safeprintf___int64; else return safeprintf_int32; case 'u': case 'x': if ( wide ) return safeprintf_u__int64; else return safeprintf_uint32; case 'z': if ( sizeof(size_t) == sizeof(__int64) ) return safeprintf_u__int64; else return safeprintf_uint32; case 'e': case 'f': case 'g': return safeprintf_float; case 'n': return safeprintf_ptrint; case 'p': return safeprintf_ptrvoid; case 's': if ( fmt == 'S' ) return safeprintf_wcharptr; else return safeprintf_charptr; default: return safeprintf_unknown; } } const char * safeprintf_fmtskipwidth(const char * ptr, bool * pWide) { *pWide = false; // h | l | I | I32 | I64 if ( *ptr == 'h' || *ptr == 'l' || *ptr == 'w' ) { // char / string modifiers return ptr+1; } else if ( *ptr == 'I' ) { if ( ptr[1] == '3' && ptr[2] == '2' ) { return ptr+3; } else if ( ptr[1] == '6' && ptr[2] == '4' ) { *pWide = true; return ptr+3; } else { // (size_t) size if ( sizeof(size_t) == sizeof(__int64) ) *pWide = true; return ptr+1; } } else { return ptr; } } ESafePrintfType safeprintf_findfmtandadvance(const char ** pptr) { const char * ptr = *pptr; const char * startPtr = ptr; REFERENCE_TO_VARIABLE(startPtr); // find the next % arg : for(;;) { ptr = strchr(ptr,'%'); if ( ! ptr ) { *pptr = NULL; return safeprintf_none; } ASSERT( ptr[0] == '%' ); if ( ptr[1] == '%' ) { ptr += 2; continue; } else { break; } } ASSERT( ptr[0] == '%' ); ptr++; while( ! isalpha(*ptr) ) { if ( ! *ptr ) { *pptr = NULL; return safeprintf_unknown; } ptr++; } ASSERT( isalpha(*ptr) ); bool wide = false; ptr = safeprintf_fmtskipwidth(ptr,&wide); ESafePrintfType fmttype = safeprintf_fmttype(*ptr,wide); *pptr = ptr+1; return fmttype; } //----------------------------------------------------------------------------- // config : bool safeprintf_checkintunsigned = false; bool safeprintf_checkintasfloat = false; bool safeprintf_checkintsize = true; bool safeprintf_noisy = true; void safeprintf_setoptions(bool noisy,bool checkintsize,bool checkintasfloat,bool checkintunsigned) { safeprintf_checkintunsigned = checkintunsigned; safeprintf_checkintsize = checkintsize; safeprintf_checkintasfloat = checkintasfloat; safeprintf_noisy = noisy; } void safeprintf_throwsyntaxerror(const char *fmt_base,const char *fmt) { if ( safeprintf_noisy ) { char buffer[4096]; buffer[0] = 0; sprintf(buffer+strlen(buffer),"safeprintf syntax err in fmt \"%s\"\n",fmt_base); sprintf(buffer+strlen(buffer),"safeprintf err at : \"%s\"\n",fmt); // can't call Log cuz it might use me fputs(buffer,stderr); DEBUG_LOG(buffer); ASSERT_BREAK(); } THROW; } static inline bool isIntFmt(ESafePrintfType fmt) { return fmt >= safeprintf_int32; } static inline int getIntFmtWidth(ESafePrintfType fmt) { if ( fmt == safeprintf_int32 || fmt == safeprintf_uint32 ) return 0; else return 1; } static inline int getIntFmtSign(ESafePrintfType fmt) { if ( fmt == safeprintf_int32 || fmt == safeprintf___int64 ) return 0; else return 1; } void safeprintf_throwerror(const char *fmt_base,const char *fmt,ESafePrintfType fmttype,ESafePrintfType argtype) { ASSERT( fmttype != argtype ); if ( isIntFmt(fmttype) && isIntFmt(argtype) ) { // %d vs %u / etc was passed an int ; can be benign ? bool sizeOk = ( ! safeprintf_checkintsize || getIntFmtWidth(fmttype) == getIntFmtWidth(argtype) ); bool signOk = ( ! safeprintf_checkintunsigned || getIntFmtSign(fmttype) == getIntFmtSign(argtype) ); if ( sizeOk && signOk ) return; // no error } if( fmttype == safeprintf_float && (argtype == safeprintf_int32 || argtype == safeprintf___int64 || argtype == safeprintf_uint32 || argtype == safeprintf_u__int64) ) { // %f was passed an int ; can be benign ? if ( ! safeprintf_checkintasfloat ) return; } if ( safeprintf_noisy ) { char buffer[4096]; buffer[0] = 0; sprintf(buffer+strlen(buffer),"safeprintf type error; wanted (%s), found (%s)\n",c_safeprintftypenames[fmttype],c_safeprintftypenames[argtype]); sprintf(buffer+strlen(buffer),"safeprintf err in fmt \"%s\"\n",fmt_base); sprintf(buffer+strlen(buffer),"safeprintf err at : \"%s\"\n",fmt); // can't call Log cuz it might use me fputs(buffer,stderr); DEBUG_LOG(buffer); ASSERT_BREAK(); } THROW; } /* void test_safeprintf() { safeprintf("hello %d\n",1); safeprintf("hello %03d\n",1); safeprintf("hello % 3d\n",1,2); safeprintf("hello %+3d\n",1); safeprintf("hello %d,%f\n",1); safeprintf("hello %03d,%f\n",1); safeprintf("hello % 3d,%f\n",1); safeprintf("hello %+3d,%f\n",1); safeprintf("hello %d,%f\n",1,2); safeprintf("hello %d %d %s\n",1,2,"test"); safeprintf("hello %d %S %s\n",1,2,"test"); } */ //END_CB void strlcpy(char * to, const char * fm, int maxLen) { int len = (int)strlen(fm) + 1; len = min(len,maxLen); memcpy(to,fm,len); to[maxLen-1] = 0; } #define strlen32 (int)strlen #define size32() size() #define ptr_diff_32(p) (int)(p) #define ASSERT_RELEASE ASSERT #define data() begin() template class VectorHacker { public : T * operator << ( typename std::vector::iterator vt ) { return &(*vt); } }; #define VDATA VectorHacker() << #pragma warning(disable : 4018) // signed/unsigned mismatch #pragma warning(disable : 4127) // conditional is constant; but it isn't ! //START_CB //! Printf strings are not meant to exceed this value //const int c_bigAssSize = 2048; //}{= StringData ====================================================================================== /*! StringData manages the COW for String (COW = Copy On Write) it's a refcounted vector you should use Readable() and Writeable() to get at m_vec in fact, only the constructors of String and those two functions should ever touch m_vec The biggest advantage of COW is that it makes vector< String > much efficient, due to the many uses of operator= (COW is a huge optimization for operator=) To hold a unique/writable String requires TWO allocations : one for StringData and one in the vector<> to point at the char data. Any COW string needs two allocations, unless you pack the ref count into the char data, which actually isn't a terrible idea... */ #pragma pack(push) #pragma pack(4) struct StringData { public: // constructed with one ref StringData() : m_refCount(1) { // when you just construct vector<> it does no allocations } enum ECOW { eCOW }; // constructed with one ref // COW : break link to old data; copy it and de-ref it StringData( ECOW e, StringData * pOldData ) : m_refCount(1), m_vec(pOldData->m_vec) { pOldData->FreeRef(); } enum EEmpty { eEmpty }; // just for GetStaticEmptyStringData : explicit StringData( EEmpty e ) : m_refCount(1) { m_vec.resize(1); m_vec[0] = 0; } ~StringData() { ASSERT( m_refCount == 0 ); } void TakeRef() { m_refCount++; } void FreeRef() { m_refCount--; if ( m_refCount == 0 ) delete this; } int GetRefCount() const { ASSERT( m_refCount > 0 ); return m_refCount; } vector m_vec; private: int m_refCount; }; //* // safe version does an alloc : static StringData * GetStaticEmptyStringData() { static StringData* s_pData = NULL; if (s_pData == NULL) { s_pData = new StringData(StringData::eEmpty); s_pData->TakeRef(); } return s_pData; } /*/ // unsafe version : // make the const empty string data just point at a chunk of memory // this is really just to avoid a tiny alloc that leaks so that my leak check is clean struct StringDataHammer { char * begin; size_t capacity; size_t size; int refCount; }; #pragma pack(pop) //COMPILER_ASSERT( sizeof(cb::vector) == sizeof(char *) + sizeof(vecsize_t) + sizeof(vecsize_t) ); COMPILER_ASSERT( sizeof(StringData) == sizeof(StringDataHammer) ); static StringData * GetStaticEmptyStringData() { //static StringData s_data; // can't do that cuz I never want to destruct static StringData* s_pData = NULL; if (s_pData == NULL) { static StringDataHammer s_hammer = { 0 }; s_pData = (StringData *) &s_hammer; s_hammer.begin = ""; s_hammer.size = 1; s_hammer.capacity = 8; s_hammer.refCount = 2; ASSERT_RELEASE( s_pData->m_vec.size() == 1 ); ASSERT_RELEASE( s_pData->m_vec[0] == 0 ); } return s_pData; } /**/ //}{= constructors ====================================================================================== // Default ctor. Make an empty string. String::String() : m_pData(GetEmpty().m_pData) { ASSERT(m_pData != NULL); m_pData->TakeRef(); //ASSERT(IsValid()); } String::String( const String &str ) : m_pData(str.m_pData) { ASSERT( m_pData != NULL ); m_pData->TakeRef(); //ASSERT( IsValid() ); } String::String( const char * const pStr ) : m_pData(new StringData) { ASSERT( pStr != NULL ); // can't call Set cuz we're not yet Valid int size = strlen32(pStr) + 1; m_pData->m_vec.resize( size ); strcpy( &m_pData->m_vec[0], pStr ); //ASSERT( IsValid() ); } /* String::String( const char c ) : m_pData(new StringData) { // can't call Set cuz we're not yet Valid m_pData->m_vec.resize( 2 ); m_pData->m_vec[0] = c; m_pData->m_vec[1] = 0; ASSERT( IsValid() ); } */ // do NOT use s_empty here // make sure I'm safe to use in other static initializers String::String( const EEmpty e ) : m_pData( GetStaticEmptyStringData() ) { ASSERT( m_pData != NULL ); m_pData->TakeRef(); ASSERT( Readable()[0] == 0 ); //ASSERT( IsValid() ); } String::String( const EReserve e, const int amount ) : m_pData(new StringData) { m_pData->m_vec.reserve( amount ); m_pData->m_vec.push_back( 0 ); //ASSERT( IsValid() ); } String::String( const EReserve e, const char * const pStr, const int amount) : m_pData(new StringData) { ASSERT( pStr != NULL ); m_pData->m_vec.reserve( amount ); m_pData->m_vec.push_back( 0 ); Set(pStr); //ASSERT( IsValid() ); } String::String( const ESubString e, const char * const pStr, const int len) : m_pData(new StringData) { ASSERT( pStr != NULL ); m_pData->m_vec.assign( pStr, pStr+len ); ASSERT( m_pData->m_vec.size32() == len ); m_pData->m_vec.push_back( 0 ); //ASSERT( IsValid() ); } String::String( const EConcat e, const char * const pStr1, const char * const pStr2) : m_pData(new StringData) { ASSERT( pStr1 != NULL ); ASSERT( pStr2 != NULL ); int l1 = strlen32(pStr1); int l2 = strlen32(pStr2); m_pData->m_vec.resize(l1+l2+1); memcpy(&m_pData->m_vec[0],pStr1,l1); memcpy(&m_pData->m_vec[0]+l1,pStr2,l2); m_pData->m_vec.back() = 0; //ASSERT( IsValid() ); } String::~String( void ) { //ASSERT( IsValid() ); m_pData->FreeRef(); m_pData = NULL; } /** return a reference to an empty String. */ /*static*/ const String& String::GetEmpty() { static String s_empty( String::eEmpty ); // DO use eEmpty here //ASSERT(s_empty.IsValid()); return s_empty; } //======================================================================================= // all utility code should use Readable() or Writeable() to get to m_vec const vector & String::Readable() const { ASSERT( m_pData ); return m_pData->m_vec; } // Writeable is somewhat inefficient if I'm immediately throwing away the // string data, as in Clear(), etc. // MemGuard() and Writeable should also appear together vector & String::Writeable() { ASSERT( m_pData ); // if ref count is 1, I'm the only owner if ( m_pData->GetRefCount() != 1 ) { // shared string ; break it StringData * pOldData = m_pData; m_pData = new StringData( StringData::eCOW, pOldData ); } return m_pData->m_vec; } // WriteableEmpty may not actually give you an empty vec; // it gives a vec whose contents are totally undetermined vector & String::WriteableEmpty(const int reserve /* = 16 */) { ASSERT( m_pData ); // if ref count is 1, I'm the only owner if ( m_pData->GetRefCount() != 1 ) { // shared string ; break it m_pData->FreeRef(); m_pData = new StringData; // and do some silly crap to make sure IsValid() will pass m_pData->m_vec.reserve(reserve); m_pData->m_vec.resize(1); m_pData->m_vec[0] = 0; } return m_pData->m_vec; } //======================================================================================= #define m_vec _dont_touch_me_below /* bool String::IO( const StreamPtr &inout ) const { if ( inout->GetStreamDirection() == Stream::eDirRead ) return const_cast(this)->Read(inout); else return Write(inout); } bool String::Read( const StreamPtr &in ) { //! The length does not have the terminating 0 int len = 0; VERIFY( in->Read( &len ) ); // avoid doing two allocs here : vector & vec = WriteableEmpty(len+1); //! Since we store the length, I do not store the 0, thus len+1 vec.resize(len + 1); VERIFY( in->Read( vec.begin(), len, NULL ) ); vec[len] = 0; ASSERT( IsValid() ); return true; } bool String::Write( const StreamPtr &out ) const { ASSERT( IsValid() ); const int len = Length(); //! Does not include terminating 0 if( !out->Write( len ) ) return false; if( !out->Write( Readable().begin(), len ) ) return false; return true; } */ // fast swap : //void String::Swap(String * pOther) /* void String::Swap(String & rhs) { // just swap impl pointers : cb::Swap( m_pData, rhs.m_pData ); } */ void String::operator=(const String& str) { ASSERT( str.IsValid() && IsValid() ); // this works even for assigning to self; don't special case : // take ref on the new one first str.m_pData->TakeRef(); // free ref to old data m_pData->FreeRef(); // set pointer : m_pData = str.m_pData; } bool String::operator ==( const String &str ) const { // COW allows some quick equality checks; but is this common enough to be worth while ? if ( m_pData == str.m_pData ) return true; return strcmp( CStr(), str.CStr() ) == 0; } bool String::operator !=( const String &str ) const { // COW allows some quick equality checks; but is this common enough to be worth while ? if ( m_pData == str.m_pData ) return false; return strcmp( CStr(), str.CStr() ) != 0; } bool String::operator < ( const String &str ) const { return strcmp( CStr(), str.CStr() ) < 0; } bool String::operator ==( const char *const pStr ) const { ASSERT( pStr && pStr[0] == pStr[0] ); return strcmp( CStr(), pStr ) == 0; } bool String::operator !=( const char *const pStr ) const { ASSERT( pStr && pStr[0] == pStr[0] ); return strcmp( CStr(), pStr ) != 0; } bool String::operator < ( const char *const pStr ) const { ASSERT( pStr && pStr[0] == pStr[0] ); return strcmp( CStr(), pStr ) < 0; } bool operator ==( const char * const pStr, const String &str ) { ASSERT( pStr && pStr[0] == pStr[0] ); return str == pStr; } bool operator !=( const char * const pStr, const String &str ) { ASSERT( pStr && pStr[0] == pStr[0] ); return str != pStr; } bool operator < ( const char * const pStr, const String &str ) { ASSERT( pStr && pStr[0] == pStr[0] ); return strcmp( pStr, str.CStr() ) < 0; } void String::Set( const char *const pStr ) { if( pStr != NULL ) { const int len = strlen32(pStr); vector & vec = WriteableEmpty(len+1); vec.clear(); vec.resize( len + 1 ); strcpy( &vec[0], pStr ); } else { /* vector & vec = WriteableEmpty(); vec.resize(1); vec[0] = 0; */ *this = GetEmpty(); } } void String::Append( const char * const pStr ) { vector & vec = Writeable(); int old_len = Length(); int add_len = strlen32(pStr); vec.resize( old_len + add_len + 1 ); strcpy( &vec[0] + old_len, pStr ); } void String::Append( const char c ) { vector & vec = Writeable(); vec.back() = c; vec.push_back(0); } int String::Index(const char * ptr) const { int index = ptr_diff_32( ptr - CStr() ); ASSERT_RELEASE( index >= 0 && index <= Length() ); return index; } void String::Insert(const int at, const char * const pStr ) { int old_len = Length(); ASSERT( at >= 0 && at <= old_len ); vector & vec = Writeable(); int add_len = strlen32(pStr); vec.resize( old_len + add_len + 1 ); char * buf = VDATA vec.data(); memmove(buf+at+add_len,buf+at,old_len-at); strcpy( buf+at + old_len, pStr ); } void String::Insert(const int at, const char c ) { int old_len = Length(); ASSERT( at >= 0 && at <= old_len ); vector & vec = Writeable(); vec.push_back(0); char * buf = VDATA vec.data(); memmove(buf+at+1,buf+at,old_len-at); buf[at] = c; } /* String::String(const EPrintf e, const char *pFormat, ... ) : m_pData(new StringData) { char bigAssBuffer[ c_bigAssSize ]; va_list argPtr; va_start( argPtr, pFormat ); const int length = _vsnprintf( bigAssBuffer, c_bigAssSize, pFormat, argPtr); va_end( argPtr ); ASSERT( length != -1 ); m_pData->m_vec.resize( length+1 ); memcpy(&m_pData->m_vec[0],bigAssBuffer,length); m_pData->m_vec.back() = 0; } /**/ static inline String rawStringPrintfSub(const char *pFormat, va_list & varargs) { // try to print to a moderate size stack array first : char stackBuf[1024]; int wroteLen = vsnprintf(stackBuf,sizeof(stackBuf)-1,pFormat,varargs); if ( wroteLen >= 0 ) { // tack it on : //Set(stackBuf); return String(stackBuf); } else { // very big string, make a dynamic alloc buffer : //String ret; //vector & vBuf = ret.Writeable(); vector vBuf; vBuf.resize(4096); wroteLen = vsnprintf(VDATA vBuf.data(),vBuf.size()-1,pFormat,varargs); while ( wroteLen < 0 ) { vBuf.resize( vBuf.size()*2 ); wroteLen = vsnprintf(VDATA vBuf.data(),vBuf.size()-1,pFormat,varargs); } //vBuf.resize( wroteLen + 1 ); //return ret; // this is an unnecessary strcpy , but also does a tighten for us : return String(VDATA vBuf.data()); } } String StringPrintf(const char *pFormat, ...) { va_list argPtr; va_start( argPtr, pFormat ); String s = rawStringPrintfSub(pFormat,argPtr); va_end( argPtr ); return s; } void String::rawPrintf( const char *pFormat, ... ) { va_list argPtr; va_start( argPtr, pFormat ); String s = rawStringPrintfSub(pFormat,argPtr); va_end( argPtr ); *this = s; } void String::rawCatPrintf( const char *pFormat, ... ) { va_list argPtr; va_start( argPtr, pFormat ); String s = rawStringPrintfSub(pFormat,argPtr); va_end( argPtr ); *this += s; } char String::GetChar( const int index ) const { ASSERT( IsValid() ); ASSERT( index >= 0 ); ASSERT( index < Length() ); return Readable()[ index ]; } void String::SetChar( const int index, const char c ) { ASSERT( IsValid() ); ASSERT( index >= 0 ); ASSERT( index < Length() ); if( c == 0 ) { Truncate(index); } else { Writeable()[ index ] = c; } ASSERT( IsValid() ); } // Split is like SetChar(index,0) - returns the tail that's cut off String String::Split( const int index ) { ASSERT( IsValid() ); ASSERT( index >= 0 ); ASSERT( index <= Length() ); if ( index == Length() ) { return String::GetEmpty(); } // index == Length()-1 also returns empty, but cuts off one char String ret( CStr() + index + 1 ); Truncate(index); return ret; } char String::PopBack() { ASSERT( IsValid() ); ASSERT( Length() > 0 ); if ( Length() == 0 ) { return 0; } vector & vec = Writeable(); ASSERT( vec.back() == 0 ); char back = vec[vec.size()-2];; vec.pop_back(); vec.back() = 0; return back; } //! Useful? //! char &operator []( const int index ); void String::Reserve( const int amount ) { ASSERT( IsValid() ); // cheat; reserve doesn't actually change data, so don't COW because of it : //vector & vec = const_cast &>( Readable() ); // actually, typical usage will be a Reserve() followed by writes ; if // we don't break sharing at the time of the Reserve, then the old // data will have unneeded extra size vector & vec = Writeable(); vec.reserve( amount ); } int String::Capacity( void ) const { ASSERT( IsValid() ); return (int)( Readable().capacity() ); } const char *String::CStr( void ) const { //ASSERT( IsValid() ); return &Readable()[0]; } // WriteableCStr : do NOT set nulls ! char * String::WriteableCStr( int size ) { vector & vec = Writeable(); //vec.reserve( size ); if ( size > vec.size() ) vec.resize( size ); return VDATA vec.data(); } void String::Truncate( const int newLength ) //!< equivalent to what you'd imagine SetChar(index,0); would do { //ASSERT( IsValid() ); // Truncate is intentionally allowed to work on invalid strings // it's my hack for jamming illegally on a string vector & vec = Writeable(); ASSERT( newLength >= 0 ); ASSERT( newLength < vec.capacity() ); vec.resize( newLength + 1 ); vec[ newLength ] = 0; ASSERT( IsValid() ); } void String::FixLength() { //ASSERT( IsValid() ); // FixLength is intentionally allowed to work on invalid strings // it's my hack for jamming illegally on a string vector & vec = Writeable(); int newLength = strlen32(VDATA vec.data()); Truncate(newLength); } int String::Length( void ) const { ASSERT( IsValid() ); return (int) Readable().size32() - 1; } bool String::IsValid() const { ASSERT( m_pData != NULL ); ASSERT( m_pData->GetRefCount() >= 1 ); ASSERT( Readable().size() > 0 ); ASSERT( strlen( &Readable()[0] ) == (size_t)(Readable().size() - 1) ); ASSERT( Readable()[ Readable().size() - 1 ] == 0 ); return true; } void String::Clear() { ASSERT( IsValid() ); vector & vec = WriteableEmpty(); vec.clear(); vec.push_back(0); ASSERT( IsValid() ); } void String::Release() { *this = GetEmpty(); } /* void String::WriteBinary(FILE * fp) const { int len = Length(); myfwrite(&len,sizeof(len),fp); myfwrite(CStr(),len,fp); } void String::ReadBinary(FILE * fp) { ASSERT( IsValid() ); vector & vec = WriteableEmpty(); vec.clear(); int len; myfread(&len,sizeof(len),fp); vec.resize(len+1); myfread(&vec[0],len,fp); vec[len] = 0; ASSERT( IsValid() ); } void String::WriteText(FILE * fp) const { int len = Length(); myfwrite( CStr(), len, fp ); } */ /* void String::ReadText(FILE * fp) { } */ //======================================================================================= //END_CB //START_CB #define APF_MAX_ARGS (20) // "%n" is not an output, it stores the # of chars written so far // I don't support that right yet //======================================================================= // configure how wchars are treated : static bool s_wcharAnsi = false; // output to Console CP is default void autoPrintfSetWCharAnsi(bool ansi) // el { s_wcharAnsi = ansi; } #ifdef _MSC_VER String UnicodeToAnsi(const wchar_t * from) { int fromLen = (int)wcslen(from); String ret; int toLen = fromLen*2+256; char * to = ret.WriteableCStr(toLen); WideCharToMultiByte(CP_ACP,0,from,-1,to,toLen,NULL,NULL); ret.FixLength(); return ret; } String UnicodeToConsole(const wchar_t * from) { int fromLen = (int)wcslen(from); String ret; int toLen = fromLen*2+256; char * to = ret.WriteableCStr(toLen); WideCharToMultiByte(GetConsoleCP(),0,from,-1,to,toLen,NULL,NULL); ret.FixLength(); return ret; } String autoPrintfWChar(const wchar_t * ws) { if ( s_wcharAnsi) return UnicodeToAnsi(ws); else return UnicodeToConsole(ws); } #endif //======================================================================= // implementation : static inline const char * safeprintf_fmtForType(ESafePrintfType fmtType) { switch(fmtType) { case safeprintf_int32 : return "d"; case safeprintf___int64 : return FMT_I64 "d"; case safeprintf_uint32 : return "u"; case safeprintf_u__int64 : return FMT_I64 "u"; case safeprintf_float : return "f"; //case safeprintf_ptrint : return "n"; // do NOT return here - intentional failure case safeprintf_ptrvoid : return "p"; case safeprintf_wcharptr : return "S"; case safeprintf_charptr : return "s"; default : safeprintf_throwerror("","",fmtType,fmtType); return ""; } } static inline String DoAutoType(const char *fmt_base,int & numArgs, const ESafePrintfType * pArgTypes ) { String ret(fmt_base); // @@ could have a fast variant if there are no %a's, I can return fmt_base unchanged numArgs = 0; int retLen = ret.Length(); char * ptrbase = ret.WriteableCStr(); char * ptr = ptrbase; while(ptr) { const char * p = ptr; ESafePrintfType fmttype = safeprintf_findfmtandadvance(&p); ptr = const_cast(p); // it's perfectly valid to just not have %'s for your args if ( fmttype == safeprintf_none ) { return ret; } else if ( ptr == NULL ) { safeprintf_throwerror(fmt_base,ptr,fmttype,safeprintf_none); return ret; } ESafePrintfType argtype = pArgTypes[ numArgs ]; numArgs += 1; if ( fmttype == safeprintf_unknown ) { if ( tolower(ptr[-1]) == 'a' ) { // autotype : if ( argtype <= safeprintf_unknown ) { safeprintf_throwerror(fmt_base,ptr,fmttype,argtype); } const char * insFmt = safeprintf_fmtForType(argtype); // change 'a' to insFmt : int insLen = (int)strlen(insFmt); if ( insLen == 1 ) { ptr[-1] = insFmt[0]; } else { // realloc to make sure we have room : ptrdiff_t iptr = ptr - ptrbase; retLen += 8; ptrbase = ret.WriteableCStr( retLen ); ptr = ptrbase + iptr; char * insAt = ptr-1; memmove(insAt+insLen,ptr,strlen(ptr)+1); memcpy(insAt,insFmt,insLen); ptr = insAt + insLen; } } else { // syntax error failure (failed to parse a type from % in fmt) safeprintf_throwsyntaxerror(fmt_base,ptr); } } else { // check type : if ( fmttype != argtype ) { safeprintf_throwerror(fmt_base,ptr,fmttype,argtype); } } } return ret; } //=============================================================== static inline void SkipVAArg(ESafePrintfType argtype, va_list & vl) { switch(argtype) { case safeprintf_charptr: { char * arg = va_arg(vl,char *); REFERENCE_TO_VARIABLE(arg); return; } case safeprintf_wcharptr: { wchar_t * arg = va_arg(vl,wchar_t *); REFERENCE_TO_VARIABLE(arg); return; } case safeprintf_int32: { int arg = va_arg(vl,int); REFERENCE_TO_VARIABLE(arg); return; } case safeprintf___int64: { __int64 arg = va_arg(vl,__int64); REFERENCE_TO_VARIABLE(arg); return; } case safeprintf_uint32: { unsigned int arg = va_arg(vl,unsigned int); REFERENCE_TO_VARIABLE(arg); return; } case safeprintf_u__int64: { unsigned __int64 arg = va_arg(vl,unsigned __int64); REFERENCE_TO_VARIABLE(arg); return; } case safeprintf_float: { double arg = va_arg(vl,double); REFERENCE_TO_VARIABLE(arg); return; } case safeprintf_ptrint: { int * arg = va_arg(vl,int*); REFERENCE_TO_VARIABLE(arg); return; } case safeprintf_ptrvoid: { void * arg = va_arg(vl,void*); REFERENCE_TO_VARIABLE(arg); return; } default: // BAD safeprintf_throwsyntaxerror("SkipVAArg","unknown arg type"); return; } } static inline String GetVAArgToString(ESafePrintfType argtype, va_list & vl) { switch(argtype) { case safeprintf_charptr: { char * arg = va_arg(vl,char *); return ToString(arg); } case safeprintf_wcharptr: { wchar_t * arg = va_arg(vl,wchar_t *); return ToString(arg); } case safeprintf_int32: { int arg = va_arg(vl,int); return ToString(arg); } case safeprintf___int64: { __int64 arg = va_arg(vl,__int64); return ToString(arg); } case safeprintf_uint32: { unsigned int arg = va_arg(vl,unsigned int); return ToString(arg); } case safeprintf_u__int64: { unsigned __int64 arg = va_arg(vl,unsigned __int64); return ToString(arg); } case safeprintf_float: { double arg = va_arg(vl,double); return ToString(arg); } //case safeprintf_ptrint: { int * arg = va_arg(vl,int*); return ToString(arg); } case safeprintf_ptrint: { int * arg = va_arg(vl,int*); REFERENCE_TO_VARIABLE(arg); /* *arg = 0; */ return String(); } // no output case safeprintf_ptrvoid: { void * arg = va_arg(vl,void*); return ToString(arg); } default: // BAD safeprintf_throwsyntaxerror("GetVAArgToString","unknown arg type"); return String::GetEmpty(); } } // autoToStringFunc is the main function : // String autoToStringFunc(int nArgs, ... ) { //PROFILE(autoToStringFunc); TRY { ESafePrintfType argtypes[APF_MAX_ARGS] = { safeprintf_none }; //lprintf("autoToStringFunc:\n"); // first pop all the argtypes off the varargs : va_list vl; va_start(vl,nArgs); for (int i=0;i= nArgs ) break; // whatever arg we get now will be treated as a string : String str = GetVAArgToString(argtypes[n],vl); n++; const char * oldFmt = str.CStr(); // find number of args, check arg types, and do autotyping : int numPercents = 0; String useFmt = DoAutoType(oldFmt,numPercents,argtypes+n); const char * newFmt = useFmt.CStr(); // check argtypes[n] vs format string fmt //lprintf ("\t oldFmt : \"%s\" \n",oldFmt); //lprintf ("\t newFmt : \"%s\" \n",newFmt); // check that we have enough args left in varargs : if ( n+numPercents > nArgs ) { safeprintf_throwsyntaxerror(oldFmt,"too many precents for args"); } // now do regular old vsnprintf using the varargs : char dstBuf[1024]; int wroteLen = vsnprintf(dstBuf,sizeof(dstBuf)-1,newFmt,vl); //lprintf ("\t %d,%d : dstBuf : \"%s\" \n",n,numPercents,dstBuf); if ( wroteLen >= 0 ) { // tack it on : ret += dstBuf; } else { std::vector vBuf; vBuf.resize( 4096 ); wroteLen = vsnprintf(&vBuf[0],vBuf.size()-1,newFmt,vl); while ( wroteLen < 0 ) { vBuf.resize( vBuf.size()*2 ); wroteLen = vsnprintf(&vBuf[0],vBuf.size()-1,newFmt,vl); } ret += &vBuf[0]; } // step past the args we think we used in the varargs list : for(int i=0;i inline String autoToStringSub( T1 arg1, T2 arg2, T3 arg3, T4 arg4 ) { return autoToStringFunc( 4, safeprintf_type(arg1), safeprintf_type(arg2), safeprintf_type(arg3), safeprintf_type(arg4), arg1, arg2, arg3, arg4 , 0 ); } */ for(int nArgs=1;nArgs inline String autoToString( T1 arg1, T2 arg2, T3 arg3 ) { return autoToStringSub( autoprintf_StringToChar( autoArgConvert(arg1) ), autoprintf_StringToChar( autoArgConvert(arg2) ), autoprintf_StringToChar( autoArgConvert(arg3) ) ); } **/ for(int nArgs=1;nArgs