printf() metaprogramming challenge

Walter Bright newshound2 at digitalmars.com
Thu May 23 19:33:15 UTC 2019


While up at night with jetlag at DConf, I started toying about solving a small 
problem. In order to use printf(), the format specifiers in the printf format 
string have to match the types of the rest of the parameters. This is well known 
to be brittle and error-prone, especially when refactoring the types of the 
arguments.

(Of course, this is not a problem with writefln() and friends, but that isn't 
available in the dmd front end, nor when using betterC. Making printf better 
would mesh nicely with betterC. Note that many C compilers have extensions to 
tell you if there's a mismatch, but they won't fix it for you.)

I thought why not use D's metaprogramming to fix it. Some ground rules:

1. No extra overhead
2. Completely self-contained
3. Only %s specifiers are rewritten
4. %% is handled
5. diagnose mismatch between number of specifiers and number of arguments

Here's my solution:

     int i;
     dprintf!"hello %s %s %s %s betty\n"(3, 4.0, &i, "abc".ptr);

gets rewritten to:

     printf("hello %d %g %p %s betty\n", 3, 4.0, &i, "abc".ptr);

The code at the end accomplishes this. Yay!

But what I'd like it to do is to extend it to convert a `string s` argument into 
`cast(int)s.length, s.ptr` tuple and use the "%.*s" specifier for it.

I completely failed at that. I suspect the language has a deficiency in 
manipulating expression tuples.

Does anyone see a way to make this work?

Note: In order to minimize template bloat, I refactored most of the work into a 
regular function, minimizing the size of the template expansions.

------ Das Code ------------
import core.stdc.stdio : printf;

template Seq(A ...) { alias Seq = A; }

int dprintf(string f, A ...)(A args)
{
     enum Fmts = Formats!(A);
     enum string s = formatString(f, Fmts);
     __gshared const(char)* s2 = s.ptr;
     return printf(Seq!(s2, args[0..2], args[2..4]));
}

template Formats(T ...)
{
     static if (T.length == 0)
	enum Formats = [ ];
     else static if (T.length == 1)
	enum Formats = [Spec!(T[0])];
     else
	enum Formats = [Spec!(T[0])] ~ Formats!(T[1 .. T.length]);
}

template Spec(T : byte)    { enum Spec = "%d"; }
template Spec(T : short)   { enum Spec = "%d"; }
template Spec(T : int)     { enum Spec = "%d"; }
template Spec(T : long)    { enum Spec = "%lld"; }

template Spec(T : ubyte)   { enum Spec = "%u"; }
template Spec(T : ushort)  { enum Spec = "%u"; }
template Spec(T : uint)    { enum Spec = "%u"; }
template Spec(T : ulong)   { enum Spec = "%llu"; }

template Spec(T : float)   { enum Spec = "%g"; }
template Spec(T : double)  { enum Spec = "%g"; }
template Spec(T : real)    { enum Spec = "%Lg"; }

template Spec(T : char)    { enum Spec = "%c"; }
template Spec(T : wchar)   { enum Spec = "%c"; }
template Spec(T : dchar)   { enum Spec = "%c"; }

template Spec(T : immutable(char)*)   { enum Spec = "%s"; }
template Spec(T : const(char)*)       { enum Spec = "%s"; }
template Spec(T : T*)                 { enum Spec = "%p"; }

/******************************************
  * Replace %s format specifiers in f with corresponding specifiers in A[].
  * Other format specifiers are left as is.
  * Number of format specifiers must match A.length.
  * Params:
  *	f = printf format string
  *	A = replacement format specifiers
  * Returns:
  *	replacement printf format string
  */
string formatString(string f, string[] A ...)
{
     string r;
     size_t i;
     size_t ai;
     while (i < f.length)
     {
	if (f[i] != '%' || i + 1 == f.length)
	{
	    r ~= f[i];
	    ++i;
	    continue;
	}
	char c = f[i + 1];
	if (c == '%')
	{
	    r ~= "%%";
	    i += 2;
	    continue;
	}
	assert(ai < A.length, "not enough arguments");
	string fmt = A[ai];
	++ai;
	if (c == 's')
	{
	    r ~= fmt;
	    i += 2;
	    continue;
	}
	r ~= '%';
	++i;
	continue;
     }
     assert(ai == A.length, "not enough formats");
     return r;
}
----- End Of Das Code ----------


More information about the Digitalmars-d mailing list