1 module more.format; 2 3 /** 4 Used for selecting either lower or upper case for certain kinds of formatting, such as hex. 5 */ 6 enum Case 7 { 8 lower, upper 9 } 10 11 /** 12 An alias to the common sink delegate used for string formatting. 13 */ 14 alias StringSink = void delegate(const(char)[]); 15 16 /** 17 A delegate formatter allows a delegate to behave as format function. 18 A common use case for this would be to have multiple ways to format a class. 19 20 Example: 21 --- 22 class Foo 23 { 24 DelegateFormatter formatPretty() { return DelegateFormatter(&prettyFormatter); } 25 private void prettyFormatter(StringSink sink) 26 { 27 sink("the pretty format"); 28 } 29 30 DelegateFormatter formatUgly() { return DelegateFormatter(&uglyFormatter); } 31 private void uglyFormatter(StringSink sink) 32 { 33 sink("the ugly format"); 34 } 35 } 36 Foo foo; 37 writefln("foo pretty = %s", foo.formatPretty()); 38 writefln("foo ugly = %s", foo.formatUgly()); 39 --- 40 41 */ 42 struct DelegateFormatter 43 { 44 void delegate(StringSink sink) formatter; 45 void toString(StringSink sink) const 46 { 47 formatter(sink); 48 } 49 } 50 51 /** 52 Append a formatted string into a character OutputRange 53 */ 54 void putf(R, U...)(auto ref R outputRange, string fmt, U args) 55 { 56 import std.format : formattedWrite; 57 formattedWrite(&outputRange.put!(const(char)[]), fmt, args); 58 } 59 60 /** 61 Converts a 4-bit nibble to the corresponding hex character (0-9 or A-F). 62 */ 63 char toHex(Case case_ = Case.lower)(ubyte b) in { assert(b <= 0x0F); } body 64 { 65 /* 66 NOTE: another implementation could be to use a hex table such as: 67 return "0123456789ABCDEF"[value]; 68 HoweverThe table lookup might be slightly worse since it would require 69 the string table to be loaded into the processor cache, whereas the current 70 implementation may be more instructions but all the code will 71 be in the same place which helps cache locality. 72 73 On processors without cache (such as the 6502), the table lookup approach 74 would likely be faster. 75 */ 76 static if(case_ == Case.lower) 77 { 78 return cast(char)(b + ((b <= 9) ? '0' : ('a'-10))); 79 } 80 else 81 { 82 return cast(char)(b + ((b <= 9) ? '0' : ('A'-10))); 83 } 84 } 85 unittest 86 { 87 assert('0' == toHex(0x0)); 88 assert('9' == toHex(0x9)); 89 assert('a' == toHex(0xA)); 90 assert('f' == toHex(0xF)); 91 assert('A' == toHex!(Case.upper)(0xA)); 92 assert('F' == toHex!(Case.upper)(0xF)); 93 } 94 alias toHexLower = toHex!(Case.lower); 95 alias toHexUpper = toHex!(Case.upper); 96 97 bool asciiIsUnreadable(char c) pure nothrow @nogc @safe 98 { 99 return c < ' ' || (c > '~' && c < 256); 100 } 101 void asciiWriteUnreadable(scope void delegate(const(char)[]) sink, char c) 102 in { assert(asciiIsUnreadable(c)); } body 103 { 104 if(c == '\r') sink("\\r"); 105 else if(c == '\t') sink("\\t"); 106 else if(c == '\n') sink("\\n"); 107 else if(c == '\0') sink("\\0"); 108 else { 109 char[4] buffer; 110 buffer[0] = '\\'; 111 buffer[1] = 'x'; 112 buffer[2] = toHexUpper((cast(char)c)>>4); 113 buffer[3] = toHexUpper((cast(char)c)&0xF); 114 sink(buffer); 115 } 116 } 117 void asciiWriteEscaped(scope void delegate(const(char)[]) sink, const(char)* ptr, const char* limit) 118 { 119 auto flushPtr = ptr; 120 121 void flush() 122 { 123 if(ptr > flushPtr) 124 { 125 sink(flushPtr[0..ptr-flushPtr]); 126 flushPtr = ptr; 127 } 128 } 129 130 for(; ptr < limit; ptr++) 131 { 132 auto c = *ptr; 133 if(asciiIsUnreadable(c)) 134 { 135 flush(); 136 sink.asciiWriteUnreadable(c); 137 flushPtr++; 138 } 139 } 140 flush(); 141 } 142 auto asciiFormatEscaped(const(char)[] str) 143 { 144 static struct Formatter 145 { 146 const(char)* str; 147 const(char)* limit; 148 void toString(scope void delegate(const(char)[]) sink) const 149 { 150 sink.asciiWriteEscaped(str, limit); 151 } 152 } 153 return Formatter(str.ptr, str.ptr + str.length); 154 } 155 156 bool utf8IsUnreadable(dchar c) pure nothrow @nogc @safe 157 { 158 if(c < ' ') return true; // unreadable 159 if(c < 0x7F) return false; // readable 160 assert(0, "utf8IsUnreadable not fully implemented"); 161 } 162 void utf8WriteUnreadable(scope void delegate(const(char)[]) sink, dchar c) 163 in { assert(utf8IsUnreadable(c)); } body 164 { 165 if(c == '\r') sink("\\r"); 166 else if(c == '\t') sink("\\t"); 167 else if(c == '\n') sink("\\n"); 168 else if(c == '\0') sink("\\0"); 169 else { 170 if(c >= 0xFF) 171 { 172 assert(0, "not implemented"); 173 } 174 char[4] buffer; 175 buffer[0] = '\\'; 176 buffer[1] = 'x'; 177 buffer[2] = toHexUpper((cast(char)c)>>4); 178 buffer[3] = toHexUpper((cast(char)c)&0xF); 179 sink(buffer); 180 } 181 } 182 void utf8WriteEscaped(scope void delegate(const(char)[]) sink, const(char)* ptr, const char* limit) 183 { 184 import more.utf8 : decodeUtf8; 185 186 auto flushPtr = ptr; 187 188 void flush() 189 { 190 if(ptr > flushPtr) 191 { 192 sink(flushPtr[0..ptr-flushPtr]); 193 flushPtr = ptr; 194 } 195 } 196 197 for(; ptr < limit;) 198 { 199 const(char)* nextPtr = ptr; 200 auto c = decodeUtf8(&nextPtr); 201 if(utf8IsUnreadable(c)) 202 { 203 flush(); 204 sink.utf8WriteUnreadable(c); 205 } 206 ptr = nextPtr; 207 } 208 flush(); 209 } 210 auto utf8FormatEscaped(const(char)[] str) 211 { 212 static struct Formatter 213 { 214 const(char)* str; 215 const(char)* limit; 216 void toString(scope void delegate(const(char)[]) sink) const 217 { 218 sink.utf8WriteEscaped(str, limit); 219 } 220 } 221 return Formatter(str.ptr, str.ptr + str.length); 222 } 223 auto utf8FormatEscaped(dchar c) 224 { 225 static struct Formatter 226 { 227 char[4] buffer; 228 ubyte size; 229 this(dchar c) 230 { 231 import more.utf8 : encodeUtf8; 232 size = encodeUtf8(buffer.ptr, c); 233 } 234 void toString(scope void delegate(const(char)[]) sink) const 235 { 236 sink.utf8WriteEscaped(buffer.ptr, buffer.ptr + size); 237 } 238 } 239 return Formatter(c); 240 } 241 242 auto formatHex(Case case_ = Case.lower, T)(const(T)[] array) if(T.sizeof == 1) 243 { 244 struct Formatter 245 { 246 const(T)[] array; 247 void toString(scope void delegate(const(char)[]) sink) const 248 { 249 char[2] chars; 250 foreach(value; array) 251 { 252 chars[0] = toHex!case_((cast(char)value)>>4); 253 chars[1] = toHex!case_((cast(char)value)&0xF); 254 sink(chars); 255 } 256 } 257 } 258 return Formatter(array); 259 } 260 261 // Policy-based formatEscape function 262 auto formatEscapeByPolicy(Hooks)(const(char)[] str) 263 { 264 struct Formatter 265 { 266 const(char)[] str; 267 void toString(scope void delegate(const(char)[]) sink) const 268 { 269 auto from = 0; 270 auto to = 0; 271 char[Hooks.escapeBufferLength] buffer; 272 Hooks.initEscapeBuffer(buffer.ptr); 273 274 for(; to < str.length; to++) 275 { 276 auto escapeLength = Hooks.escapeCheck(buffer.ptr, str[to]); 277 if(escapeLength > 0) 278 { 279 if(to > from) 280 { 281 sink(str[from..to]); 282 } 283 sink(buffer[0..escapeLength]); 284 from = to + 1; 285 } 286 } 287 if(to > from) 288 { 289 sink(str[from..to]); 290 } 291 } 292 } 293 return Formatter(str); 294 } 295 auto formatEscapeSet(string escapePrefix, string escapeSet)(const(char)[] str) 296 { 297 static struct Hooks 298 { 299 enum escapeBufferLength = escapePrefix.length + 1; 300 static void initEscapeBuffer(char* escapeBuffer) pure 301 { 302 escapeBuffer[0..escapePrefix.length] = escapePrefix[]; 303 } 304 static auto escapeCheck(char* escapeBuffer, char charToCheck) pure 305 { 306 foreach(escapeChar; escapeSet) 307 { 308 if(charToCheck == escapeChar) 309 { 310 escapeBuffer[escapePrefix.length] = charToCheck; 311 return escapePrefix.length + 1; 312 } 313 } 314 return 0; // char should not be escaped 315 } 316 } 317 return formatEscapeByPolicy!Hooks(str); 318 } 319 unittest 320 { 321 import more.test; 322 mixin(scopedTest!"format"); 323 324 import std.format : format; 325 assert(`` == format("%s", formatEscapeSet!(`\`, `\'`)(``))); 326 assert(`a` == format("%s", formatEscapeSet!(`\`, `\'`)(`a`))); 327 assert(`abcd` == format("%s", formatEscapeSet!(`\`, `\'`)(`abcd`))); 328 329 assert(`\'` == format("%s", formatEscapeSet!(`\`, `\'`)(`'`))); 330 assert(`\\` == format("%s", formatEscapeSet!(`\`, `\'`)(`\`))); 331 assert(`\'\\` == format("%s", formatEscapeSet!(`\`, `\'`)(`'\`))); 332 assert(`a\'\\` == format("%s", formatEscapeSet!(`\`, `\'`)(`a'\`))); 333 assert(`\'a\\` == format("%s", formatEscapeSet!(`\`, `\'`)(`'a\`))); 334 assert(`\'\\a` == format("%s", formatEscapeSet!(`\`, `\'`)(`'\a`))); 335 assert(`abcd\'\\` == format("%s", formatEscapeSet!(`\`, `\'`)(`abcd'\`))); 336 assert(`\'abcd\\` == format("%s", formatEscapeSet!(`\`, `\'`)(`'abcd\`))); 337 assert(`\'\\abcd` == format("%s", formatEscapeSet!(`\`, `\'`)(`'\abcd`))); 338 }