www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Improvement on format strings, take two.

reply renoX <renosky free.fr> writes:
Here's some code which allow to embed the variable name inside the format
string, the syntax is "text %{variable} text " and "text %08d{variable2} text"
(inspired from Ruby) IMHO this is better than the normal D format string as the
'embedding' of the variable improves readability: with this syntax one rarely
forget a space or a comma around the variable for example.

The code is below, it's main problem is that it doesn't handle correctly
non-const char[] variable (if you know how to detect const char[], please tell
me).
The code needs improvement in the format string detection to allow '% d{}' and
to allow to mix printf style format string and %{}: currently the mix provides
weird result: don't mix them.
renoX

import std.stdio;

template FindChar(char[] A, char B) {
    static if (A.length == 0) {
        const int FindChar = -1;
    } else static if (A[0] == B) {
        const int FindChar = 0;
    } else static if (-1 == FindChar!(A[1..$], B)) {
		const int FindChar = -1;
	} else {
		const int FindChar = 1 + FindChar!(A[1..$], B);
	}
}

// match the .*{ part in a %.*{, without any % or space in the .*
template FindRestFmt(char[] A) {
    static if (A.length == 0) {
        const int FindRestFmt = -1;
    } else static if (A[0] == '{') {
        const int FindRestFmt = 0;
    } else static if (A[0] == ' ') { // TODO in fact % {x} should be
authorised: equivalent to "% d",x
        const int FindRestFmt = -1;	
	} else static if (A[0] == '%') {
        const int FindRestFmt = -1;	
	} else static if (-1 == FindRestFmt!(A[1..$])) {
		const int FindRestFmt = -1;
	} else {
		const int FindRestFmt = 1 + FindRestFmt!(A[1..$]);
	}
}

// find a %.*{ without any % or space in the .*
template FindStartFmt(char[] A) {
    static if (A.length == 0) {
        const int FindStartFmt = -1;
    } else static if (A[0] != '%') {
		const int FindStartFmt = -1;
	} else static if (-1 == FindRestFmt!(A[1..$])) {
		const int FindStartFmt = -1;
	} else {
		const int FindStartFmt = 1 + FindRestFmt!(A[1..$]);
	}
}

template FmtString(char[] F, A...)
{
//	static pragma(msg, "FmtString in, F is '"~F~"'");
    static if (F.length == 0)
		const char[] FmtString = "\"," ~ Fmt!(A);
    else static if (F.length == 1)
		const char[] FmtString = F[0] ~ "\"," ~ Fmt!(A);
	// need to escape the %% otherwise doesn't work correctly.
	else static if (F[0..2] == "%%") //TODO must do full parsing of format strings
		const char[] FmtString = "%%" ~ FmtString!(F[2..$],A);
	else static if (FindStartFmt!(F) != -1)
			static if (FindChar!(F,'}') < FindStartFmt!(F))
				static assert(0, "format %[format]{<var>} incorrect in '" ~ F ~ "'");
			else static if (F[1] == '{')
				const char[] FmtString = "%s\"," ~ F[2..FindChar!(F,'}')] ~ ",\"" ~ 
					FmtString!(F[(1+FindChar!(F,'}'))..$],A);
			else
				const char[] FmtString = F[0..FindStartFmt!(F)] ~ "\"," ~
F[1+FindStartFmt!(F)..FindChar!(F,'}')] ~ ",\"" ~ 
					FmtString!(F[1+FindChar!(F,'}')..$],A);
    else
		const char[] FmtString = F[0] ~ FmtString!(F[1..$],A);
// 	static pragma(msg, "FmtString out is '"~FmtString~"'");
}

template Fmt(A...)
{
    static if (A.length == 0)
		const char[] Fmt = "";
    else static if (is(typeof(A[0]) : char[])) //TODO: how to parse static
string only??
			const char[] Fmt = "\"" ~ FmtString!(A[0], A[1..$]);
	else
		const char[] Fmt = A[0].stringof ~ "," ~ Fmt!(A[1..$]);	
// 	static pragma(msg, "Fmt out is '"~Fmt~"'");
}

template Putf(A...)
{
	// 0..$-1 to remove the last ','
	const char[] Putf = "writef(" ~ Fmt!(A)[0..$-1] ~ ");";
//	static pragma(msg, "Putf out is'"~Putf~"'");
}



int main()
{
	int x = 10;
	int y = 20;
	const char[] csimple = "const bonjour simple";
	char[] simple = "bonjour simple";
	// text containg %
	char[] text="percent d: %d percent{x}: %{x}";
	const char[] ctext="const percent d: %d percent{x}: %{x}";
	

	writef("BEFORE'");
	mixin(Putf!("coucou simple"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("ah que","coucou"));
	writef("'AFTER\n");
	
 	writef("BEFORE'");
	mixin(Putf!(x));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("foo\n",x,y));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!(x,"foo\n"));
	writef("'AFTER\n");
	
 	writef("BEFORE'");
	mixin(Putf!(csimple)); // works for const.
//	mixin(Putf!(simple));  Doesn't work for non-const char.
	writef("'AFTER\n");
	
 	writef("BEFORE'");
	mixin(Putf!("%{simple}"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("%s{simple}"));
	writef("'AFTER\n");

	writef("BEFORE'");
	mixin(Putf!("a is %x",10));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("test double percent escape: should seen one percent then {x}:
%%{x}"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("test avant %{x} apres"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("test format hexa:%08x{x}after"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("test text containing percent: '%{text}' apres",30));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!(ctext,30)); //TODO fix the erroneous inversion between the %d and
%{x} replacement. 
//	mixin(Putf!(text,30)); Doesn't work for non-const string.
	writef("'AFTER\n"); 
	
	writef("BEFORE'");
	mixin(Putf!("test avant '%{text}'"," apres %{x} encore apres ",x,y));
	writef("'AFTER\n"); 

	return 0;
}
Feb 22 2007
parent renoX <renosky free.fr> writes:
A new version, works now also with non const char[] parameter thanks to 
mario pernici who gave me the tip.

The syntax of the format string is slightly changed, now you need to 
write %s{} or %d{}, '%{' is just the escape sequence for '{'.

It needs further testing / polishing, still it's not too bad, the only 
annoying part is the syntax at the call site instead of writef(..), 
mixin(Puts!(..)) which is quite ugly..

Is-there a way to improve this by 'hiding' the mixin?
renoX



import std.stdio;


template Relay(int index, int incr = 1)
{
	static if (index == -1)
		const int Relay = -1;
	else
		const int Relay = incr + index;
}

template FindChar(char[] A, char B) {
	static if (A.length == 0)
		const int FindChar = -1;
	else static if (A[0] == B)
		const int FindChar = 0;
	else
		const int FindChar = Relay!(FindChar!(A[1..$], B));
}

/* Match format string:
  '%' Flags Width Precision FormatChar

Width: empty | Integer | '*'
Precision: empty | '.' | '.' Integer | '.*'

Integer: Digit | Digit Integer
Digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

FormatChar: 's' | 'b' | 'd' | 'o' | 'x' | 'X' | 'e' | 'E' | 'f' | 'F' | 
'g' | 'G' | 'a' | 'A'
*/
template MatchFmt(char[] A)
{
	static if (A.length == 0)
		const int MatchFmt = -1;
	else static if (A[0] == '%')
		const int MatchFmt = Relay!(MatchFmtFlags!(A[1..$]));
	else
		const int MatchFmt = -1;
}

Flags */
template MatchFmtFlags(char[] A)
{
	static if (A.length == 0)
		const int MatchFmtFlags = -1;

(A[0] == '0') || (A[0] == ' '))
		const int MatchFmtFlags = Relay!(MatchFmtFlags!(A[1..$]));
	else
		const int MatchFmtFlags = MatchFmtWidth!(A);
}
/* Width: empty | Integer | '*' */
template MatchFmtWidth(char[] A)
{
	static if (A.length == 0)
		const int MatchFmtWidth = -1;
	else static if (A[0] == '*')
		const int MatchFmtWidth = Relay!(MatchFmtPrecision!(A[1..$]));
	else static if (MatchUinteger!(A) > 0)
		const int MatchFmtWidth = 
Relay!(MatchFmtPrecision!(A[MatchUinteger!(A)..$]),MatchUinteger!(A));
	else
		const int MatchFmtWidth = MatchFmtPrecision!(A);
}
/* Precision: empty | '.' | '.' Integer | '.*' */
template MatchFmtPrecision(char[] A)
{
	static if (A.length == 0)
		const int MatchFmtPrecision = -1;
	else static if (A[0] == '.') {
		static if (MatchUinteger!(A[1..$]) > 0) {
			const int MatchFmtPrecision = 
Relay!(MatchFmtChar!(A[1+MatchUinteger!(A[1..$])..$]), 
1+MatchUinteger!(A[1..$]));
		} else {
			const int MatchFmtPrecision = Relay!(MatchFmtChar!(A[1..$]));
		}
	}
	else static if (MatchUinteger!(A) > 0)
		const int MatchFmtPrecision = 
Relay!(MatchFmtChar!(A[MatchUinteger!(A)..$]), MatchUinteger!(A));
	else
		const int MatchFmtPrecision = MatchFmtChar!(A);
}
/*
Integer: Digit | Digit Integer
Digit: '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
*/
template MatchUinteger(char[] s)
{
	static if (s.length == 0) {
		const int MatchUinteger = -1;
	} // allow 0 for first digit or not?
	else static if ((s[0] >= '0') && (s[0] <= '9')) {
		const int MatchUinteger = Relay!(MatchRestUinteger!(s[1..$]));
	} else {
		const int MatchUinteger = -1;
	}
}
template MatchRestUinteger(char[] s)
{
	static if (s.length == 0) {
		const int MatchRestUinteger = 0;
	}
	else static if ((s[0] >= '0') && (s[0] <= '9')) {
		const int MatchRestUinteger = Relay!(MatchRestUinteger!(s[1..$]));
	} else {
		const int MatchRestUinteger = 0;
	}
}
/* FormatChar: 's' | 'b' | 'd' | 'o' | 'x' | 'X' | 'e' | 'E' | 'f' | 'F' 
| 'g' | 'G' | 'a' | 'A' */
template MatchFmtChar(char[] A)
{
	static if (A.length == 0)
		const int MatchFmtChar = -1;
	else static if ((A[0] == 's') || (A[0] == 'b') || (A[0] == 'd') || 
(A[0] == 'o') || (A[0] == 'x') || (A[0] == 'X') || (A[0] == 'e') || 
(A[0] == 'E') || (A[0] == 'f') || (A[0] == 'F') || (A[0] == 'g') || 
(A[0] == 'G') || (A[0] == 'a') || (A[0] == 'A'))
		const int MatchFmtChar = 0;
	else
		const int MatchFmtChar = -1;
}

template FmtString(char[] F, A...)
{
//	static pragma(msg, "FmtString in, F is '"~F~"'");
	static if (F.length == 0)
		const char[] FmtString = "\"," ~ Fmt!(A);
	else static if (F.length == 1)
		const char[] FmtString = F[0] ~ "\"," ~ Fmt!(A);
	else static if (F[0] == '%') {
		// need to escape the %% otherwise doesn't work correctly.
		static if (F[1] == '%')
			const char[] FmtString = "%%" ~ FmtString!(F[2..$],A);
		// now { needs an escape sequence: \{ doesn't work in dmd so %{
		else static if (F[1] == '{')
			const char[] FmtString = "{" ~ FmtString!(F[2..$],A);
		// is-it a format string?
		else static if (MatchFmt!(F) != -1) {
			// new format string %[..]{<variable>}
			static if (FindChar!(F,'{') == 1+MatchFmt!(F)) {
				static if (FindChar!(F,'}') <= FindChar!(F,'{'))
					static assert(0, "bad format string %[format]{<var>} in '" ~ F ~ "'");
				const char[] FmtString = F[0..FindChar!(F,'{')] ~ "\"," ~ 
F[1+FindChar!(F,'{')..FindChar!(F,'}')] ~ ",\"" ~
					FmtString!(F[(1+FindChar!(F,'}'))..$],A);
			} else
				// normal format string: move things around so that old and new 
format cohabitate peacefully.
				const char[] FmtString = F[0..1+MatchFmt!(F)] ~ "\"," ~ 
A[0].stringof ~ ",\"" ~
					FmtString!(F[1+MatchFmt!(F)..$],A[1..$]);
		} else
			static assert(0, "bad format string in '" ~ F ~ "'");
	}
	else
		const char[] FmtString = F[0] ~ FmtString!(F[1..$],A);
//	 static pragma(msg, "FmtString out is '"~FmtString~"'");
}

template Fmt(A...)
{
	static if (A.length == 0)
		const char[] Fmt = "";
	// magic incantation to parse only const char[] format string
	else static if (is(typeof(A[0]) : char[]) && !is(typeof(&(A[0]))) && 
is(typeof(A[0])))
		const char[] Fmt = "\"" ~ FmtString!(A[0], A[1..$]);
	else
		const char[] Fmt = A[0].stringof ~ "," ~ Fmt!(A[1..$]);
	 static pragma(msg, "Fmt out is '"~Fmt~"'");
}

template Putf(A...)
{
	// 0..$-1 to remove the last ','
	const char[] Putf = "writef(" ~ Fmt!(A)[0..$-1] ~ ");";
//	static pragma(msg, "Putf out is'"~Putf~"'");
}



int main()
{
	int x = 10;
	int y = 20;
	const char[] csimple = "const bonjour simple";
	char[] simple = "bonjour simple";
	// text containing %
	char[] text="percentd: %d percentd{x}: %d{x}";
	const char[] ctext="const percent d: %d percentd{x}: %d{x}";

	writef("BEFORE'");
	assert(Putf!("simple") == "writef(\"simple\");");
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("ah que","coucou"));
	writef("'AFTER\n");
	
	 writef("BEFORE'");
	mixin(Putf!(x));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("foo\n",x,y));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("coucou %d simple",10));
	writef("'AFTER\n");
	
	
	writef("BEFORE'");
	mixin(Putf!(x,"foo\n"));
	writef("'AFTER\n");
	
	 writef("BEFORE'");
	mixin(Putf!(csimple)); // works for const.
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!(simple));  //now works also with non-const char: do not 
parse them
	writef("'AFTER\n");

	 writef("BEFORE'");
	mixin(Putf!("should be {simple}: %{simple}"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("%s{simple}"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("10 is %d{x}"));
	writef("'AFTER\n");

	writef("BEFORE'");
	mixin(Putf!("a is %x",10));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("test double percent escape: should seen one percent then 
{x}: %%{x}"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("test avant %{x} apres"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("test format hexa:%08x{x}after"));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("test text containing percent: '%{text}' apres",30));
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!(ctext,30)); //Done fix the erroneous invertion between the 
%d and %{x} replacement.
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!(text,30)); // Does now work for non-const string.
	writef("'AFTER\n");
	
	writef("BEFORE'");
	mixin(Putf!("test avant '%{text}'"," apres %{x} encore apres ",x,y));
	writef("'AFTER\n");

	return 0;
}
Feb 22 2007