1 /// Utility functions for handling strings 2 module mecca.lib..string; 3 4 // Licensed under the Boost license. Full copyright information in the AUTHORS file 5 6 import std.ascii; 7 import std.conv; 8 import std..string; 9 import std.traits; 10 import std.typetuple; 11 12 import mecca.lib.exception; 13 import mecca.lib.typedid; 14 import mecca.log; 15 16 /** 17 * Convert a D string to a C style null terminated pointer 18 * 19 * The function uses a static buffer in order to keep the string whie needed. The function's return type is a custom type that tracks the 20 * life time of the pointer. This type is implicitly convertible to `char*` for ease of use. 21 * 22 * Most cases can use the return type as if it is a pointer to char: 23 * 24 * `fcntl.open(toStringzNGC(pathname), flags);` 25 * 26 * If keeping the pointer around longer is needed, it should be stored in a variable of type `auto`: 27 * 28 * `auto fileNameC = toStringzNGC(fileName);` 29 * 30 * This can be kept around until end of scope. It can be released earlier with the `release` function: 31 * 32 * `fileNameC.release();` 33 * 34 * Params: 35 * dString = the D string to be converted to zero terminated format. 36 * 37 * Returns: 38 * A custom type with a destructor, a function called `release()`, and an implicit conversion to `char*`. 39 * 40 */ 41 auto toStringzNGC(string dString) nothrow @trusted @nogc { 42 enum MaxStringSize = 4096; 43 __gshared static char[MaxStringSize] buffer; 44 __gshared static bool inUse; 45 46 ASSERT!"toStringzNGC called while another instance is still in use. Buffer currently contains: %s"(!inUse, buffer[]); 47 // DMDBUG? The following line triggers a closure GC 48 //ASSERT!"toStringzNGC got %s chars long string, maximal string size is %s"(dString.length<MaxStringSize, dString.length, MaxStringSize-1); 49 50 char[] cString = buffer[0..dString.length + 1]; 51 cString[0..dString.length] = dString[]; 52 cString[dString.length] = '\0'; 53 54 static struct toStringzNGCContext { 55 private: 56 alias LengthType = ushort; 57 static assert((1 << (LengthType.sizeof * 8)) > MaxStringSize, "Length type not big enough for buffer"); 58 LengthType length; 59 60 public: 61 @disable this(this); 62 63 this(LengthType length) nothrow @trusted @nogc { 64 this.length = length; 65 inUse = true; 66 } 67 68 ~this() nothrow @safe @nogc { 69 release(); 70 } 71 72 void release() nothrow @trusted @nogc { 73 enum UnusedString = "toStringzNGC result used after already stale\0"; 74 inUse = false; 75 buffer[0..UnusedString.length] = UnusedString[]; 76 } 77 78 @property char* ptr() nothrow @trusted @nogc { 79 return &buffer[0]; 80 } 81 82 alias ptr this; 83 } 84 85 return toStringzNGCContext(cast(toStringzNGCContext.LengthType)cString.length); 86 } 87 88 89 struct ToStringz(size_t N) { 90 char[N] buffer; 91 92 @disable this(); 93 @disable this(this); 94 95 this(const(char)[] str) nothrow @safe @nogc { 96 opAssign(str); 97 } 98 ref ToStringz opAssign(const(char)[] str) nothrow @safe @nogc { 99 assert (str.length < buffer.length, "Input string too long"); 100 buffer[0 .. str.length] = str; 101 buffer[str.length] = '\0'; 102 return this; 103 } 104 @property const(char)* ptr() const nothrow @system @nogc { 105 return buffer.ptr; 106 } 107 alias ptr this; 108 } 109 110 unittest { 111 import core.stdc..string: strlen; 112 113 assert (strlen(ToStringz!64("hello")) == 5); 114 assert (strlen(ToStringz!64("kaki")) == 4); 115 { 116 auto s = ToStringz!64("0123456789"); 117 assert (strlen(s) == 10); 118 s = "moshe"; 119 assert (strlen(s) == 5); 120 } 121 assert (strlen(ToStringz!64("mishmish")) == 8); 122 } 123 124 125 private enum FMT: ubyte { 126 STR, 127 CHR, 128 DEC, 129 HEX, 130 PTR, 131 FLT, 132 } 133 134 @notrace 135 ulong getNextNonDigitFrom(string fmt){ 136 ulong idx; 137 foreach(c; fmt){ 138 if ("0123456789+-.".indexOf(c) < 0) { 139 return idx; 140 } 141 ++idx; 142 } 143 return idx; 144 } 145 146 template splitFmt(string fmt) { 147 template pair(int j, FMT f) { 148 enum size_t pair = (j << 8) | (cast(ubyte)f); 149 } 150 151 template helper(int from, int j) { 152 enum idx = fmt[from .. $].indexOf('%'); 153 static if (idx < 0) { 154 enum helper = TypeTuple!(fmt[from .. $]); 155 } 156 else { 157 enum idx1 = idx + from; 158 static if (idx1 >= fmt.length - 1) { 159 static assert (false, "Expected formatter after %"); 160 }else{ 161 enum idx2 = idx1 + getNextNonDigitFrom(fmt [idx1+1 .. $]); 162 //pragma(msg, fmt); 163 //pragma(msg, idx2); 164 static if (fmt[idx2+1] == 's') { 165 enum helper = TypeTuple!(fmt[from .. idx2], pair!(j, FMT.STR), helper!(idx2+2, j+1)); 166 } 167 else static if (fmt[idx2+1] == 'c') { 168 enum helper = TypeTuple!(fmt[from .. idx2], pair!(j, FMT.CHR), helper!(idx2+2, j+1)); 169 } 170 else static if (fmt[idx2+1] == 'n') { 171 enum helper = TypeTuple!(fmt[from .. idx2], pair!(j, FMT.STR), helper!(idx2+2, j+1)); 172 } 173 else static if (fmt[idx2+1] == 'd') { 174 enum helper = TypeTuple!(fmt[from .. idx2], pair!(j, FMT.DEC), helper!(idx2+2, j+1)); 175 } 176 else static if (fmt[idx2+1] == 'x') { 177 enum helper = TypeTuple!(fmt[from .. idx2], pair!(j, FMT.HEX), helper!(idx2+2, j+1)); 178 } 179 else static if (fmt[idx2+1] == 'b') { // should be binary, but use hex for now 180 enum helper = TypeTuple!(fmt[from .. idx2], pair!(j, FMT.HEX), helper!(idx2+2, j+1)); 181 } 182 else static if (fmt[idx2+1] == 'p') { 183 enum helper = TypeTuple!(fmt[from .. idx2], pair!(j, FMT.PTR), helper!(idx2+2, j+1)); 184 } 185 else static if (fmt[idx2+1] == 'f' || fmt[idx2+1] == 'g') { 186 enum helper = TypeTuple!(fmt[from .. idx2], pair!(j, FMT.FLT), helper!(idx2+2, j+1)); 187 } 188 else static if (fmt[idx2+1] == '%') { 189 enum helper = TypeTuple!(fmt[from .. idx2+1], helper!(idx2+2, j)); 190 } 191 else { 192 static assert (false, "Invalid formatter '"~fmt[idx2+1]~"'"); 193 } 194 } 195 } 196 } 197 198 template countFormatters(tup...) { 199 static if (tup.length == 0) { 200 enum countFormatters = 0; 201 } 202 else static if (is(typeof(tup[0]) == size_t)) { 203 enum countFormatters = 1 + countFormatters!(tup[1 .. $]); 204 } 205 else { 206 enum countFormatters = countFormatters!(tup[1 .. $]); 207 } 208 } 209 210 alias tokens = helper!(0, 0); 211 alias numFormatters = countFormatters!tokens; 212 } 213 214 @notrace @nogc char[] formatDecimal(size_t W = 0, char fillChar = ' ', T)(char[] buf, T val) pure nothrow if (is(typeof({ulong v = val;}))) { 215 const neg = (isSigned!T) && (val < 0); 216 size_t len = neg ? 1 : 0; 217 ulong v = neg ? -long(val) : val; 218 219 auto tmp = v; 220 while (tmp) { 221 tmp /= 10; 222 len++; 223 } 224 static if (W > 0) { 225 if (W > len) { 226 buf[0 .. W - len] = fillChar; 227 len = W; 228 } 229 } 230 231 if (v == 0) { 232 static if (W > 0) { 233 buf[len-1] = '0'; 234 } 235 else { 236 buf[len++] = '0'; 237 } 238 } 239 else { 240 auto idx = len; 241 while (v) { 242 buf[--idx] = "0123456789"[v % 10]; 243 v /= 10; 244 } 245 if (neg) { 246 buf[--idx] = '-'; 247 } 248 } 249 return buf[0 .. len]; 250 } 251 252 @notrace @nogc char[] formatDecimal(char[] buf, bool val) pure nothrow { 253 if (val) { 254 return cast(char[])"1"; 255 } 256 return cast(char[])"0"; 257 } 258 259 unittest { 260 char[100] buf; 261 assert (formatDecimal!10(buf, -1234) == " -1234"); 262 assert (formatDecimal!10(buf, 0) == " 0"); 263 assert (formatDecimal(buf, -1234) == "-1234"); 264 assert (formatDecimal(buf, 0) == "0"); 265 assert (formatDecimal!3(buf, 1234) == "1234"); 266 assert (formatDecimal!3(buf, -1234) == "-1234"); 267 assert (formatDecimal!3(buf, 0) == " 0"); 268 assert (formatDecimal!(3,'0')(buf, 0) == "000"); 269 assert (formatDecimal!(3,'a')(buf, 0) == "aa0"); 270 assert (formatDecimal!(10, '0')(buf, -1234) == "00000-1234"); 271 } 272 273 @notrace @nogc char[] formatHex(size_t W=0)(char[] buf, ulong val) pure nothrow { 274 size_t len = 0; 275 auto v = val; 276 277 while (v) { 278 v >>= 4; 279 len++; 280 } 281 static if (W > 0) { 282 if (W > len) { 283 buf[0 .. W - len] = '0'; 284 len = W; 285 } 286 } 287 288 v = val; 289 if (v == 0) { 290 static if (W == 0) { 291 buf[0] = '0'; 292 len = 1; 293 } 294 } 295 else { 296 auto idx = len; 297 while (v) { 298 buf[--idx] = "0123456789ABCDEF"[v & 0x0f]; 299 v >>= 4; 300 } 301 } 302 return buf[0 .. len]; 303 } 304 305 unittest { 306 import mecca.lib.exception; 307 char[100] buf; 308 assertEQ(formatHex(buf, 0x123), "123"); 309 assertEQ(formatHex!10(buf, 0x123), "0000000123"); 310 assertEQ(formatHex(buf, 0), "0"); 311 assertEQ(formatHex!10(buf, 0), "0000000000"); 312 assertEQ(formatHex!10(buf, 0x123456789), "0123456789"); 313 assertEQ(formatHex!10(buf, 0x1234567890), "1234567890"); 314 assertEQ(formatHex!10(buf, 0x1234567890a), "1234567890A"); 315 } 316 317 @notrace @nogc char[] formatPtr(char[] buf, ulong p) pure nothrow { 318 return formatPtr(buf, cast(void*)p); 319 } 320 321 @notrace @nogc char[] formatPtr(char[] buf, const void* p) pure nothrow { 322 if (p is null) { 323 buf[0 .. 4] = "null"; 324 return buf[0 .. 4]; 325 } 326 else { 327 import std.stdint : intptr_t; 328 return formatHex!((void*).sizeof*2)(buf, cast(intptr_t)p); 329 } 330 } 331 332 @notrace @nogc char[] formatFloat(char[] buf, double val) pure nothrow { 333 assert (false, "Not implemented"); 334 } 335 336 @notrace @nogc string enumToStr(E)(E value) pure nothrow { 337 switch (value) { 338 foreach(name; __traits(allMembers, E)) { 339 case __traits(getMember, E, name): 340 return name; 341 } 342 default: 343 return null; 344 } 345 } 346 347 unittest { 348 import mecca.lib.exception; 349 import std..string: format, toUpper; 350 char[100] buf; 351 int p; 352 353 assertEQ(formatPtr(buf, 0x123), "0000000000000123"); 354 assertEQ(formatPtr(buf, 0), "null"); 355 assertEQ(formatPtr(buf, null), "null"); 356 assertEQ(formatPtr(buf, &p), format("%016x", &p).toUpper); 357 } 358 359 @notrace @nogc string nogcFormat(string fmt, T...)(char[] buf, T args) pure nothrow { 360 alias sfmt = splitFmt!fmt; 361 static assert (sfmt.numFormatters == T.length, "Expected " ~ text(sfmt.numFormatters) ~ 362 " arguments, got " ~ text(T.length)); 363 364 char[] p = buf; 365 @nogc pure nothrow 366 void advance(const(char[]) str) { 367 p = p[str.length..$]; 368 } 369 @nogc pure nothrow 370 void write(const(char[]) str) { 371 p[0..str.length] = str; 372 advance(str); 373 } 374 foreach(tok; sfmt.tokens) { 375 static if (is(typeof(tok) == string)) { 376 static if (tok.length > 0) { 377 write(tok); 378 } 379 } 380 else static if (is(typeof(tok) == size_t)) { 381 enum j = tok >> 8; 382 enum f = cast(FMT)(tok & 0xff); 383 384 alias Typ = T[j]; 385 auto val = args[j]; 386 387 static if (f == FMT.STR) { 388 static if (is(typeof(advance(val.nogcToString(p))))) { 389 advance(val.nogcToString(p)); 390 } else static if (is(Typ == string) || is(Typ == char[]) || is(Typ == char[Len], uint Len)) { 391 write(val[]); 392 } else static if (is(Typ == enum)) { 393 auto tmp = enumToStr(val); 394 if (tmp is null) { 395 advance(p.nogcFormat!"%s(%d)"(Typ.stringof, val)); 396 } else { 397 write(tmp); 398 } 399 } else static if (is(Typ == U[N], U, size_t N) || is(Typ == U[], U)) { 400 write("["); 401 foreach(i, x; val) { 402 if(i > 0) { 403 advance(p.nogcFormat!", %s"(x)); 404 } else { 405 advance(p.nogcFormat!"%s"(x)); 406 } 407 } 408 write("]"); 409 } else static if (isTypedIdentifier!Typ) { 410 advance(p.nogcFormat!(Typ.name ~ "<%s>")(val.value)); 411 } else static if (isPointer!Typ) { 412 advance(formatPtr(p, val)); 413 } else static if (is(Typ : ulong)) { 414 advance(formatDecimal(p, val)); 415 } else static if (is(Typ == struct)) { 416 { 417 enum Prefix = Typ.stringof ~ "("; 418 write(Prefix); 419 } 420 alias Names = FieldNameTuple!Typ; 421 foreach(i, field; val.tupleof) { 422 enum string Name = Names[i]; 423 enum Prefix = (i == 0 ? "" : ", ") ~ Name ~ " = "; 424 write(Prefix); 425 // TODO: Extract entire FMT.STR hangling to nogcToString and use that: 426 advance(p.nogcFormat!"%s"(field)); 427 } 428 write(")"); 429 } else { 430 static assert (false, "Expected string, enum or integer, not " ~ Typ.stringof); 431 } 432 } 433 else static if (f == FMT.CHR) { 434 static assert (is(T[j] : char)); 435 write((&val)[0..1]); 436 } 437 else static if (f == FMT.DEC) { 438 static assert (is(T[j] : ulong)); 439 advance(formatDecimal(p, val)); 440 } 441 else static if (f == FMT.HEX) { 442 static assert (is(T[j] : ulong)); 443 write("0x"); 444 advance(formatHex(p, val)); 445 } 446 else static if (f == FMT.PTR) { 447 static assert (is(T[j] : ulong) || isPointer!(T[j])); 448 advance(formatPtr(p, val)); 449 } 450 else static if (f == FMT.FLT) { 451 static assert (is(T[j] : double)); 452 advance(formatFloat(p, val)); 453 } 454 } 455 else { 456 static assert (false); 457 } 458 } 459 460 auto len = p.ptr - buf.ptr; 461 import std.exception : assumeUnique; 462 return buf[0 .. len].assumeUnique; 463 } 464 465 @notrace @nogc string nogcFormatTmp(string fmt, T...)(T args) nothrow { 466 // the lengths i have to go to fool `pure` 467 static __gshared char[1024] tmpBuf; 468 469 return nogcFormat!fmt(cast(char[])tmpBuf, args); 470 } 471 472 unittest { 473 char[100] buf; 474 assert (nogcFormat!"hello %s %s %% world %d %x %p"(buf, [1, 2, 3], "moshe", -567, 7, 7) == "hello [1, 2, 3] moshe % world -567 0x7 0000000000000007"); 475 } 476 477 unittest { 478 import std.exception; 479 import core.exception : RangeError; 480 481 auto fmt(string fmtStr, size_t size = 16, Args...)(Args args) { 482 auto buf = new char[size]; 483 return nogcFormat!fmtStr(buf, args); 484 } 485 486 static assert(fmt!"abcd abcd" == "abcd abcd"); 487 static assert(fmt!"123456789a" == "123456789a"); 488 version (D_NoBoundsChecks) {} else { 489 assertThrown!RangeError(fmt!("123412341234", 10)); 490 } 491 492 // literal escape 493 static assert(fmt!"123 %%" == "123 %"); 494 static assert(fmt!"%%%%" == "%%"); 495 496 // %d 497 static assert(fmt!"%d"(1234) == "1234"); 498 static assert(fmt!"ab%dcd"(1234) == "ab1234cd"); 499 static assert(fmt!"ab%d%d"(1234, 56) == "ab123456"); 500 501 // %x 502 static assert(fmt!"%x"(0x1234) == "0x1234"); 503 504 // %p 505 static assert(fmt!("%p", 20)(0x1234) == "0000000000001234"); 506 507 // %s 508 static assert(fmt!"12345%s"("12345") == "1234512345"); 509 static assert(fmt!"12345%s"(12345) == "1234512345"); 510 enum Floop {XXX, YYY, ZZZ} 511 static assert(fmt!"12345%s"(Floop.YYY) == "12345YYY"); 512 513 // Arg num 514 static assert(!__traits(compiles, fmt!"abc"(5))); 515 static assert(!__traits(compiles, fmt!"%d"())); 516 static assert(!__traits(compiles, fmt!"%d a %d"(5))); 517 518 // Format error 519 static assert(!__traits(compiles, fmt!"%"())); 520 static assert(!__traits(compiles, fmt!"abcd%d %"(15))); 521 static assert(!__traits(compiles, fmt!"%$"(1))); 522 //static assert(!__traits(compiles, fmt!"%s"(1))); 523 static assert(!__traits(compiles, fmt!"%d"("hello"))); 524 static assert(!__traits(compiles, fmt!"%x"("hello"))); 525 526 static assert(fmt!"Hello %s"(5) == "Hello 5"); 527 alias Moishe = TypedIdentifier!("Moishe", ushort); 528 static assert(fmt!"Hello %s"(Moishe(5)) == "Hello Moishe<5>"); 529 530 struct Foo { int x, y; } 531 static assert(fmt!("Hello %s", 40)(Foo(1, 2)) == "Hello Foo(x = 1, y = 2)"); 532 } 533 534 @notrace @nogc nothrow pure 535 string nogcRtFormat(T...)(char[] buf, string fmt, T args) { 536 size_t fmtIdx = 0; 537 size_t bufIdx = 0; 538 539 @notrace @nogc nothrow pure 540 char nextFormatter() { 541 while (true) { 542 long pctIdx = -1; 543 foreach(j, ch; fmt[fmtIdx .. $]) { 544 if (ch == '%') { 545 pctIdx = fmtIdx + j; 546 break; 547 } 548 } 549 if (pctIdx < 0) { 550 return 's'; 551 } 552 553 auto fmtChar = (pctIdx < fmt.length - 1) ? fmt[pctIdx + 1] : 's'; 554 555 auto tmp = fmt[fmtIdx .. pctIdx]; 556 buf[bufIdx .. bufIdx + tmp.length] = tmp; 557 bufIdx += tmp.length; 558 fmtIdx = pctIdx + 2; 559 560 if (fmtChar == '%') { 561 buf[bufIdx++] = '%'; 562 continue; 563 } 564 return fmtChar; 565 } 566 } 567 568 foreach(i, U; T) { 569 auto fmtChar = nextFormatter(); 570 571 static if (is(U == string) || is(U: char[])) { 572 assert (fmtChar == 's'/*, text(fmtChar)*/); 573 buf[bufIdx .. bufIdx + args[i].length] = args[i]; 574 bufIdx += args[i].length; 575 } 576 else static if (is(U == char*) || is(U == const(char)*) || is (U == const(char*))) { 577 if (fmtChar == 's') { 578 auto tmp = fromStringz(args[i]); 579 buf[bufIdx .. bufIdx + tmp.length] = tmp; 580 bufIdx += tmp.length; 581 } 582 else if (fmtChar == 'p') { 583 bufIdx += formatPtr(buf[bufIdx .. $], args[i]).length; 584 } 585 else { 586 assert (false /*, text(fmtChar)*/); 587 } 588 } 589 else static if (is(U == enum)) { 590 if (fmtChar == 's') { 591 auto tmp = enumToStr(args[i]); 592 if (tmp is null) { 593 bufIdx += nogcFormat!"%s(%d)"(buf[bufIdx .. $], U.stringof, args[i]).length; 594 } 595 else { 596 buf[bufIdx .. bufIdx + tmp.length] = tmp; 597 bufIdx += tmp.length; 598 } 599 } 600 else if (fmtChar == 'x') { 601 buf[bufIdx .. bufIdx + 2] = "0x"; 602 bufIdx += 2; 603 bufIdx += formatHex(buf[bufIdx .. $], args[i]).length; 604 } 605 else if (fmtChar == 'p') { 606 bufIdx += formatPtr(buf[bufIdx .. $], args[i]).length; 607 } 608 else if (fmtChar == 's' || fmtChar == 'd') { 609 bufIdx += formatDecimal(buf[bufIdx .. $], args[i]).length; 610 } 611 else { 612 assert (false /*, text(fmtChar)*/); 613 } 614 } 615 else static if (is(U : ulong)) { 616 if (fmtChar == 'x') { 617 buf[bufIdx .. bufIdx + 2] = "0x"; 618 bufIdx += 2; 619 bufIdx += formatHex(buf[bufIdx .. $], args[i]).length; 620 } 621 else if (fmtChar == 'p') { 622 bufIdx += formatPtr(buf[bufIdx .. $], args[i]).length; 623 } 624 else if (fmtChar == 's' || fmtChar == 'd') { 625 bufIdx += formatDecimal(buf[bufIdx .. $], args[i]).length; 626 } 627 else { 628 assert (false /*, text(fmtChar)*/); 629 } 630 } 631 else static if (is(U == TypedIdentifier!X, X...)) { 632 bufIdx += nogcFormat!"%s(%d)"(buf[bufIdx .. $], U.name, args[i].value).length; 633 } 634 else static if (isPointer!U) { 635 bufIdx += formatPtr(buf[bufIdx .. $], args[i]).length; 636 } 637 else { 638 static assert (false, "Cannot format " ~ U.stringof); 639 } 640 } 641 642 // tail 643 if (fmtIdx < fmt.length) { 644 auto tmp = fmt[fmtIdx .. $]; 645 buf[bufIdx .. bufIdx + tmp.length] = tmp; 646 bufIdx += tmp.length; 647 } 648 649 return cast(string)buf[0 .. bufIdx]; 650 } 651 652 unittest { 653 import mecca.lib.exception; 654 char[100] buf; 655 assertEQ(nogcRtFormat(buf, "hello %% %s world %d", "moshe", 15), "hello % moshe world 15"); 656 } 657 658 659 template HexFormat(T) if( isIntegral!T ) 660 { 661 static if( T.sizeof == 1 ) 662 enum HexFormat = "%02x"; 663 else static if( T.sizeof == 2 ) 664 enum HexFormat = "%04x"; 665 else static if( T.sizeof == 4 ) 666 enum HexFormat = "%08x"; 667 else static if( T.sizeof == 8 ) 668 enum HexFormat = "%016x"; 669 else 670 static assert(false); 671 } 672 673 @notrace static string hexArray(T)( const T[] array ) if( isIntegral!T ) { 674 import std..string; 675 auto res = "["; 676 foreach( i, element; array ) { 677 if( i==0 ) 678 res ~= " "; 679 else 680 res ~= ", "; 681 682 res ~= format(HexFormat!T, element); 683 } 684 res ~= " ]"; 685 return res; 686 } 687 688 689 @notrace string buildStructFormatCode(string fmt, string structName, string conversionFunction = "text") { 690 string iter = fmt[]; 691 string result = "`"; 692 while (0 < iter.length) { 693 auto phStart = iter.indexOf('{'); 694 auto phEnd = iter.indexOf('}'); 695 696 // End of format string 697 if (phStart < 0 && phEnd < 0) { 698 result ~= iter; 699 break; 700 } 701 702 assert (0 <= phStart, "single '}' in `" ~ fmt ~ "`"); 703 assert (0 <= phEnd, "single '{' in `" ~ fmt ~ "`"); 704 assert (phStart < phEnd, "single '}' in `" ~ fmt ~ "`"); 705 706 result ~= iter[0 .. phStart]; 707 result ~= "` ~ " ~ conversionFunction ~ "(" ~ structName ~ "." ~ iter[phStart + 1 .. phEnd] ~ ") ~ `"; 708 iter = iter[phEnd + 1 .. $]; 709 } 710 return result ~ "`"; 711 } 712 713 unittest { 714 struct Foo { 715 int x; 716 string y; 717 } 718 string formatFoo(Foo foo) { 719 return mixin(buildStructFormatCode("x is {x} and y is {y}", "foo")); 720 } 721 assert (formatFoo(Foo(1, "a")) == "x is 1 and y is a"); 722 assert (formatFoo(Foo(2, "b")) == "x is 2 and y is b"); 723 } 724 725 struct StaticFormatter { 726 char[] buf; 727 size_t offset; 728 729 this(char[] buf) @nogc nothrow @safe { 730 this.buf = buf; 731 offset = 0; 732 } 733 @notrace void rewind() nothrow @nogc { 734 offset = 0; 735 } 736 @notrace void append(char ch) @nogc { 737 buf[offset .. offset + 1] = ch; 738 offset++; 739 } 740 @notrace void append(string s) @nogc { 741 buf[offset .. offset + s.length] = s; 742 offset += s.length; 743 } 744 @notrace void append(string FMT, T...)(T args) @nogc { 745 auto s = nogcFormat!FMT(buf[offset .. $], args); 746 offset += s.length; 747 } 748 @notrace void accumulate(const(char)[] function(char[]) @nogc dg) @nogc { 749 auto s = dg(remaining); 750 assert(cast(void*)s.ptr == remaining.ptr, "dg() returned wrong buffer"); 751 skip(s.length); 752 } 753 @notrace void accumulate(const(char)[] delegate(char[]) @nogc dg) @nogc { 754 auto s = dg(remaining); 755 assert(cast(void*)s.ptr == remaining.ptr, "dg() returned wrong buffer"); 756 skip(s.length); 757 } 758 @notrace void accumulate(alias F, T...)(T args) @nogc { 759 auto s = F(remaining, args); 760 assert(cast(void*)s.ptr == remaining.ptr, "dg() returned wrong buffer"); 761 skip(s.length); 762 } 763 764 @property string text() @nogc { 765 return cast(string)buf[0 .. offset]; 766 } 767 @property char[] remaining() @nogc { 768 return buf[offset .. $]; 769 } 770 @notrace void skip(size_t count) @nogc { 771 assert (offset + count <= buf.length, "overflow"); 772 offset += count; 773 } 774 } 775 776 unittest { 777 char[100] buf; 778 auto sf = StaticFormatter(buf); 779 sf.append!"a=%s b=%s "(1, 2); 780 sf.append!"c=%s d=%s"(3, 4); 781 assert(sf.text == "a=1 b=2 c=3 d=4", sf.text); 782 } 783 784