Gettings!
I wanted to share with the community my sprintf replacement. It is not by all means a perfect one by it gets the job done at least for my needs. I wanted the usage to be exactly the same so I don't have to change the way I was doing things with sprintf. Here it goes:
[Code]
// Supports %s, %d, %c, %b and %f
u32 StringFormatVargs(string Buffer, string Input, va_list Args)
{
string Dst = Buffer;
u32 BytesWritten = 0;
u32 DefaultFractDigits = 3; // 3 digits after decimal point by default unless specified otherwise
u32 NextFractDigits = DefaultFractDigits;
while (*Input)
{
char c = *Input;
if (c == '%')
{
char Type = Input[1];
if (Type == 'c')
{
*Dst++ = va_arg(Args, char);
BytesWritten++;
}
else
{
if (IsNumeric(Type))
{
NextFractDigits = Type - 48;
Assert(Input[2] == 'f');
Type = 'f';
Input++;
}
string Replacement = 0;
string ToFree = 0;
switch (Type)
{
case 'b':
{
b32 Bool32 = va_arg(Args, b32);
Replacement = StringFromB32(Bool32);
} break;
case 's':
{
string String = va_arg(Args, string);
Replacement = String;
} break;
case 'd':
{
s32 Signed32 = va_arg(Args, s32);
Replacement = StringFromS32(Signed32);
ToFree = Replacement;
} break;
case 'f':
{
r32 Real32 = (r32)va_arg(Args, r64); // NOTE: float is promoted to double!
Replacement = StringFromR32(Real32, NextFractDigits);
NextFractDigits = DefaultFractDigits;
ToFree = Replacement;
} break;
default: InvalidSwitchCase;
}
while (*Replacement)
{
*Dst++ = *Replacement++;
BytesWritten++;
}
if (ToFree)
Free(ToFree);
}
Input++;
}
else
{
*Dst++ = c;
BytesWritten++;
}
Input++;
}
*Dst = 0;
return (BytesWritten);
}
u32 StringFormat(string Buffer, string Input, ...)
{
u32 BytesWritten;
va_list Args;
va_start(Args, Input);
BytesWritten = StringFormatVargs(Buffer, Input, Args);
va_end(Args);
return (BytesWritten);
}
[/Code]
Here are the auxiliary functions, I pulled them from some old code I had where I used to use 'int' instead of s32 etc.
[Code]
inline u32 GetDigitCount(s32 Value)
{
u32 Result;
for (Result = 1; (Value /= 10) > 0; Result++);
return Result;
}
void IntCpy(char *Dst, s32 Value, s32 Start, u32 Count)
{
s32 End = Start + Count;
for (s32 i = End - 1; i >= Start; i--, Value /= 10)
*(Dst + i) = (char)(Value % 10 + 48);
Dst[End] = 0;
}
string StringFromS32(s32 Value)
{
b32 Negative = Value < 0;
Value = Negative ? -Value : Value;
u32 NumDigits = GetDigitCount(Value);
string Result;
if (Negative)
{
Result = Salloc(NumDigits + 1);
char *Ptr = Result;
{
*Ptr = '-';
IntCpy(Ptr, Value, 1, NumDigits);
}
}
else
{
Result = Salloc(NumDigits);
char *ptr = Result;
IntCpy(ptr, Value, 0, NumDigits);
}
return Result;
}
string StringFromR32(r32 Value, u32 DecimalAccuracy)
{
// e.g. 3.148
b32 Negative = Value < 0;
if (Negative) Value = -Value;
int Mul = 1;
ForCount(DecimalAccuracy) Mul *= 10;
int Number = (int)(Value * Mul); // gets the number as a whole, e.g. 3148
int LeftNum = Number / Mul; // left part of the decimal point, e.g. 3
int RightNum = Number % Mul; // right part of the decimal pnt, e.g. 148
int LeftDigitCount = GetDigitCount(LeftNum); // e.g. 1
int RightDigitCount = GetDigitCount(RightNum); // e.g. 3
int Total = LeftDigitCount + RightDigitCount + 1; // +1 for '.'
string Result;
if (Negative)
{
Result = Salloc(Total + 1); // +1 for '-'
char *Ptr = Result;
{
*Ptr = '-';
IntCpy(Ptr, LeftNum, 1, LeftDigitCount);
*(Ptr + LeftDigitCount + 1) = '.';
IntCpy(Ptr, RightNum, LeftDigitCount + 2, RightDigitCount);
}
}
else
{
Result = Salloc(Total);
char *Ptr = Result;
{
IntCpy(Ptr, LeftNum, 0, LeftDigitCount);
*(Ptr + LeftDigitCount) = '.';
IntCpy(Ptr, RightNum, LeftDigitCount + 1, RightDigitCount);
}
}
return Result;
}
inline string StringFromB32(b32 Input)
{
return Input ? "true" : "false";
}
inline bool IsNumeric(char Input)
{
return Input >= '0' && Input <= '9';
}
[/Code]
Some typedefs and defines I use
[Code]
typedef char* string;
typedef signed int b32;
typedef signed int s32;
typedef unsigned int u32;
typedef float r32;
typedef double r64;
#define Salloc(N) (string)calloc(N + 1, sizeof(char))
#define ForCount(N) for(u32 i = 0; i < (N); i++)
[/Code]
The StringFormat function is using variable arguments defined in cstdargs but you could write your own va_list, va_etc equivalents
quite easily.
I'm allocating memory for strings because I usually use that stuff for debugging code or metaprograms where I don't care much about it as long as it's doing what it's supposed to (so I don't care about security etc). I don't use that code in games. In my game I have the memory allocation stuff (Salloc etc) wrapped in macros to point to whatever memory allocation schemes I use (memory arena, heap alloc, etc) so I can easily change where these allocations get their memory from.
Usage:
[Code]
char Buffer[256];
StringFormat(Buffer, "Hello %s 1+1=%d, 2.10+2.75=%2f, (char)65=%c, it's %b", "World", 2, 4.85f, 'A', true);
[/Code]
Hope somebody finds this useful. Comments/suggestions/improvements/critique are all welcome!
Thanks!
elxenoanizd/vexe
EDIT: [Correction] Salloc should really allocate N + 1 instead of N to include the null terminator. And it's sizeof(char) not sizeof(string)