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