# HG changeset patch # User Jordan Miner # Date 1358650241 21600 # Node ID 7678554e75def16d06331ff7af7e8ffb1055e4d5 # Parent b3091e460553727fa83652c21870f81a394a94f9 Add dynamin.core.format with parseFormatString(). diff -r b3091e460553 -r 7678554e75de dynamin/all_core.d --- a/dynamin/all_core.d Sat Jan 19 20:48:53 2013 -0600 +++ b/dynamin/all_core.d Sat Jan 19 20:50:41 2013 -0600 @@ -16,6 +16,7 @@ public import dynamin.core.environment; public import dynamin.core.event; public import dynamin.core.file; +public import dynamin.core.format; public import dynamin.core.global; public import dynamin.core.list; public import dynamin.core.math; diff -r b3091e460553 -r 7678554e75de dynamin/core/format.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dynamin/core/format.d Sat Jan 19 20:50:41 2013 -0600 @@ -0,0 +1,186 @@ + +/* + * Copyright Jordan Miner + * + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file BOOST_LICENSE.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + */ + +module dynamin.core.format; + +import dynamin.core.global; +import dynamin.core.string; +import dynamin.core.test; + +private: + +cstring parseFormatParam(cstring specifier, ref int paramIndex) { + // specifier is something like "1:x" or ":d" + int i; + for(i = 0; i < specifier.length && specifier[i] != ':'; ++i) { + } + if(i > 0) { + try { + paramIndex = to!int(specifier[0..i]); + } catch(Exception ex) { + throw new Exception("Invalid parameter index."); + } + } + return i >= specifier.length ? "" : specifier[i+1..$]; +} + +/** + * Parses `formatStr`, passing each part to `write` or `writeParam` as it is encountered. + * For example: + * + * parseFormatString("I am {0:x} years old!", writeParam, write); + * // will result in: + * write("I am "); + * writeParam(0, "x"); + * write(" years old!"); + * + */ +public void parseFormatString( cstring formatStr, + void delegate(int param, cstring format) writeParam, + void delegate(scope cstring str) write) { + + int paramIndex = 0; // param to write next, if not specified + cstring paramFormat; + + uword start = 0; + + for(int i = 0; i < formatStr.length; ++i) { + if(formatStr[i] == '{') { + write(formatStr[start..i]); + start = i + 1; + + if(i + 1 >= formatStr.length) + throw new Exception("Unterminated format specifier."); + ++i; + + if(formatStr[i] == '{') { // escaped open brace + // start already is the index of the second open brace + } else { // format specifier + while(formatStr[i] != '}') { + if(formatStr[i] == '{') + throw new Exception("Open brace in format specifier."); + if(++i >= formatStr.length) + throw new Exception("Unterminated format specifier."); + } + paramFormat = parseFormatParam(formatStr[start..i], paramIndex); + start = i + 1; + writeParam(paramIndex, paramFormat); + ++paramIndex; + } + } else if(formatStr[i] == '}') { + write(formatStr[start..i]); + start = i + 1; + + if(i + 1 >= formatStr.length || formatStr[i + 1] != '}') + throw new Exception("Unescaped close brace."); + ++i; // escaped close brace + } + } + if(start < formatStr.length) + write(formatStr[start..$]); +} + +unittest { + struct Call { + int index; + string str; + } + Call[] calls; + int callIndex; + void paramCallback(int param, cstring format) { + assertEqual(param, calls[callIndex].index); // crash if out of bounds + assertEqual(format, calls[callIndex].str); + ++callIndex; + }; + void strCallback(scope cstring str) { + assertEqual(str, calls[callIndex].str); // crash if out of bounds + ++callIndex; + }; + + calls = [ + Call(-1, "one"), + Call(0, ""), + Call(-1, "two"), + Call(1, ""), + Call(-1, "three") + ]; + callIndex = 0; + parseFormatString("one{0}two{1}three", ¶mCallback, &strCallback); + assertEqual(callIndex, calls.length); + + calls = [ + Call(-1, "one"), + Call(0, "abc12"), + Call(-1, "two"), + Call(1, "yyyy-mm-dd"), + Call(-1, "three") + ]; + callIndex = 0; + parseFormatString("one{0:abc12}two{1:yyyy-mm-dd}three", ¶mCallback, &strCallback); + assertEqual(callIndex, calls.length); + + calls = [ + Call(-1, "one"), + Call(0, "~!@#$%^&*()_+-=[]\\:\"<>?;',./"), + Call(-1, "two"), + ]; + callIndex = 0; + parseFormatString("one{0:~!@#$%^&*()_+-=[]\\:\"<>?;',./}two", ¶mCallback, &strCallback); + assertEqual(callIndex, calls.length); + + calls = [ + Call(-1, "one"), + Call(0, ""), + Call(-1, "two"), + Call(1, ""), + Call(-1, "three"), + Call(2, ""), + ]; + callIndex = 0; + parseFormatString("one{}two{}three{}", ¶mCallback, &strCallback); + assertEqual(callIndex, calls.length); + + calls = [ + Call(-1, "one"), + Call(4, ""), + Call(-1, "two"), + Call(5, ""), + Call(-1, "three"), + Call(2, ""), + Call(-1, "four"), + Call(3, "x"), + ]; + callIndex = 0; + parseFormatString("one{4}two{}three{2}four{:x}", ¶mCallback, &strCallback); + assertEqual(callIndex, calls.length); + + string testStr; + parseFormatString( "on{{e}}{2}}}two{{{}}}", + delegate (i, f) { testStr ~= "P"; }, + delegate (scope str) { testStr ~= str; }); + assertEqual(testStr, "on{e}P}two{P}"); + + assertThrows!Exception(parseFormatString( "test{", + delegate (i, f) { }, + delegate (scope str) { })); + assertThrows!Exception(parseFormatString( "test{0", + delegate (i, f) { }, + delegate (scope str) { })); + assertThrows!Exception(parseFormatString( "test{0{}", + delegate (i, f) { }, + delegate (scope str) { })); + assertThrows!Exception(parseFormatString( "test}ing", + delegate (i, f) { }, + delegate (scope str) { })); + assertThrows!Exception(parseFormatString( "test{0x12}ing", + delegate (i, f) { }, + delegate (scope str) { })); +} +