1 /++ 2 $(H1 @nogc Formatting Utilities) 3 4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 5 Authors: Ilya Yaroshenko 6 +/ 7 module mir.format; 8 9 import std.traits; 10 import mir.primitives: isOutputRange; 11 12 /// `mir.conv: to` extension. 13 version(mir_test) 14 @safe pure @nogc 15 unittest 16 { 17 import mir.conv: to; 18 import mir.small_string; 19 alias S = SmallString!32; 20 21 // Floating-point numbers are formatted to 22 // the shortest precise exponential notation. 23 assert(123.0.to!S == "123.0"); 24 assert(123.to!(immutable S) == "123"); 25 assert(true.to!S == "true"); 26 assert(true.to!string == "true"); 27 28 assert((cast(S)"str")[] == "str"); 29 } 30 31 /// `mir.conv: to` extension. 32 version(mir_test) 33 @safe pure 34 unittest 35 { 36 import mir.conv: to; 37 import mir.small_string; 38 alias S = SmallString!32; 39 40 auto str = S("str"); 41 assert(str.to!(const(char)[]) == "str"); // GC allocated result 42 assert(str.to!(char[]) == "str"); // GC allocated result 43 } 44 45 /// ditto 46 version(mir_test) 47 @safe pure 48 unittest 49 { 50 import mir.conv: to; 51 import mir.small_string; 52 alias S = SmallString!32; 53 54 // Floating-point numbers are formatted to 55 // the shortest precise exponential notation. 56 assert(123.0.to!string == "123.0"); 57 assert(123.to!(char[]) == "123"); 58 59 assert(S("str").to!string == "str"); // GC allocated result 60 } 61 62 /// Concatenated string results 63 string text(string separator = "", Args...)(auto ref Args args) 64 if (Args.length > 0) 65 { 66 static if (Args.length == 1) 67 { 68 import mir.functional: forward; 69 import mir.conv: to; 70 return to!string(forward!args); 71 } 72 else 73 { 74 import mir.appender: scopedBuffer; 75 auto buffer = scopedBuffer!char; 76 foreach (i, ref arg; args) 77 { 78 buffer.print(arg); 79 static if (separator.length && i + 1 < args.length) 80 { 81 buffer.printStaticString!char(separator); 82 } 83 } 84 return buffer.data.idup; 85 } 86 } 87 88 /// 89 @safe pure nothrow unittest 90 { 91 const i = 100; 92 assert(text("str ", true, " ", i, " ", 124.1) == "str true 100 124.1", text("str ", true, " ", 100, " ", 124.1)); 93 assert(text!" "("str", true, 100, 124.1) == "str true 100 124.1"); 94 } 95 96 import mir.format_impl; 97 98 /// 99 struct GetData {} 100 101 /// 102 enum getData = GetData(); 103 104 /++ 105 +/ 106 struct _stringBuf(C) 107 { 108 import mir.appender: ScopedBuffer; 109 110 /// 111 ScopedBuffer!(C, 256) buffer; 112 113 /// 114 alias buffer this; 115 116 /// 117 mixin StreamFormatOp!C; 118 } 119 120 ///ditto 121 alias stringBuf = _stringBuf!char; 122 ///ditto 123 alias wstringBuf = _stringBuf!wchar; 124 ///ditto 125 alias dstringBuf = _stringBuf!dchar; 126 127 /++ 128 +/ 129 mixin template StreamFormatOp(C) 130 { 131 /// 132 ref typeof(this) opBinary(string op : "<<", T)(ref const T c) scope 133 { 134 return print!C(this, c); 135 } 136 137 /// 138 ref typeof(this) opBinary(string op : "<<", T)(const T c) scope 139 { 140 return print!C(this, c); 141 } 142 143 /// ditto 144 const(C)[] opBinary(string op : "<<", T : GetData)(const T c) scope 145 { 146 return buffer.data; 147 } 148 } 149 150 /// 151 @safe pure nothrow @nogc 152 version (mir_test) unittest 153 { 154 auto name = "D"; 155 auto ver = 2.0; 156 assert(stringBuf() << "Hi " << name << ver << "!\n" << getData == "Hi D2.0!\n"); 157 } 158 159 /// 160 @safe pure nothrow @nogc 161 version (mir_test) unittest 162 { 163 auto name = "D"w; 164 auto ver = 2; 165 assert(wstringBuf() << "Hi "w << name << ver << "!\n"w << getData == "Hi D2!\n"w); 166 } 167 168 /// 169 @safe pure nothrow @nogc 170 version (mir_test) unittest 171 { 172 auto name = "D"d; 173 auto ver = 2UL; 174 assert(dstringBuf() << "Hi "d << name << ver << "!\n"d << getData == "Hi D2!\n"); 175 } 176 177 @safe pure nothrow @nogc 178 version (mir_test) unittest 179 { 180 assert(stringBuf() << -1234567890 << getData == "-1234567890"); 181 } 182 183 /++ 184 Mir's numeric format specification 185 186 Note: the specification isn't complete an may be extended in the future. 187 +/ 188 struct NumericSpec 189 { 190 /// 191 enum Format 192 { 193 /++ 194 Human-frindly precise output. 195 Examples: `0.000001`, `600000.0`, but `1e-7` and `6e7`. 196 +/ 197 human, 198 /++ 199 Precise output with explicit exponent. 200 Examples: `1e-6`, `6e6`, `1.23456789e-100`. 201 +/ 202 exponent, 203 } 204 205 /// 206 Format format; 207 208 /// Default valus is '\0' (no separators) 209 char separatorChar = '\0'; 210 211 /// Defaults to 'e' 212 char exponentChar = 'e'; 213 214 /// Adds '+' to positive numbers and `+0`. 215 bool plus; 216 217 /// Separator count 218 ubyte separatorCount = 3; 219 220 /++ 221 Precise output with explicit exponent. 222 Examples: `1e-6`, `6e6`, `1.23456789e-100`. 223 +/ 224 enum NumericSpec exponent = NumericSpec(Format.exponent); 225 226 /++ 227 Human-frindly precise output. 228 +/ 229 enum NumericSpec human = NumericSpec(Format.human); 230 } 231 232 // 16-bytes 233 /// C's compatible format specifier. 234 struct FormatSpec 235 { 236 /// 237 bool dash; 238 /// 239 bool plus; 240 /// 241 bool space; 242 /// 243 bool hash; 244 /// 245 bool zero; 246 /// 247 char format = 's'; 248 /// 249 char separator = '\0'; 250 /// 251 ubyte unitSize; 252 /// 253 int width; 254 /// 255 int precision = -1; 256 } 257 258 /++ 259 +/ 260 enum SwitchLU : bool 261 { 262 /// 263 lower, 264 /// 265 upper, 266 } 267 268 /++ 269 Wrapper to format floating point numbers using C's library. 270 +/ 271 struct FormattedFloating(T) 272 if(is(T == float) || is(T == double) || is(T == real)) 273 { 274 /// 275 T value; 276 /// 277 FormatSpec spec; 278 279 /// 280 void toString(C = char, W)(scope ref W w) scope const 281 if (isSomeChar!C) 282 { 283 C[512] buf = void; 284 auto n = printFloatingPoint(value, spec, buf); 285 w.put(buf[0 .. n]); 286 } 287 } 288 289 /// ditto 290 FormattedFloating!T withFormat(T)(const T value, FormatSpec spec) 291 { 292 version(LDC) pragma(inline); 293 return typeof(return)(value, spec); 294 } 295 296 /++ 297 +/ 298 struct HexAddress(T) 299 if (isUnsigned!T && !is(T == enum)) 300 { 301 /// 302 T value; 303 /// 304 SwitchLU switchLU = SwitchLU.upper; 305 306 /// 307 void toString(C = char, W)(scope ref W w) scope const 308 if (isSomeChar!C) 309 { 310 enum N = T.sizeof * 2; 311 static if(isFastBuffer!W) 312 { 313 w.advance(printHexAddress(value, w.getStaticBuf!N, cast(bool) switchLU)); 314 } 315 else 316 { 317 C[N] buf = void; 318 printHexAddress(value, buf, cast(bool) switchLU); 319 w.put(buf[]); 320 } 321 } 322 } 323 324 /++ 325 Escaped string formats 326 +/ 327 enum EscapeFormat 328 { 329 /// JSON escaped string format 330 json, 331 /// Amzn Ion CLOB format 332 ionClob, 333 /// Amzn Ion symbol format 334 ionSymbol, 335 /// Amzn Ion string format 336 ion, 337 } 338 339 enum escapeFormatQuote(EscapeFormat escapeFormat) = escapeFormat == EscapeFormat.ionSymbol ? '\'' : '\"'; 340 341 /++ 342 +/ 343 ref W printEscaped(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope return ref W w, scope const(C)[] str) 344 if (isOutputRange!(W, C)) 345 { 346 import mir.utility: _expect; 347 foreach (C c; str) 348 { 349 if (_expect(c == escapeFormatQuote!escapeFormat || c == '\\', false)) 350 goto E; 351 if (_expect(c < ' ', false)) 352 goto C; 353 static if (escapeFormat == EscapeFormat.ionClob) 354 { 355 if (c >= 127) 356 goto A; 357 } 358 P: 359 w.put(c); 360 continue; 361 E: 362 { 363 C[2] pair; 364 pair[0] = '\\'; 365 pair[1] = c; 366 w.printStaticString!C(pair); 367 continue; 368 } 369 C: 370 switch (c) 371 { 372 static if (escapeFormat != EscapeFormat.json) 373 { 374 case '\0': 375 c = '0'; 376 goto E; 377 case '\a': 378 c = 'a'; 379 goto E; 380 case '\v': 381 c = 'v'; 382 goto E; 383 } 384 case '\b': 385 c = 'b'; 386 goto E; 387 case '\t': 388 c = 't'; 389 goto E; 390 case '\n': 391 c = 'n'; 392 goto E; 393 case '\f': 394 c = 'f'; 395 goto E; 396 case '\r': 397 c = 'r'; 398 goto E; 399 default: 400 A: 401 static if (escapeFormat == EscapeFormat.json) 402 put_uXXXX!C(w, cast(char)c); 403 else 404 put_xXX!C(w, cast(char)c); 405 } 406 } 407 return w; 408 } 409 410 /// 411 @safe pure nothrow @nogc 412 version (mir_test) unittest 413 { 414 import mir.format: stringBuf; 415 stringBuf w; 416 assert(w.printEscaped("Hi \a\v\0\f\t\b \\\r\n" ~ `"@nogc"`).data == `Hi \a\v\0\f\t\b \\\r\n\"@nogc\"`); 417 w.reset; 418 assert(w.printEscaped("\x03").data == `\x03`, w.data); 419 } 420 421 /++ 422 Decodes `char` `c` to the form `u00XX`, where `XX` is 2 hexadecimal characters. 423 +/ 424 ref W put_xXX(C = char, W)(scope return ref W w, char c) 425 if (isSomeChar!C) 426 { 427 ubyte[2] spl; 428 spl[0] = c >> 4; 429 spl[1] = c & 0xF; 430 C[4] buffer; 431 buffer[0] = '\\'; 432 buffer[1] = 'x'; 433 buffer[2] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); 434 buffer[3] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); 435 return w.printStaticString(buffer); 436 } 437 438 /++ 439 Decodes `char` `c` to the form `u00XX`, where `XX` is 2 hexadecimal characters. 440 +/ 441 ref W put_uXXXX(C = char, W)(scope return ref W w, char c) 442 if (isSomeChar!C) 443 { 444 ubyte[2] spl; 445 spl[0] = c >> 4; 446 spl[1] = c & 0xF; 447 C[6] buffer; 448 buffer[0] = '\\'; 449 buffer[1] = 'u'; 450 buffer[2] = '0'; 451 buffer[3] = '0'; 452 buffer[4] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); 453 buffer[5] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); 454 return w.printStaticString(buffer); 455 } 456 457 /++ 458 Decodes ushort `c` to the form `uXXXX`, where `XXXX` is 2 hexadecimal characters. 459 +/ 460 ref W put_uXXXX(C = char, W)(scope return ref W w, ushort c) 461 if (isSomeChar!C) 462 { 463 ubyte[4] spl; 464 spl[0] = (c >> 12) & 0xF; 465 spl[1] = (c >> 8) & 0xF; 466 spl[2] = (c >> 4) & 0xF; 467 spl[3] = c & 0xF; 468 C[6] buffer; 469 buffer[0] = '\\'; 470 buffer[1] = 'u'; 471 buffer[2] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A'); 472 buffer[3] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A'); 473 buffer[4] = cast(ubyte)(spl[2] < 10 ? spl[2] + '0' : spl[2] - 10 + 'A'); 474 buffer[5] = cast(ubyte)(spl[3] < 10 ? spl[3] + '0' : spl[3] - 10 + 'A'); 475 return w.printStaticString(buffer); 476 } 477 478 /// 479 ref W printElement(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope return ref W w, scope const(C)[] c) 480 if (isSomeChar!C) 481 { 482 static immutable C[1] quote = '\"'; 483 return w 484 .printStaticString!C(quote) 485 .printEscaped!(C, escapeFormat)(c) 486 .printStaticString!C(quote); 487 } 488 489 /// 490 ref W printElement(C = char, EscapeFormat escapeFormat = EscapeFormat.ion, W, T)(scope return ref W w, scope auto ref const T c) 491 if (!isSomeString!T) 492 { 493 return w.print!C(c); 494 } 495 496 /++ 497 Multiargument overload. 498 +/ 499 ref W print(C = char, W, Args...)(scope return ref W w, scope auto ref const Args args) 500 if (isSomeChar!C && Args.length > 1) 501 { 502 foreach(i, ref c; args) 503 static if (i < Args.length - 1) 504 w.print!C(c); 505 else 506 return w.print!C(c); 507 } 508 509 /// Prints enums 510 ref W print(C = char, W, T)(scope return ref W w, const T c) 511 if (isSomeChar!C && is(T == enum)) 512 { 513 import mir.enums: getEnumIndex, enumStrings; 514 import mir.utility: _expect; 515 516 static assert(!is(OriginalType!T == enum)); 517 uint index = void; 518 if (getEnumIndex(c, index)._expect(true)) 519 { 520 w.put(enumStrings!T[index]); 521 return w; 522 } 523 static immutable C[] str = T.stringof ~ "("; 524 w.put(str[]); 525 print!C(w, cast(OriginalType!T) c); 526 w.put(')'); 527 return w; 528 } 529 530 /// 531 @safe pure nothrow @nogc 532 version (mir_test) unittest 533 { 534 enum Flag 535 { 536 no, 537 yes, 538 } 539 540 import mir.appender: scopedBuffer; 541 auto w = scopedBuffer!char; 542 w.print(Flag.yes); 543 assert(w.data == "yes", w.data); 544 } 545 546 /// Prints boolean 547 ref W print(C = char, W)(scope return ref W w, bool c) 548 if (isSomeChar!C) 549 { 550 enum N = 5; 551 static if(isFastBuffer!W) 552 { 553 w.advance(printBoolean(c, w.getStaticBuf!N)); 554 } 555 else 556 { 557 C[N] buf = void; 558 auto n = printBoolean(c, buf); 559 w.put(buf[0 .. n]); 560 } 561 return w; 562 } 563 564 /// 565 @safe pure nothrow @nogc 566 version (mir_test) unittest 567 { 568 import mir.appender: scopedBuffer; 569 auto w = scopedBuffer!char; 570 assert(w.print(true).data == `true`, w.data); 571 w.reset; 572 assert(w.print(false).data == `false`, w.data); 573 } 574 575 /// Prints associative array 576 pragma(inline, false) 577 ref W print(C = char, W, V, K)(scope return ref W w, scope const V[K] c) 578 if (isSomeChar!C) 579 { 580 enum C left = '['; 581 enum C right = ']'; 582 enum C[2] sep = ", "; 583 enum C[2] mid = ": "; 584 w.put(left); 585 bool first = true; 586 foreach (ref key, ref value; c) 587 { 588 if (!first) 589 w.printStaticString!C(sep); 590 first = false; 591 w 592 .printElement!C(key) 593 .printStaticString!C(mid) 594 .printElement!C(value); 595 } 596 w.put(right); 597 return w; 598 } 599 600 /// 601 @safe pure 602 version (mir_test) unittest 603 { 604 import mir.appender: scopedBuffer; 605 auto w = scopedBuffer!char; 606 w.print(["a": 1, "b": 2]); 607 assert(w.data == `["a": 1, "b": 2]` || w.data == `["b": 2, "a": 1]`, w.data); 608 } 609 610 /// Prints array 611 pragma(inline, false) 612 ref W print(C = char, W, T)(scope return ref W w, scope const(T)[] c) 613 if (isSomeChar!C && !isSomeChar!T) 614 { 615 enum C left = '['; 616 enum C right = ']'; 617 enum C[2] sep = ", "; 618 w.put(left); 619 bool first = true; 620 foreach (ref e; c) 621 { 622 if (!first) 623 w.printStaticString!C(sep); 624 first = false; 625 printElement!C(w, e); 626 } 627 w.put(right); 628 return w; 629 } 630 631 /// 632 @safe pure nothrow @nogc 633 version (mir_test) unittest 634 { 635 import mir.appender: scopedBuffer; 636 auto w = scopedBuffer!char; 637 string[2] array = ["a\na", "b"]; 638 assert(w.print(array[]).data == `["a\na", "b"]`, w.data); 639 } 640 641 /// Prints escaped character in the form `'c'`. 642 pragma(inline, false) 643 ref W print(C = char, W)(scope return ref W w, char c) 644 if (isSomeChar!C) 645 { 646 w.put('\''); 647 if (c >= 0x20) 648 { 649 if (c < 0x7F) 650 { 651 if (c == '\'' || c == '\\') 652 { 653 L: 654 w.put('\\'); 655 } 656 w.put(c); 657 } 658 else 659 { 660 M: 661 w.printStaticString!C(`\x`); 662 w.print!C(HexAddress!ubyte(cast(ubyte)c)); 663 } 664 } 665 else 666 { 667 switch(c) 668 { 669 case '\n': c = 'n'; goto L; 670 case '\r': c = 'r'; goto L; 671 case '\t': c = 't'; goto L; 672 case '\a': c = 'a'; goto L; 673 case '\b': c = 'b'; goto L; 674 case '\f': c = 'f'; goto L; 675 case '\v': c = 'v'; goto L; 676 case '\0': c = '0'; goto L; 677 default: goto M; 678 } 679 } 680 w.put('\''); 681 return w; 682 } 683 684 /// 685 @safe pure nothrow @nogc 686 version (mir_test) unittest 687 { 688 import mir.appender: scopedBuffer; 689 auto w = scopedBuffer!char; 690 assert(w 691 .print('\n') 692 .print('\'') 693 .print('a') 694 .print('\xF4') 695 .data == `'\n''\'''a''\xF4'`); 696 } 697 698 /// Prints some string 699 ref W print(C = char, W)(scope return ref W w, scope const(C)[] c) 700 if (isSomeChar!C) 701 { 702 w.put(c); 703 return w; 704 } 705 706 /// Prints integers 707 ref W print(C = char, W, I)(scope return ref W w, const I c) 708 if (isSomeChar!C && isIntegral!I && !is(I == enum)) 709 { 710 static if (I.sizeof == 16) 711 enum N = 39; 712 else 713 static if (I.sizeof == 8) 714 enum N = 20; 715 else 716 enum N = 10; 717 C[N + !__traits(isUnsigned, I)] buf = void; 718 static if (__traits(isUnsigned, I)) 719 auto n = printUnsignedToTail(c, buf); 720 else 721 auto n = printSignedToTail(c, buf); 722 w.put(buf[$ - n .. $]); 723 return w; 724 } 725 726 /// Prints floating point numbers 727 ref W print(C = char, W, T)(scope return ref W w, const T c, NumericSpec spec = NumericSpec.init) 728 if(isSomeChar!C && is(T == float) || is(T == double) || is(T == real)) 729 { 730 import mir.bignum.decimal; 731 auto decimal = Decimal!(T.mant_dig < 64 ? 1 : 2)(c); 732 decimal.toString(w, spec); 733 return w; 734 } 735 736 /// Human friendly precise output (default) 737 version(mir_bignum_test) 738 @safe pure nothrow @nogc 739 unittest 740 { 741 auto spec = NumericSpec.human; 742 stringBuf buffer; 743 744 void check(double num, string value) 745 { 746 assert(buffer.print(num, spec).data == value, buffer.data); buffer.reset; 747 } 748 749 check(-0.0, "-0.0"); 750 check(0.0, "0.0"); 751 check(-0.01, "-0.01"); 752 check(0.0125, "0.0125"); 753 check(0.000003, "0.000003"); 754 check(-3e-7, "-3e-7"); 755 check(123456.0, "123456.0"); 756 check(123456.1, "123456.1"); 757 check(12.3456, "12.3456"); 758 check(-0.123456, "-0.123456"); 759 check(0.1234567, "0.1234567"); 760 check(0.01234567, "0.01234567"); 761 check(0.001234567, "0.001234567"); 762 check(1.234567e-4, "1.234567e-4"); 763 check(-1234567.0, "-1.234567e+6"); 764 check(123456.7890123, "123456.7890123"); 765 check(1234567.890123, "1.234567890123e+6"); 766 check(1234567890123.0, "1.234567890123e+12"); 767 check(0.30000000000000004, "0.30000000000000004"); 768 check(0.030000000000000002, "0.030000000000000002"); 769 check(0.0030000000000000005, "0.0030000000000000005"); 770 check(3.0000000000000003e-4, "3.0000000000000003e-4"); 771 check(+double.nan, "nan"); 772 check(-double.nan, "nan"); 773 check(+double.infinity, "+inf"); 774 check(-double.infinity, "-inf"); 775 776 spec.separatorChar = ','; 777 778 check(-0.0, "-0.0"); 779 check(0.0, "0.0"); 780 check(-0.01, "-0.01"); 781 check(0.0125, "0.0125"); 782 check(0.000003, "0.000003"); 783 check(-3e-7, "-3e-7"); 784 check(123456.0, "123,456.0"); 785 check(123456e5, "12,345,600,000.0"); 786 check(123456.1, "123,456.1"); 787 check(12.3456, "12.3456"); 788 check(-0.123456, "-0.123456"); 789 check(0.1234567, "0.1234567"); 790 check(0.01234567, "0.01234567"); 791 check(0.001234567, "0.001234567"); 792 check(1.234567e-4, "0.0001234567"); 793 check(-1234567.0, "-1,234,567.0"); 794 check(123456.7890123, "123,456.7890123"); 795 check(1234567.890123, "1,234,567.890123"); 796 check(123456789012.0, "123,456,789,012.0"); 797 check(1234567890123.0, "1.234567890123e+12"); 798 check(0.30000000000000004, "0.30000000000000004"); 799 check(0.030000000000000002, "0.030000000000000002"); 800 check(0.0030000000000000005, "0.0030000000000000005"); 801 check(3.0000000000000003e-4, "0.00030000000000000003"); 802 check(3.0000000000000005e-6, "0.0000030000000000000005"); 803 check(3.0000000000000004e-7, "3.0000000000000004e-7"); 804 check(+double.nan, "nan"); 805 check(-double.nan, "nan"); 806 check(+double.infinity, "+inf"); 807 check(-double.infinity, "-inf"); 808 809 spec.separatorChar = '_'; 810 spec.separatorCount = 2; 811 check(123456e5, "1_23_45_60_00_00.0"); 812 813 spec.plus = true; 814 check(0.0125, "+0.0125"); 815 check(-0.0125, "-0.0125"); 816 } 817 818 /// Prints structs and unions 819 pragma(inline, false) 820 ref W print(C = char, W, T)(scope return ref W w, ref const T c) 821 if (isSomeChar!C && is(T == struct) || is(T == union) && !is(T : NumericSpec)) 822 { 823 static if (__traits(hasMember, T, "toString")) 824 { 825 static if (is(typeof(c.toString!C(w)))) 826 c.toString!C(w); 827 else 828 static if (is(typeof(c.toString(w)))) 829 c.toString(w); 830 else 831 static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) 832 c.toString((scope const(C)[] s) { w.put(s); }); 833 else 834 static if (is(typeof(w.put(c.toString)))) 835 w.put(c.toString); 836 else static assert(0, T.stringof ~ ".toString definition is wrong: 'const' qualifier may be missing."); 837 return w; 838 } 839 else 840 static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) 841 { 842 scope const(C)[] string_of_c = c; 843 return w.print!C(string_of_c); 844 } 845 else 846 static if (hasIterableLightConst!T) 847 { 848 enum C left = '['; 849 enum C right = ']'; 850 enum C[2] sep = ", "; 851 w.put(left); 852 bool first = true; 853 foreach (ref e; c.lightConst) 854 { 855 if (!first) 856 printStaticString!C(w, sep); 857 first = false; 858 print!C(w, e); 859 } 860 w.put(right); 861 return w; 862 } 863 else 864 { 865 enum C left = '('; 866 enum C right = ')'; 867 enum C[2] sep = ", "; 868 w.put(left); 869 foreach (i, ref e; c.tupleof) 870 { 871 static if (i) 872 w.printStaticString!C(sep); 873 print!C(w, e); 874 } 875 w.put(right); 876 return w; 877 } 878 } 879 880 /// ditto 881 // FUTURE: remove it 882 pragma(inline, false) 883 ref W print(C = char, W, T)(scope return ref W w, scope const T c) 884 if (isSomeChar!C && is(T == struct) || is(T == union)) 885 { 886 return print!(C, W, T)(w, c); 887 } 888 889 /// 890 @safe pure nothrow @nogc 891 version (mir_test) unittest 892 { 893 static struct A { scope void toString(C, W)(scope ref W w) const { w.put(C('a')); } } 894 static struct S { scope void toString(W)(scope ref W w) const { w.put("s"); } } 895 static struct D { scope void toString(Dg)(scope Dg sink) const { sink("d"); } } 896 static struct F { scope const(char)[] toString()() const return { return "f"; } } 897 static struct G { const(char)[] s = "g"; alias s this; } 898 899 import mir.appender: scopedBuffer; 900 auto w = scopedBuffer!char; 901 assert(stringBuf() << A() << S() << D() << F() << G() << getData == "asdfg"); 902 } 903 904 /// Prints classes and interfaces 905 pragma(inline, false) 906 ref W print(C = char, W, T)(scope return ref W w, scope const T c) 907 if (isSomeChar!C && is(T == class) || is(T == interface)) 908 { 909 enum C[4] Null = "null"; 910 static if (__traits(hasMember, T, "toString") || __traits(compiles, { scope const(C)[] string_of_c = c; })) 911 { 912 if (c is null) 913 w.printStaticString!C(Null); 914 else 915 static if (is(typeof(c.toString!C(w)))) 916 c.toString!C(w); 917 else 918 static if (is(typeof(c.toString(w)))) 919 c.toString(w); 920 else 921 static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) 922 c.toString((scope const(C)[] s) { w.put(s); }); 923 else 924 static if (is(typeof(w.put(c.toString)))) 925 w.put(c.toString); 926 else 927 static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) 928 { 929 scope const(C)[] string_of_c = c; 930 return w.print!C(string_of_c); 931 } 932 else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing."); 933 } 934 else 935 static if (hasIterableLightConst!T) 936 { 937 enum C left = '['; 938 enum C right = ']'; 939 enum C[2] sep = ", "; 940 w.put(left); 941 bool first = true; 942 foreach (ref e; c.lightConst) 943 { 944 if (!first) 945 w.printStaticString!C(sep); 946 first = false; 947 print!C(w, e); 948 } 949 w.put(right); 950 } 951 else 952 { 953 w.put(T.stringof); 954 } 955 return w; 956 } 957 958 /// 959 @safe pure nothrow 960 version (mir_test) unittest 961 { 962 static class A { void toString(C, W)(scope ref W w) const { w.put(C('a')); } } 963 static class S { void toString(W)(scope ref W w) const { w.put("s"); } } 964 static class D { void toString(Dg)(scope Dg sink) const { sink("d"); } } 965 static class F { const(char)[] toString()() const return { return "f"; } } 966 static class G { const(char)[] s = "g"; alias s this; } 967 968 assert(stringBuf() << new A() << new S() << new D() << new F() << new G() << getData == "asdfg"); 969 } 970 971 /// 972 ref W printStaticString(C, size_t N, W)(scope return ref W w, ref scope const C[N] c) 973 if (isSomeChar!C && is(C == char) || is(C == wchar) || is(C == dchar)) 974 { 975 static if (isFastBuffer!W) 976 { 977 enum immutable(ForeachType!(typeof(w.getBuffer(size_t.init))))[] value = c; 978 w.getStaticBuf!(value.length) = value; 979 w.advance(c.length); 980 } 981 else 982 { 983 w.put(c[]); 984 } 985 return w; 986 } 987 988 private template hasIterableLightConst(T) 989 { 990 static if (__traits(hasMember, T, "lightConst")) 991 { 992 enum hasIterableLightConst = isIterable!(ReturnType!((const T t) => t.lightConst)); 993 } 994 else 995 { 996 enum hasIterableLightConst = false; 997 } 998 } 999 1000 private ref C[N] getStaticBuf(size_t N, C, W)(scope return ref W w) 1001 if (isFastBuffer!W) 1002 { 1003 auto buf = w.getBuffer(N); 1004 assert(buf.length >= N); 1005 return buf.ptr[0 .. N]; 1006 } 1007 1008 private @trusted ref C[N] getStaticBuf(size_t N, C)(scope return ref C[] buf) 1009 { 1010 assert(buf.length >= N); 1011 return buf.ptr[0 .. N]; 1012 } 1013 1014 template isFastBuffer(W) 1015 { 1016 enum isFastBuffer = __traits(hasMember, W, "getBuffer") && __traits(hasMember, W, "advance"); 1017 } 1018 1019 /// 1020 ref W printZeroPad(C = char, W, I)(scope return ref W w, const I c, size_t minimalLength) 1021 if (isSomeChar!C && isIntegral!I && !is(I == enum)) 1022 { 1023 static if (I.sizeof == 16) 1024 enum N = 39; 1025 else 1026 static if (I.sizeof == 8) 1027 enum N = 20; 1028 else 1029 enum N = 10; 1030 C[N + !__traits(isUnsigned, I)] buf = void; 1031 static if (__traits(isUnsigned, I)) 1032 auto n = printUnsignedToTail(c, buf); 1033 else 1034 auto n = printSignedToTail(c, buf); 1035 sizediff_t zeros = minimalLength - n; 1036 1037 if (zeros > 0) 1038 { 1039 static if (!__traits(isUnsigned, I)) 1040 { 1041 if (c < 0) 1042 { 1043 n--; 1044 w.put(C('-')); 1045 } 1046 } 1047 do w.put(C('0')); 1048 while(--zeros); 1049 } 1050 w.put(buf[$ - n .. $]); 1051 return w; 1052 } 1053 1054 /// 1055 version (mir_test) unittest 1056 { 1057 import mir.appender; 1058 auto w = scopedBuffer!char; 1059 1060 w.printZeroPad(-123, 5); 1061 w.put(' '); 1062 w.printZeroPad(123, 5); 1063 1064 assert(w.data == "-0123 00123", w.data); 1065 } 1066 1067 /// 1068 size_t printBoolean(C)(bool c, ref C[5] buf) 1069 if(is(C == char) || is(C == wchar) || is(C == dchar)) 1070 { 1071 version(LDC) pragma(inline, true); 1072 if (c) 1073 { 1074 buf[0] = 't'; 1075 buf[1] = 'r'; 1076 buf[2] = 'u'; 1077 buf[3] = 'e'; 1078 return 4; 1079 } 1080 else 1081 { 1082 buf[0] = 'f'; 1083 buf[1] = 'a'; 1084 buf[2] = 'l'; 1085 buf[3] = 's'; 1086 buf[4] = 'e'; 1087 return 5; 1088 } 1089 }