1 /++ 2 +/ 3 module mir.format; 4 5 import std.traits; 6 7 import mir.format_impl; 8 9 /// 10 struct GetData {} 11 12 /// 13 enum getData = GetData(); 14 15 /++ 16 +/ 17 struct _stringBuf(C) 18 { 19 import mir.appender: ScopedBuffer; 20 21 /// 22 ScopedBuffer!C buffer; 23 24 /// 25 alias buffer this; 26 27 /// 28 mixin StreamFormatOp!C; 29 } 30 31 ///ditto 32 alias stringBuf = _stringBuf!char; 33 ///ditto 34 alias wstringBuf = _stringBuf!wchar; 35 ///ditto 36 alias dstringBuf = _stringBuf!dchar; 37 38 /++ 39 +/ 40 mixin template StreamFormatOp(C) 41 { 42 /// 43 ref typeof(this) opBinary(string op : "<<", T)(scope ref const T c) scope 44 { 45 return print!C(this, c); 46 } 47 48 /// 49 ref typeof(this) opBinary(string op : "<<", T)(const T c) scope 50 { 51 return print!C(this, c); 52 } 53 54 /// ditto 55 const(C)[] opBinary(string op : "<<", T : GetData)(const T c) scope 56 { 57 return buffer.data; 58 } 59 } 60 61 /// 62 @safe pure nothrow @nogc 63 unittest 64 { 65 auto name = "D"; 66 auto ver = 2.0; 67 assert(stringBuf() << "Hi " << name << ver << "!\n" << getData == "Hi D2!\n"); 68 } 69 70 /// 71 @safe pure nothrow @nogc 72 unittest 73 { 74 auto name = "D"w; 75 auto ver = 2; 76 assert(wstringBuf() << "Hi "w << name << ver << "!\n"w << getData == "Hi D2!\n"w); 77 } 78 79 /// 80 @safe pure nothrow @nogc 81 unittest 82 { 83 auto name = "D"d; 84 auto ver = 2; 85 assert(dstringBuf() << "Hi "d << name << ver << "!\n"d << getData == "Hi D2!\n"); 86 } 87 88 @safe pure nothrow @nogc 89 unittest 90 { 91 assert(stringBuf() << -1234567890 << getData == "-1234567890"); 92 } 93 94 // 16-bytes 95 /// C's compatible format specifier. 96 struct FormatSpec 97 { 98 /// 99 bool dash; 100 /// 101 bool plus; 102 /// 103 bool space; 104 /// 105 bool hash; 106 /// 107 bool zero; 108 /// 109 char format = 's'; 110 /// 111 char separator = '\0'; 112 /// 113 ubyte unitSize; 114 /// 115 int width; 116 /// 117 int precision = -1; 118 } 119 120 /++ 121 +/ 122 enum SwitchLU : bool 123 { 124 /// 125 lower, 126 /// 127 upper, 128 } 129 130 /++ 131 +/ 132 struct FormattedFloating(T) 133 if(is(T == float) || is(T == double) || is(T == real)) 134 { 135 /// 136 T value; 137 /// 138 FormatSpec spec; 139 140 /// 141 void toString(C = char, W)(scope ref W w) scope const 142 { 143 C[512] buf = void; 144 auto n = printFloatingPoint(value, spec, buf); 145 w.put(buf[0 .. n]); 146 } 147 } 148 149 /// ditto 150 FormattedFloating!T withFormat(T)(const T value, FormatSpec spec) 151 { 152 version(LDC) pragma(inline); 153 return typeof(return)(value, spec); 154 } 155 156 /++ 157 +/ 158 struct HexAddress(T) 159 if (isUnsigned!T && !is(T == enum)) 160 { 161 /// 162 T value; 163 /// 164 SwitchLU switchLU = SwitchLU.upper; 165 166 /// 167 void toString(C = char, W)(scope ref W w) scope const 168 { 169 enum N = T.sizeof * 2; 170 static if(isFastBuffer!W) 171 { 172 w.advance(printHexAddress(value, w.getBuffer(N).getStaticBuf!N, cast(bool) switchLU)); 173 } 174 else 175 { 176 C[N] buf = void; 177 printHexAddress(value, buf, cast(bool) switchLU); 178 w.put(buf[]); 179 } 180 } 181 } 182 183 /++ 184 Note: Non-ASCII Unicode characters are encoded as sequence of \xXX bytes. This may be fixed in the future. 185 +/ 186 pragma(inline, false) 187 ref W printEscaped(C = char, W)(scope return ref W w, scope const(char)[] str) 188 { 189 // TODO: replace with Mir implementation. 190 import std.uni: isGraphical; 191 w.put('\"'); 192 foreach (char c; str[]) 193 { 194 if (c >= 0x20) 195 { 196 if (c < 0x7F) 197 { 198 if (c == '\"' || c == '\\') 199 { 200 L: 201 w.put('\\'); 202 } 203 w.put(c); 204 } 205 else 206 { 207 M: 208 printStaticStringInternal!(C, "\\x")(w); 209 print!C(w, HexAddress!ubyte(cast(ubyte)c)); 210 } 211 } 212 else 213 { 214 switch(c) 215 { 216 case '\n': c = 'n'; goto L; 217 case '\r': c = 'r'; goto L; 218 case '\t': c = 't'; goto L; 219 case '\a': c = 'a'; goto L; 220 case '\b': c = 'b'; goto L; 221 case '\f': c = 'f'; goto L; 222 case '\v': c = 'v'; goto L; 223 case '\0': c = '0'; goto L; 224 default: goto M; 225 } 226 } 227 } 228 w.put('\"'); 229 return w; 230 } 231 232 /// 233 @safe pure nothrow @nogc 234 unittest 235 { 236 import mir.appender: ScopedBuffer; 237 ScopedBuffer!char w; 238 assert(w.printEscaped("Hi\t" ~ `"@nogc"`).data == `"Hi\t\"@nogc\""`, w.data); 239 w.reset; 240 assert(w.printEscaped("\xF3").data == `"\xF3"`, w.data); 241 } 242 243 /// 244 ref W printElement(C = char, W, T)(scope return ref W w, scope auto ref const T c) 245 { 246 static if (isSomeString!T) 247 { 248 return printEscaped!C(w, c); 249 } 250 else 251 { 252 return print!C(w, c); 253 } 254 } 255 256 /++ 257 Multiargument overload. 258 +/ 259 ref W print(C = char, W, Args...)(scope return ref W w, scope auto ref const Args args) 260 if (Args.length > 1) 261 { 262 foreach(i, ref c; args) 263 static if (i < Args.length - 1) 264 print!C(w, c); 265 else 266 return print!C(w, c); 267 } 268 269 /// 270 ref W print(C = char, W, T)(scope return ref W w, const T c) 271 if (is(T == enum)) 272 { 273 static assert(!is(OriginalType!T == enum)); 274 string s; 275 S: switch (c) 276 { 277 static foreach(member; __traits(allMembers, T)) 278 { 279 case __traits(getMember, T, member): 280 s = member; 281 break S; 282 } 283 default: 284 static immutable C[] str = T.stringof; 285 w.put(str[]); 286 w.put('('); 287 print(w, cast(OriginalType!T) c); 288 w.put(')'); 289 return w; 290 } 291 w.put(s); 292 return w; 293 } 294 295 /// 296 @safe pure nothrow @nogc 297 unittest 298 { 299 enum Flag 300 { 301 no, 302 yes, 303 } 304 305 import mir.appender: ScopedBuffer; 306 ScopedBuffer!char w; 307 w.print(Flag.yes); 308 assert(w.data == "yes", w.data); 309 } 310 311 /// ditto 312 ref W print(C = char, W)(scope return ref W w, bool c) 313 { 314 enum N = 5; 315 static if(isFastBuffer!W) 316 { 317 w.advance(printBoolean(c, w.getBuffer(N).getStaticBuf!N)); 318 } 319 else 320 { 321 C[N] buf = void; 322 auto n = printBoolean(c, buf); 323 w.put(buf[0 .. n]); 324 } 325 return w; 326 } 327 328 /// 329 @safe pure nothrow @nogc 330 unittest 331 { 332 import mir.appender: ScopedBuffer; 333 ScopedBuffer!char w; 334 assert(w.print(true).data == `true`, w.data); 335 w.reset; 336 assert(w.print(false).data == `false`, w.data); 337 } 338 339 /// ditto 340 pragma(inline, false) 341 ref W print(C = char, W, V, K)(scope return ref W w, scope const V[K] c) 342 { 343 enum C left = '['; 344 enum C right = ']'; 345 enum C[2] sep = ", "; 346 enum C[2] mid = ": "; 347 w.put(left); 348 bool first = true; 349 foreach (ref key, ref value; c) 350 { 351 if (!first) 352 printStaticStringInternal!(C, sep)(w); 353 first = false; 354 printElement!C(w, key); 355 printStaticStringInternal!(C, mid)(w); 356 printElement!C(w, value); 357 } 358 w.put(right); 359 return w; 360 } 361 362 /// 363 @safe pure 364 unittest 365 { 366 import mir.appender: ScopedBuffer; 367 ScopedBuffer!char w; 368 w.print(["a": 1, "b": 2]); 369 assert(w.data == `["a": 1, "b": 2]` || w.data == `["b": 2, "a": 1]`, w.data); 370 } 371 372 /// ditto 373 pragma(inline, false) 374 ref W print(C = char, W, T)(scope return ref W w, scope const(T)[] c) 375 if (!isSomeChar!T) 376 { 377 enum C left = '['; 378 enum C right = ']'; 379 enum C[2] sep = ", "; 380 w.put(left); 381 bool first = true; 382 foreach (ref e; c) 383 { 384 if (!first) 385 printStaticStringInternal!(C, sep)(w); 386 first = false; 387 printElement!C(w, e); 388 } 389 w.put(right); 390 return w; 391 } 392 393 /// 394 @safe pure nothrow @nogc 395 unittest 396 { 397 import mir.appender: ScopedBuffer; 398 ScopedBuffer!char w; 399 string[2] array = ["a\ta", "b"]; 400 assert(w.print(array[]).data == `["a\ta", "b"]`, w.data); 401 } 402 403 /// ditto 404 pragma(inline, false) 405 ref W print(C = char, W)(scope return ref W w, char c) 406 { 407 w.put('\''); 408 if (c >= 0x20) 409 { 410 if (c < 0x7F) 411 { 412 if (c == '\'' || c == '\\') 413 { 414 L: 415 w.put('\\'); 416 } 417 w.put(c); 418 } 419 else 420 { 421 M: 422 printStaticStringInternal!(C, "\\x")(w); 423 print!C(w, HexAddress!ubyte(cast(ubyte)c)); 424 } 425 } 426 else 427 { 428 switch(c) 429 { 430 case '\n': c = 'n'; goto L; 431 case '\r': c = 'r'; goto L; 432 case '\t': c = 't'; goto L; 433 case '\a': c = 'a'; goto L; 434 case '\b': c = 'b'; goto L; 435 case '\f': c = 'f'; goto L; 436 case '\v': c = 'v'; goto L; 437 case '\0': c = '0'; goto L; 438 default: goto M; 439 } 440 } 441 w.put('\''); 442 return w; 443 } 444 445 /// 446 @safe pure nothrow @nogc 447 unittest 448 { 449 import mir.appender: ScopedBuffer; 450 ScopedBuffer!char w; 451 assert(w 452 .print('\n') 453 .print('\'') 454 .print('a') 455 .print('\xF4') 456 .data == `'\n''\'''a''\xF4'`); 457 } 458 459 /// ditto 460 ref W print(C = char, W)(scope return ref W w, scope const(C)[] c) 461 if (isSomeChar!C) 462 { 463 w.put(c); 464 return w; 465 } 466 467 /// ditto 468 ref W print(C = char, W, I)(scope return ref W w, const I c) 469 if (isIntegral!I && !is(I == enum)) 470 { 471 static if (I.sizeof == 16) 472 enum N = 39; 473 else 474 static if (I.sizeof == 8) 475 enum N = 20; 476 else 477 enum N = 10; 478 C[N + !__traits(isUnsigned, I)] buf = void; 479 static if (__traits(isUnsigned, I)) 480 auto n = printUnsignedToTail(c, buf); 481 else 482 auto n = printSignedToTail(c, buf); 483 w.put(buf[$ - n .. $]); 484 return w; 485 } 486 487 /// ditto 488 ref W print(C = char, W, T)(scope return ref W w, const T c) 489 if(is(T == float) || is(T == double) || is(T == real)) 490 { 491 auto ff = FormattedFloating!T(c); 492 return print!C(w, ff); 493 } 494 495 /// ditto 496 pragma(inline, false) 497 ref W print(C = char, W, T)(scope return ref W w, scope ref const T c) 498 if (is(T == struct) || is(T == union)) 499 { 500 static if (__traits(hasMember, T, "toString")) 501 { 502 static if (is(typeof(c.toString!C(w)))) 503 c.toString!C(w); 504 else 505 static if (is(typeof(c.toString(w)))) 506 c.toString(w); 507 else 508 static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) 509 c.toString((scope const(C)[] s) { w.put(s); }); 510 else 511 static if (is(typeof(w.put(c.toString)))) 512 w.put(c.toString); 513 else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing."); 514 return w; 515 } 516 else 517 static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) 518 { 519 scope const(C)[] string_of_c = c; 520 return print(w, string_of_c); 521 } 522 else 523 static if (hasIterableLightConst!T) 524 { 525 enum C left = '['; 526 enum C right = ']'; 527 enum C[2] sep = ", "; 528 w.put(left); 529 bool first = true; 530 foreach (ref e; c.lightConst) 531 { 532 if (!first) 533 printStaticStringInternal!(C, sep)(w); 534 first = false; 535 print!C(w, e); 536 } 537 w.put(right); 538 return w; 539 } 540 else 541 { 542 enum C left = '('; 543 enum C right = ')'; 544 enum C[2] sep = ", "; 545 w.put(left); 546 foreach (i, ref e; c.tupleof) 547 { 548 static if (i) 549 printStaticStringInternal!(C, sep)(w); 550 print!C(w, e); 551 } 552 w.put(right); 553 return w; 554 } 555 } 556 557 /// ditto 558 // FUTURE: remove it 559 pragma(inline, false) 560 ref W print(C = char, W, T)(scope return ref W w, scope const T c) 561 if (is(T == struct) || is(T == union)) 562 { 563 return print!(C, W, T)(w, c); 564 } 565 566 /// 567 @safe pure nothrow @nogc 568 unittest 569 { 570 static struct A { scope const void toString(C, W)(scope ref W w) { w.put(C('a')); } } 571 static struct S { scope const void toString(W)(scope ref W w) { w.put("s"); } } 572 static struct D { scope const void toString(Dg)(scope Dg sink) { sink("d"); } } 573 static struct F { scope const const(char)[] toString()() return { return "f"; } } 574 static struct G { const(char)[] s = "g"; alias s this; } 575 576 import mir.appender: ScopedBuffer; 577 ScopedBuffer!char w; 578 assert(stringBuf() << A() << S() << D() << F() << G() << getData == "asdfg"); 579 } 580 581 /// ditto 582 pragma(inline, false) 583 ref W print(C = char, W, T)(scope return ref W w, scope const T c) 584 if (is(T == class) || is(T == interface)) 585 { 586 static if (__traits(hasMember, T, "toString") || __traits(compiles, { scope const(C)[] string_of_c = c; })) 587 { 588 if (c is null) 589 printStaticStringInternal!(C, "null")(w); 590 else 591 static if (is(typeof(c.toString!C(w)))) 592 c.toString!C(w); 593 else 594 static if (is(typeof(c.toString(w)))) 595 c.toString(w); 596 else 597 static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); })))) 598 c.toString((scope const(C)[] s) { w.put(s); }); 599 else 600 static if (is(typeof(w.put(c.toString)))) 601 w.put(c.toString); 602 else 603 static if (__traits(compiles, { scope const(C)[] string_of_c = c; })) 604 { 605 scope const(C)[] string_of_c = c; 606 return print(w, string_of_c); 607 } 608 else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing."); 609 } 610 else 611 static if (hasIterableLightConst!T) 612 { 613 enum C left = '['; 614 enum C right = ']'; 615 enum C[2] sep = ", "; 616 w.put(left); 617 bool first = true; 618 foreach (ref e; c.lightConst) 619 { 620 if (!first) 621 printStaticStringInternal!(C, sep)(w); 622 first = false; 623 print!C(w, e); 624 } 625 w.put(right); 626 } 627 else 628 { 629 w.put(T.stringof); 630 } 631 return w; 632 } 633 634 /// 635 @safe pure nothrow 636 unittest 637 { 638 static class A { scope const void toString(C, W)(scope ref W w) { w.put(C('a')); } } 639 static class S { scope const void toString(W)(scope ref W w) { w.put("s"); } } 640 static class D { scope const void toString(Dg)(scope Dg sink) { sink("d"); } } 641 static class F { scope const const(char)[] toString()() return { return "f"; } } 642 static class G { const(char)[] s = "g"; alias s this; } 643 644 import mir.appender: ScopedBuffer; 645 ScopedBuffer!char w; 646 assert(stringBuf() << new A() << new S() << new D() << new F() << new G() << getData == "asdfg"); 647 } 648 649 private template hasIterableLightConst(T) 650 { 651 static if (__traits(hasMember, T, "lightConst")) 652 { 653 enum hasIterableLightConst = isIterable!(ReturnType!((const T t) => t.lightConst)); 654 } 655 else 656 { 657 enum hasIterableLightConst = false; 658 } 659 } 660 661 private ref W printStaticStringInternal(C, immutable(C)[] c, W)(scope return ref W w) 662 if (C.sizeof * c.length <= 512) 663 { 664 static if (isFastBuffer!W) 665 { 666 printStaticString!c(w.getBuffer(c.length).getStaticBuf!(c.length)); 667 w.advance(c.length); 668 } 669 else 670 static if (c.length <= 4) 671 { 672 static foreach(i; 0 .. c.length) 673 w.put(c[i]); 674 } 675 else 676 { 677 w.put(c[]); 678 } 679 return w; 680 } 681 682 private @trusted ref C[N] getStaticBuf(size_t N, C)(scope return ref C[] buf) 683 { 684 assert(buf.length >= N); 685 return buf.ptr[0 .. N]; 686 } 687 688 template isFastBuffer(W) 689 { 690 enum isFastBuffer = __traits(hasMember, W, "getBuffer") && __traits(hasMember, W, "advance"); 691 } 692 693 /// 694 ref W printZeroPad(C = char, W, I)(scope return ref W w, const I c, size_t minimalLength) 695 if (isIntegral!I && !is(I == enum)) 696 { 697 static if (I.sizeof == 16) 698 enum N = 39; 699 else 700 static if (I.sizeof == 8) 701 enum N = 20; 702 else 703 enum N = 10; 704 C[N + !__traits(isUnsigned, I)] buf = void; 705 static if (__traits(isUnsigned, I)) 706 auto n = printUnsignedToTail(c, buf); 707 else 708 auto n = printSignedToTail(c, buf); 709 sizediff_t zeros = minimalLength - n; 710 711 if (zeros > 0) 712 { 713 static if (!__traits(isUnsigned, I)) 714 { 715 if (c < 0) 716 { 717 n--; 718 w.put(C('-')); 719 } 720 } 721 do w.put(C('0')); 722 while(--zeros); 723 } 724 w.put(buf[$ - n .. $]); 725 return w; 726 } 727 728 /// 729 unittest 730 { 731 import mir.appender; 732 ScopedBuffer!char w; 733 734 w.printZeroPad(-123, 5); 735 w.put(' '); 736 w.printZeroPad(123, 5); 737 738 assert(w.data == "-0123 00123", w.data); 739 } 740 741 /// 742 size_t printBoolean(C)(bool c, ref C[5] buf) 743 if(is(C == char) || is(C == wchar) || is(C == dchar)) 744 { 745 version(LDC) pragma(inline, true); 746 if (c) 747 { 748 buf[0] = 't'; 749 buf[1] = 'r'; 750 buf[2] = 'u'; 751 buf[3] = 'e'; 752 return 4; 753 } 754 else 755 { 756 buf[0] = 'f'; 757 buf[1] = 'a'; 758 buf[2] = 'l'; 759 buf[3] = 's'; 760 buf[4] = 'e'; 761 return 5; 762 } 763 } 764 765 /// 766 size_t printStaticString(string str, C)(scope ref C[str.length] buf) 767 if((is(C == char) || is(C == wchar) || is(C == dchar)) && (C[str.length]).sizeof <= 512) 768 { 769 version(LDC) pragma(inline, true); 770 static foreach (i, e; str) buf[i] = e; 771 return buf.length; 772 } 773 774 /// ditto 775 size_t printStaticString(wstring str, C)(scope ref C[str.length] buf) 776 if((is(C == wchar) || is(C == dchar)) && (C[str.length]).sizeof <= 512) 777 { 778 version(LDC) pragma(inline, true); 779 static foreach (i, e; str) buf[i] = e; 780 return buf.length; 781 } 782 783 /// ditto 784 size_t printStaticString(dstring str)(scope ref dchar[str.length] buf) 785 if((dchar[str.length]).sizeof <= 512) 786 { 787 version(LDC) pragma(inline, true); 788 static foreach (i, e; str) buf[i] = e; 789 return buf.length; 790 }