1 /******************************************************************************* 2 * 3 * Functions for formatting data into strings and back. 4 * 5 * Authors: 6 * $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise) 7 * 8 * Copyright: 9 * © 2015 $(LINK2 mailto:Marco.Leise@gmx.de, Marco Leise) 10 * 11 * License: 12 * $(LINK2 http://www.gnu.org/licenses/gpl-3.0, GNU General Public License 3.0) 13 * 14 **************************************/ 15 module fast.format; 16 17 import fast.helpers; 18 import std.string; 19 import std.traits; 20 import std.typecons; 21 import std.typetuple; 22 import core.stdc.stdlib; 23 import core.stdc.string; 24 import core.bitop; 25 26 27 /+ 28 ╔══════════════════════════════════════════════════════════════════════════════ 29 ║ ⚑ Hex String 30 ╚══════════════════════════════════════════════════════════════════════════════ 31 +/ 32 33 /** 34 * Converts an unsigned type into a fixed width 8 digits hex string using lower-case letters. 35 * 36 * Params: 37 * n = the number to convert 38 * 39 * Returns: 40 * hexadecimal representation of $(D n), lower-case letters 41 */ 42 @safe pure nothrow @nogc 43 char[2 * U.sizeof] hexStrLower(U)(Unqual!U n) if (isUnsigned!U) 44 { 45 char[2 * U.sizeof] hex = void; 46 foreach_reverse (i; 0 .. 2 * U.sizeof) 47 { 48 U d = n & U(0xF); 49 hex[i] = cast(char) (d < 10 ? '0' + d : 'a' + d - 10); 50 n >>= 4; 51 } 52 return hex; 53 } 54 55 56 /** 57 * Converts an unsigned type into a fixed width 8 digits hex string using upper-case letters. 58 * 59 * Params: 60 * n = the number to convert 61 * 62 * Returns: 63 * hexadecimal representation of $(D n), upper-case letters 64 */ 65 @safe pure nothrow @nogc 66 char[2 * U.sizeof] hexStrUpper(U)(U n) if (isUnsigned!U) 67 { 68 char[2 * U.sizeof] hex = void; 69 foreach_reverse (i; 0 .. 2 * U.sizeof) 70 { 71 U d = n & U(0xF); 72 hex[i] = cast(char) (d < 10 ? '0' + d : 'A' + d - 10); 73 n >>= 4; 74 } 75 return hex; 76 } 77 78 79 /+ 80 ╔══════════════════════════════════════════════════════════════════════════════ 81 ║ ⚑ Decimal String 82 ╚══════════════════════════════════════════════════════════════════════════════ 83 +/ 84 85 template decDigits(T) if (isIntegral!T) 86 { 87 static if (is(T == ulong)) 88 enum decDigits = 20; 89 else static if (is(T == long)) 90 enum decDigits = 19; 91 else static if (is(T == uint) || is(T == int)) 92 enum decDigits = 10; 93 else static if (is(T == ushort) || is(T == short)) 94 enum decDigits = 5; 95 else static if (is(T == ubyte) || is(T == byte)) 96 enum decDigits = 3; 97 } 98 99 100 enum decChars(T) = decDigits!T + isSigned!T; 101 102 103 @safe pure nothrow @nogc 104 RevFillStr!(decChars!I) decStr(I)(I i) if (isIntegral!I) 105 { 106 RevFillStr!(decChars!I) str; 107 size_t idx = decChars!I; 108 109 static if (isSigned!I) 110 { 111 bool signed = i < 0; 112 UnsignedOf!I u = i < 0 ? -i : i; 113 } 114 else alias u = i; 115 116 do 117 { 118 str ~= char('0' + u % 10); 119 u /= 10; 120 } 121 while (u); 122 123 static if (isSigned!I) if (signed) 124 str ~= '-'; 125 126 return str; 127 } 128 129 130 /+ 131 ╔══════════════════════════════════════════════════════════════════════════════ 132 ║ ⚑ Formatting 133 ╚══════════════════════════════════════════════════════════════════════════════ 134 +/ 135 136 template hasKnownSpaceRequirement(T) 137 { 138 static if (isIntegral!T || isPointer!T) 139 enum hasKnownSpaceRequirement = true; 140 else 141 enum hasKnownSpaceRequirement = false; 142 } 143 144 145 template spaceRequirement(string format, T) if (hasKnownSpaceRequirement!T) 146 { 147 static if (isIntegral!T) 148 { 149 static if (format == "%s" || format == "%d") 150 enum spaceRequirement = decChars!T; 151 else static if (isUnsigned!T && (format == "%x" || format == "%X")) 152 enum spaceRequirement = 2 * T.sizeof; 153 else static assert (0, "Don't know how to handle " ~ T.stringof ~ " as " ~ format); 154 } 155 else static if (isPointer!T) 156 { 157 static if (format == "%s" || format == "%p") 158 enum spaceRequirement = 2 * T.sizeof; 159 else static assert (0, "Don't know how to handle " ~ T.stringof ~ " as " ~ format); 160 } 161 else static assert (0, "Don't know how to handle " ~ T.stringof); 162 } 163 164 165 enum spaceRequirements(string format, Args...)() if (allSatisfy!(hasKnownSpaceRequirement, Args)) 166 { 167 size_t sum = 0; 168 169 alias parts = tokenizedFormatString!format; 170 foreach (i; staticIota!(0, parts.length)) 171 { 172 static if (parts[i][1] == size_t.max) 173 sum += parts[i][0].length; 174 else 175 sum += spaceRequirement!(parts[i][0], Args[parts[i][1]]); 176 } 177 178 return sum; 179 } 180 181 182 template tokenizedFormatString(string format) 183 { 184 enum impl() 185 { 186 Tuple!(string, size_t)[] parts; 187 size_t i = 0; 188 string rest = format; 189 190 while (1) 191 { 192 ptrdiff_t markerPos = rest.indexOf("%"); 193 if (markerPos < 0) 194 return rest.length ? parts ~ tuple(rest, size_t.max) : parts; 195 196 if (markerPos) 197 { 198 parts ~= tuple(rest[0 .. markerPos], size_t.max); 199 rest = rest[markerPos .. $]; 200 } 201 202 // TODO: more complex formats 203 parts ~= tuple(rest[0 .. 2], i++); 204 rest = rest[2 .. $]; 205 } 206 } 207 208 enum result = impl(); 209 static immutable Tuple!(string, size_t)[result.length] tokenizedFormatString = result; 210 } 211 212 213 enum formatStringArgCount(string format)() 214 { 215 size_t count = 0; 216 217 alias parts = tokenizedFormatString!format; 218 foreach (i; staticIota!(0, parts.length)) 219 if (parts[i][1] != size_t.max && parts[i][1] >= count) 220 count = parts[i][1] + 1; 221 222 return count; 223 } 224 225 226 template format(string fmt) 227 { 228 enum argCnt = formatStringArgCount!fmt; 229 230 enum codeGen() 231 { 232 string code = `pure nothrow string format(`; 233 foreach (i; staticIota!(0, argCnt)) 234 { 235 if (i) code ~= `, `; 236 code ~= std..string.format("A%s", i); 237 } 238 code ~= `)(`; 239 foreach (i; staticIota!(0, argCnt)) 240 { 241 if (i) code ~= `, `; 242 code ~= std..string.format("A%s a%s", i, i); 243 } 244 code ~= `, char[] buffer = new char[](spaceRequirements!(fmt`; 245 foreach (i; staticIota!(0, argCnt)) 246 code ~= std..string.format(", A%s", i); 247 code ~= `))) { return std.exception.assumeUnique(formattedWrite!fmt(buffer.ptr`; 248 foreach (i; staticIota!(0, argCnt)) 249 code ~= std..string.format(", a%s", i); 250 code ~= `)); }`; 251 return code; 252 } 253 254 mixin(codeGen()); 255 } 256 257 258 template formata(string fmt) 259 { 260 enum argCnt = formatStringArgCount!fmt; 261 262 enum codeGen() 263 { 264 string code = `pure nothrow @nogc char[] formata(`; 265 foreach (i; staticIota!(0, argCnt)) 266 { 267 if (i) code ~= `, `; 268 code ~= std..string.format("A%s", i); 269 } 270 code ~= `)(`; 271 foreach (i; staticIota!(0, argCnt)) 272 { 273 if (i) code ~= `, `; 274 code ~= std..string.format("A%s a%s", i, i); 275 } 276 code ~= `, void* buffer = alloca(spaceRequirements!(fmt`; 277 foreach (i; staticIota!(0, argCnt)) 278 code ~= std..string.format(", A%s", i); 279 code ~= `))) { return formattedWrite!fmt(cast(char*) buffer`; 280 foreach (i; staticIota!(0, argCnt)) 281 code ~= std..string.format(", a%s", i); 282 code ~= `); }`; 283 return code; 284 } 285 286 mixin(codeGen()); 287 } 288 289 290 template formats(string fmt) 291 { 292 enum argCnt = formatStringArgCount!fmt; 293 294 enum codeGen() 295 { 296 string code = `@safe pure nothrow @nogc auto formats(`; 297 foreach (i; staticIota!(0, argCnt)) 298 { 299 if (i) code ~= `, `; 300 code ~= std..string.format("A%s", i); 301 } 302 code ~= `)(`; 303 foreach (i; staticIota!(0, argCnt)) 304 { 305 if (i) code ~= `, `; 306 code ~= std..string.format("A%s a%s", i, i); 307 } 308 code ~= `))) { LimitedScopeBuffer!(char, spaceRequirements!(fmt`; 309 foreach (i; staticIota!(0, argCnt)) 310 code ~= std..string.format(", A%s", i); 311 code ~= `)) buffer; buffer.length = formattedWrite!fmt(buffer.ptr`; 312 foreach (i; staticIota!(0, argCnt)) 313 code ~= std..string.format(", a%s", i); 314 code ~= `).length; return buffer; }`; 315 return code; 316 } 317 318 mixin(codeGen()); 319 } 320 321 322 char[] formattedWrite(string format, Args...)(char* buffer, Args args) 323 { 324 char* it = buffer; 325 326 alias parts = tokenizedFormatString!format; 327 foreach (i; staticIota!(0, parts.length)) 328 { 329 static if (parts[i][1] == size_t.max) 330 { 331 // Direct string copy 332 memcpy( it, parts[i][0].ptr, parts[i][0].length ); 333 it += parts[i][0].length; 334 } 335 else 336 { 337 // Formatted argument 338 it.formattedWriteItem!(parts[i][0])( args[parts[i][1]] ); 339 } 340 } 341 342 return buffer[0 .. it - buffer]; 343 } 344 345 346 pure nothrow @nogc 347 void formattedWriteItem(string format, T)(ref char* buffer, T t) 348 if (isUnsigned!T && format == "%x") 349 { 350 alias RT = ReturnType!(hexStrLower!T); 351 *cast(RT*) buffer = hexStrLower!T(t); 352 buffer += RT.length; 353 } 354 355 356 pure nothrow @nogc 357 void formattedWriteItem(string format, T)(ref char* buffer, T t) 358 if (isUnsigned!T && format == "%X") 359 { 360 alias RT = ReturnType!(hexStrUpper!T); 361 *cast(RT*) buffer = hexStrUpper!T(t); 362 buffer += RT.length; 363 } 364 365 366 pure nothrow @nogc 367 void formattedWriteItem(string format, T)(ref char* buffer, T t) 368 if (isIntegral!T && (format == "%s" || format == "%d")) 369 { 370 auto str = decStr(t); 371 memcpy( buffer, str.ptr, str.length ); 372 buffer += str.length; 373 } 374 375 376 pure nothrow @nogc 377 void formattedWriteItem(string format)(ref char* buffer, void* p) 378 if (format == "%s" || format == "%p") 379 { 380 buffer.formattedWriteItem!"%X"( cast(size_t) p ); 381 } 382 383 384 /+ 385 ╔══════════════════════════════════════════════════════════════════════════════ 386 ║ ⚑ Helper Structs 387 ╚══════════════════════════════════════════════════════════════════════════════ 388 +/ 389 390 struct RevFillStr(size_t n) 391 { 392 private: 393 394 size_t offset = n; 395 char[n] buffer = '\0'; 396 397 398 public: 399 400 alias opSlice this; 401 402 @safe pure nothrow @nogc 403 void opOpAssign(string op : "~")(char ch) 404 in 405 { 406 assert( offset > 0 ); 407 } 408 body 409 { 410 buffer[--offset] = ch; 411 } 412 413 414 @safe pure nothrow @nogc 415 @property inout(char)[] opSlice() inout 416 { 417 return buffer[offset .. n]; 418 } 419 420 421 @safe pure nothrow @nogc 422 @property inout(char)* ptr() inout 423 { 424 return &buffer[offset]; 425 } 426 427 428 @safe pure nothrow @nogc 429 @property size_t length() const 430 { 431 return n - offset; 432 } 433 }