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 * © 2017 $(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 core.stdc.stdlib; 18 import core.stdc..string; 19 import core.bitop; 20 import std..string; 21 import std.traits; 22 import std.typecons; 23 import std.typetuple; 24 import fast.internal.helpers; 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 import std.exception; 229 230 enum argCnt = formatStringArgCount!fmt; 231 232 enum codeGen() 233 { 234 string code = `pure nothrow string format(`; 235 foreach (i; staticIota!(0, argCnt)) 236 { 237 if (i) code ~= `, `; 238 code ~= std..string.format("A%s", i); 239 } 240 code ~= `)(`; 241 foreach (i; staticIota!(0, argCnt)) 242 { 243 if (i) code ~= `, `; 244 code ~= std..string.format("A%s a%s", i, i); 245 } 246 code ~= `, char[] buffer = new char[](spaceRequirements!(fmt`; 247 foreach (i; staticIota!(0, argCnt)) 248 code ~= std..string.format(", A%s", i); 249 code ~= `))) { return assumeUnique(formattedWrite!fmt(buffer.ptr`; 250 foreach (i; staticIota!(0, argCnt)) 251 code ~= std..string.format(", a%s", i); 252 code ~= `)); }`; 253 return code; 254 } 255 256 mixin(codeGen()); 257 } 258 259 260 template formata(string fmt) 261 { 262 enum argCnt = formatStringArgCount!fmt; 263 264 enum codeGen() 265 { 266 string code = `pure nothrow @nogc char[] formata(`; 267 foreach (i; staticIota!(0, argCnt)) 268 { 269 if (i) code ~= `, `; 270 code ~= std..string.format("A%s", i); 271 } 272 code ~= `)(`; 273 foreach (i; staticIota!(0, argCnt)) 274 { 275 if (i) code ~= `, `; 276 code ~= std..string.format("A%s a%s", i, i); 277 } 278 code ~= `, void* buffer = alloca(spaceRequirements!(fmt`; 279 foreach (i; staticIota!(0, argCnt)) 280 code ~= std..string.format(", A%s", i); 281 code ~= `))) { return formattedWrite!fmt(cast(char*) buffer`; 282 foreach (i; staticIota!(0, argCnt)) 283 code ~= std..string.format(", a%s", i); 284 code ~= `); }`; 285 return code; 286 } 287 288 mixin(codeGen()); 289 } 290 291 292 template formats(string fmt) 293 { 294 enum argCnt = formatStringArgCount!fmt; 295 296 enum codeGen() 297 { 298 string code = `@safe pure nothrow @nogc auto formats(`; 299 foreach (i; staticIota!(0, argCnt)) 300 { 301 if (i) code ~= `, `; 302 code ~= std..string.format("A%s", i); 303 } 304 code ~= `)(`; 305 foreach (i; staticIota!(0, argCnt)) 306 { 307 if (i) code ~= `, `; 308 code ~= std..string.format("A%s a%s", i, i); 309 } 310 code ~= `))) { LimitedScopeBuffer!(char, spaceRequirements!(fmt`; 311 foreach (i; staticIota!(0, argCnt)) 312 code ~= std..string.format(", A%s", i); 313 code ~= `)) buffer; buffer.length = formattedWrite!fmt(buffer.ptr`; 314 foreach (i; staticIota!(0, argCnt)) 315 code ~= std..string.format(", a%s", i); 316 code ~= `).length; return buffer; }`; 317 return code; 318 } 319 320 mixin(codeGen()); 321 } 322 323 324 char[] formattedWrite(string format, Args...)(char* buffer, Args args) 325 { 326 char* it = buffer; 327 328 alias parts = tokenizedFormatString!format; 329 foreach (i; staticIota!(0, parts.length)) 330 { 331 static if (parts[i][1] == size_t.max) 332 { 333 // Direct string copy 334 memcpy( it, parts[i][0].ptr, parts[i][0].length ); 335 it += parts[i][0].length; 336 } 337 else 338 { 339 // Formatted argument 340 it.formattedWriteItem!(parts[i][0])( args[parts[i][1]] ); 341 } 342 } 343 344 return buffer[0 .. it - buffer]; 345 } 346 347 348 pure nothrow @nogc 349 void formattedWriteItem(string format, T)(ref char* buffer, T t) 350 if (isUnsigned!T && format == "%x") 351 { 352 alias RT = ReturnType!(hexStrLower!T); 353 *cast(RT*) buffer = hexStrLower!T(t); 354 buffer += RT.length; 355 } 356 357 358 pure nothrow @nogc 359 void formattedWriteItem(string format, T)(ref char* buffer, T t) 360 if (isUnsigned!T && format == "%X") 361 { 362 alias RT = ReturnType!(hexStrUpper!T); 363 *cast(RT*) buffer = hexStrUpper!T(t); 364 buffer += RT.length; 365 } 366 367 368 pure nothrow @nogc 369 void formattedWriteItem(string format, T)(ref char* buffer, T t) 370 if (isIntegral!T && (format == "%s" || format == "%d")) 371 { 372 auto str = decStr(t); 373 memcpy( buffer, str.ptr, str.length ); 374 buffer += str.length; 375 } 376 377 378 pure nothrow @nogc 379 void formattedWriteItem(string format)(ref char* buffer, void* p) 380 if (format == "%s" || format == "%p") 381 { 382 buffer.formattedWriteItem!"%X"( cast(size_t) p ); 383 } 384 385 386 /+ 387 ╔══════════════════════════════════════════════════════════════════════════════ 388 ║ ⚑ Helper Structs 389 ╚══════════════════════════════════════════════════════════════════════════════ 390 +/ 391 392 struct RevFillStr(size_t n) 393 { 394 private: 395 396 size_t offset = n; 397 char[n] buffer = '\0'; 398 399 400 public: 401 402 alias opSlice this; 403 404 @safe pure nothrow @nogc 405 void opOpAssign(string op : "~")(char ch) 406 in 407 { 408 assert( offset > 0 ); 409 } 410 body 411 { 412 buffer[--offset] = ch; 413 } 414 415 416 @safe pure nothrow @nogc 417 @property inout(char)[] opSlice() inout 418 { 419 return buffer[offset .. n]; 420 } 421 422 423 @safe pure nothrow @nogc 424 @property inout(char)* ptr() inout 425 { 426 return &buffer[offset]; 427 } 428 429 430 @safe pure nothrow @nogc 431 @property size_t length() const 432 { 433 return n - offset; 434 } 435 }