printf() metaprogramming challenge
Jonathan Marler
johnnymarler at gmail.com
Thu May 23 22:48:33 UTC 2019
On Thursday, 23 May 2019 at 19:33:15 UTC, Walter Bright wrote:
> 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 ----------
It uses mixin, so not pretty, but it works...
void main()
{
int i = 0;
dprintf!"hello %s %s %s %s betty\n"(3, 4.0, &i, "abc".ptr);
const msg = "AAA!";
dprintf!"A dstring '%s'\n"(msg[0 .. 3]);
}
template Seq(A ...) { alias Seq = A; }
int dprintf(string f, A ...)(A args)
{
import core.stdc.stdio : printf;
enum Fmts = Formats!(A);
enum string s = formatString(f, Fmts);
__gshared const(char)* s2 = s.ptr;
enum call = function() {
import std.conv : to;
string printfCall = "printf(s2";
foreach(i, T; A)
{
static if (is(T : string))
{
printfCall ~= ", cast(size_t)args[" ~ i.to!string
~ "].length, args["
~ i.to!string ~ "].ptr";
}
else
{
printfCall ~= ", args[" ~ i.to!string ~ "]";
}
}
return printfCall ~ ")";
}();
//pragma(msg, call); // uncomment to see the final call
return mixin(call);
}
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 : string) { enum Spec = "%.*s"; }
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;
}
More information about the Digitalmars-d
mailing list