Improvement on format strings, take two.
renoX
renosky at free.fr
Thu Feb 22 14:05:27 PST 2007
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
Flags: empty | '-' Flags | '+' Flags | '#' Flags | '0' Flags | ' ' Flags
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: empty | '-' Flags | '+' Flags | '#' Flags | '0' Flags | ' '
Flags */
template MatchFmtFlags(char[] A)
{
static if (A.length == 0)
const int MatchFmtFlags = -1;
else static if ((A[0] == '-') || (A[0] == '+') || (A[0] == '#') ||
(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;
}
More information about the Digitalmars-d
mailing list