printf in C#

I’ve been wondering about making DANSE open-source for a while now (after all if anyone else wants to start using it seriously they will need the source code), but up until recently (well, yesterday) I’ve had a problem: DANSE uses a version of printf written in C# by Richard Prinz which is only available under the Code Project Open License, and this does not allow me to release the code under any other license. I can’t use the CPOL since there is other code also in DANSE released under the GPL.

I can’t use the built-in printf since I want to target Silverlight as well, and I can’t just use the C# print routines since the students taking the module where this software is used haven’t learned C#, all they know is C.

So I figured… how hard can it be to write my own version of printf? Perhaps not implementing all the features, but at least being good enough for my purposes here?

Turns out it’s not that hard, only took a couple of hours. Not very well tested yet, but if anyone else would like to give the following a spin and let me know how they get on, I’d be very interested.


internal enum eTStype { _int, _float, _exponential, _string, _char, _pointer, _hex, _octal, _gtype, _error }
internal class TS
{
internal int start; internal int end; internal int width; internal int precision; internal eTStype type;
internal TS(int A, int B, int C, int D, eTStype what)
{ start = A; end = B; width = C; precision = D; type = what; }
}

internal static string sprintf(string format, params Object[] parameters)
{
// Starts from a copy of the string format, then replaces the type specifiers.
string result = String.Copy(format);

// Keep a list of the type-specifiers as I go through...
List myTS = new List();

// The first type specifier will start with a % which is not preceeded by
// an escape character '\', include an option precision field (".X")
// and then a length and specifier, both of which are ignored to be
// friendly. The specifier is assumed to end at the first occurrance
// of any one of the following specifiers: i, d, u, o, x, X, f, F, e, E,
// g, G, c, s, or p. (C# knows what these variables are.)

// Go through the format string, counting how many type specifiers there
// are, and making a note of what they are:
for (int loop = 0; loop < format.Length; loop++) { if (format[loop] == '%' && (loop == 0 || format[loop - 1] != '\\')) { // Found the start of a format string. int startTS = loop; int precision = 0; int width = 0; Boolean precisionValid = false; eTStype what = eTStype._error; // Now I need to find the end of it and work out what it is: while (loop < format.Length && what == eTStype._error) { char c = format[loop]; if (c == '.') precisionValid = true; if (c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9') { string TryThis = format[loop].ToString(); int whatIsThis = 0; int.TryParse(TryThis, out whatIsThis); if (precisionValid == true) precision = precision * 10 + whatIsThis; else width = width * 10 + whatIsThis; } if (c == 'i' || c == 'd' || c == 'u') what = eTStype._int; else if (c == 'o') what = eTStype._octal; else if (c == 'x' || c == 'X') what = eTStype._hex; else if (c == 'f' || c == 'F') what = eTStype._float; else if (c == 'e' || c == 'E') what = eTStype._exponential; else if (c == 'g' || c == 'G') what = eTStype._gtype; else if (c == 'c') what = eTStype._char; else if (c == 's') what = eTStype._string; else if (c == 'p') what = eTStype._pointer; loop++; } if (what != eTStype._error) { // Must have found the end of a format string, so add this to the list: if (precisionValid == false) precision = -1; TS ts = new TS(startTS, loop, width, precision, what); myTS.Add(ts); } } } // Right - now just go through and make a new string from the // non-type specifier parts of the format string, and the // replacements for the type-specifiers from the parameters list: StringBuilder sb = new StringBuilder(format.Length + 20 * myTS.Count); int lastFinishes = 0; for (int loop = 0; loop < myTS.Count; loop++) { int includeUpToHere = myTS[loop].start; sb.Append(format.Substring(lastFinishes, includeUpToHere - lastFinishes)); if (parameters.Length > loop) sb.Append(makeString(myTS[loop], parameters[loop]));
else sb.Append(" ERROR - Insufficient Parameters ");
lastFinishes = myTS[loop].end;
}
// Then add on the bit after all type specifiers:
sb.Append(format.Substring(lastFinishes, format.Length - lastFinishes));

return sb.ToString();
}
static private string makeString(TS ts, Object parameter)
{
if (ts.type == eTStype._error) return "*Error: Unrecognised type specifier*";

// For pointers, characters and strings all that's required is:
string q = parameter.ToString();

// Then if this is an integer, there are some special cases to include the
// precision, and deal with hex and octal numbers:
if (ts.type == eTStype._int)
{
long d;
if (long.TryParse(q, out d))
{
int prec = ts.precision;
if (ts.type == eTStype._hex)
{
q = d.ToString("X" + ((prec < 0) ? "" : ts.precision.ToString())); } else if (ts.type == eTStype._octal) { // Precision not implemented for octal yet. Does anyone care? string qq = Convert.ToString(d, 8); } else { q = d.ToString("D" + ((prec < 0) ? "" : ts.precision.ToString())); } } else { q = "*Error: Unrecognised type specifier*"; } } if (ts.type == eTStype._exponential || ts.type == eTStype._float || ts.type == eTStype._gtype) { char s = ts.type == eTStype._exponential ? 'e' : ts.type == eTStype._float ? 'f' : 'g'; double d; int prec = ts.precision; if (double.TryParse(q, out d)) q = d.ToString(s + ((prec < 0) ? "" : ts.precision.ToString())); else q = "*Error: Unrecognised type specifier*"; } // Then pad (or truncate) to fit the width required: if (ts.width > 0)
{
while (q.Length < ts.width) q = " " + q; if (q.Length > ts.width) return q.Substring(0, ts.width);
return q;
}
return q;
}
}

This entry was posted in Uncategorized. Bookmark the permalink.