import std.stdio; //import std.file; //import std.string; /+ The idea for putf (for put format string) is to extend the printf/writef normal format string with embedded expression. In Ruby the syntax for this is #{...}, but I choose to reuse % as the escape string: %s{} %d{} %08X{} etc (all the normal format string works). Those new format strings are only parsed in constant string. '%{' is the escape string for '{'. The problem with the normal format string are: - in a classical printf, the variable are put in a separated location from the format string which reduce readability and increase the likelyhood of mistakes. - with writef, you can write: writef("... %d",var,"...") but the interruption of the string increase the risk to forget a ',' or ' ' for separation A limitation with putf/sputf is that they can't take expression outside of a embedded expression as they are templates. Usage: mixin(sputf!("Here is a string with an embedded expression %08d{vx+vy*5} and a 'old' format string %d\n",100)); +/ private int relay(int index, int incr = 1) { if (index == -1) return -1; else return incr + index; } private int find_char(char[] s, char c) { for(int i = 0; i < s.length; i++) { if (s[i] == c) return i; } return -1; } /* 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' */ private 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 */ private 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 | '*' */ private 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 | '.*' */ private 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' */ private 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; } } private 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' */ private 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; } private template FmtString(char[] F, A...) { // static pragma(msg, "FmtString in, F is '"~F~"'"); static if (F.length == 0) const char[] FmtString = "\"," ~ LFmt!(A); else static if (F.length == 1) const char[] FmtString = F[0] ~ "\"," ~ LFmt!(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 %[..]{} static if (find_char(F,'{') == 1+MatchFmt!(F)) { static if (find_char(F,'}') <= find_char(F,'{')) static assert(0, "bad format string %[format]{} in '" ~ F ~ "'"); const char[] FmtString = F[0..find_char(F,'{')] ~ "\"," ~ F[1+find_char(F,'{')..find_char(F,'}')] ~ ",\"" ~ FmtString!(F[(1+find_char(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~"'"); } private template LFmt(A...) { static if (A.length == 0) const char[] LFmt = ""; // parse only const char[] format string (use the property that const have no address) else static if (is(typeof(A[0]) : char[]) && !is(typeof(&(A[0])))) const char[] LFmt = "\"" ~ FmtString!(A[0], A[1..$]); else const char[] LFmt = A[0].stringof ~ "," ~ LFmt!(A[1..$]); // static pragma(msg, "LFmt out is '"~LFmt~"'"); } template Fmt(A...) { static if (A.length == 0) const char[] Fmt = ""; else // 0..$-1 to remove the last ',' const char[] Fmt = LFmt!(A)[0..$-1]; // static pragma(msg, "Fmt out is '"~Fmt~"'"); } template putf(A...) { const char[] putf = "std.stdio.writef(" ~ Fmt!(A) ~ ");"; // static pragma(msg, "putf out is'"~putf~"'"); } template sputf(A...) { const char[] sputf = "std.string.format(" ~ Fmt!(A) ~ ")"; // static pragma(msg, "sputf out is'"~sputf~"'"); } private void Tputf(int line, char[] res_sputf, char[] expected_res) { static int i = 1; if (res_sputf != expected_res) writef("Error: at line %d",line," sputf is <'%s'>",res_sputf," expected <'%s'>\n",expected_res); else writef("Test %2d ok\n",i); i++; } int main() { int vx = 10; const int cx = 11; int vy = 20; const char[] csimple = "text in const simple"; char[] vsimple = "text in vsimple"; // text containing % char[] vtext="percentd: %d"; char[] vtext2="percentd: %d{vx}"; const char[] ctext="const percent d: %d percentd{vx}: %d{vx}"; mixin(putf!("Tests of [s]putf functions\n")); Tputf(__LINE__, mixin(sputf!("simple")), "simple"); Tputf(__LINE__, mixin(sputf!("ah que ", "coucou")), "ah que coucou"); Tputf(__LINE__, mixin(sputf!("coucou %d simple", 10)), "coucou 10 simple"); Tputf(__LINE__, mixin(sputf!(vx)), "10"); Tputf(__LINE__, mixin(sputf!(vx, "foo\n")), "10foo\n"); Tputf(__LINE__, mixin(sputf!("foo\n", vx, vy)), "foo\n1020"); Tputf(__LINE__, mixin(sputf!(csimple)),"text in const simple"); Tputf(__LINE__, mixin(sputf!(vsimple)),"text in vsimple"); Tputf(__LINE__, mixin(sputf!("%s{vsimple}")),"text in vsimple"); Tputf(__LINE__, mixin(sputf!("%d{vx}")),"10"); Tputf(__LINE__, mixin(sputf!("%X{vx}")), "A"); Tputf(__LINE__, mixin(sputf!("%08x{vx}")),"0000000a"); Tputf(__LINE__, mixin(sputf!("%%{vx}")),"%{vx}"); Tputf(__LINE__, mixin(sputf!("%{vsimple}")),"{vsimple}"); Tputf(__LINE__, mixin(sputf!("%{vx}")),"{vx}"); Tputf(__LINE__, mixin(sputf!("%d{1+3}")), "4"); Tputf(__LINE__, mixin(sputf!("%d{vx+vy+1}")),"31"); Tputf(__LINE__, mixin(sputf!("%s{vtext}",30)),"percentd: %d30"); Tputf(__LINE__, mixin(sputf!(ctext, 30)),"const percent d: 30 percentd{vx}: 10"); Tputf(__LINE__, mixin(sputf!(vtext, 30)),"percentd: 30"); Tputf(__LINE__, mixin(sputf!(vtext2, 30)),"percentd: 30{vx}"); return 0; }