1 /******************************************************************************* 2 3 Lightweight, memory friendly string formatting module 4 5 This module provides 4 possible semantics: 6 - For pedestrian usage which doesn't care about allocations, see `format` 7 - For allocation-friendly semantic where the data is output either to 8 a sink or to a `ref char[]`, see the `sformat` overloads 9 - To ensure absolutely no allocation happens, see `snformat` 10 11 Users of Phobos' `std.format` will find many similarities in the API: 12 - `format` is equivalent to `std.format.format` 13 - `snformat` is equivalent to `std.format.sformat` 14 - `sformat` is roughly equivalent to `std.format.formattedWrite` 15 16 Format_specifier: 17 18 The Formatter uses a format specification similar to C#/.NET over 19 the traditional `printf` style. 20 As a result, the most simple usage is to call: 21 --- 22 format("This value will be default formatted: {}", value); 23 --- 24 25 More specific formatting options are however available. 26 27 The format specifier is defined as follows: 28 29 '{'[INDEX][WIDTH_CHAR[ALIGN_LEFT_CHAR][ALIGN_WIDTH][' '*]][':'][FORMAT_STRING]'}' 30 31 In more details: 32 - `INDEX` is the positive, decimal and 0 based index of the argument 33 to format. 34 - `WIDTH_CHAR` is either ',' (comma) if a minimum width is requested, 35 in which case the output will be padded with spaces, 36 or '.' if a maximum width is requested, in which case the output will be 37 cropped and cropping will be noted by the presence of "..." 38 - `ALIGN_LEFT_CHAR` is '-'. If present, padding / cropping will be done 39 on the left side of the string, otherwise it will be the right side 40 This can only be used after a `WIDTH_CHAR`. 41 - `ALIGN_WIDTH` is the positive, decimal width the argument should have. 42 - ':' can optionally be used to separate the index / width specification 43 from the format string. So `{}`, `{:}` and `{0:X}` are all valid. 44 - `FORMAT_STRING` is an argument-defined format string 45 46 Format_string: 47 48 The format string defines how the argument will be formatted, and thus 49 is dependent on the argument type. 50 51 Currently the following formatting strings are supported: 52 - 'X' or 'x' are used for hexadecimal formatting of the output. 53 'X' outputs uppercase, 'x' will output lowercase. 54 Applies to integer types and pointers, and will also output 55 the hexadecimal. 56 - 'e' for floating point type will display exponential notation. 57 Using a number will set the precision, so for example the string 58 `"{:2}"` with argument `0.123456` will output `"0.12"` 59 Finally, '.' will prevent padding. 60 Unrecognized formatting strings should be ignored. For composed types, 61 the formatting string is passed along, so using `X` on a `struct` or an 62 array will display any integer / pointer members in uppercase hexadecimal. 63 64 Copyright: 65 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 66 Some parts (marked explicitly) copyright Kris and/or Larsivi. 67 All rights reserved. 68 69 License: 70 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 71 See LICENSE_TANGO.txt for details. 72 73 *******************************************************************************/ 74 75 module ocean.text.convert.Formatter; 76 77 import ocean.meta.types.Qualifiers; 78 import ocean.core.Buffer; 79 import Integer = ocean.text.convert.Integer_tango; 80 import Float = ocean.text.convert.Float; 81 import UTF = ocean.text.convert.Utf; 82 import ocean.core.Verify; 83 84 import ocean.meta.traits.Basic; 85 import ocean.meta.types.Typedef; 86 import ocean.meta.types.Arrays; 87 import ocean.meta.codegen.Identifier; 88 89 /******************************************************************************* 90 91 Type of 'sink' that can be passed to `format`, that will just format a 92 string into the provided sink. 93 94 *******************************************************************************/ 95 96 public alias void delegate(in cstring) FormatterSink; 97 98 /******************************************************************************* 99 100 Internal sink type that wraps the user-provided one and takes care 101 of cropping and width 102 103 This sink expects to receive a full element as the first parameter, 104 in other words, the full chunk of text that needs a fixed size. 105 This is why one cannot format a whole aggregate (struct, arrays etc.), but 106 only individual elements. 107 108 *******************************************************************************/ 109 110 private alias void delegate(cstring, ref const(FormatInfo)) ElemSink; 111 112 113 /******************************************************************************* 114 115 Formats an input string into a newly-allocated string and returns it 116 117 Params: 118 fmt = Format string to use 119 args = Variadic arguments to format according to `fmt` 120 121 Returns: 122 A newly allocated, immutable formatted string 123 124 *******************************************************************************/ 125 126 public istring format (Args...) (cstring fmt, Args args) 127 { 128 import ocean.core.TypeConvert : assumeUnique; 129 130 mstring buffer; 131 132 scope FormatterSink sink = (in cstring s) 133 { 134 buffer ~= s; 135 }; 136 137 sformat(sink, fmt, args); 138 return assumeUnique(buffer); 139 } 140 141 142 /******************************************************************************* 143 144 Append the processed (formatted) input onto the end of the provided buffer 145 146 Params: 147 buffer = The buffer to which to append the formatted string; its 148 capacity will be increased if necessary 149 fmt = Format string to use 150 args = Variadic arguments to format according to `fmt` 151 152 Returns: 153 A reference to `buffer` 154 155 *******************************************************************************/ 156 157 public mstring sformat (Args...) (ref mstring buffer, cstring fmt, Args args) 158 { 159 scope FormatterSink sink = (in cstring s) 160 { 161 buffer ~= s; 162 }; 163 sformat(sink, fmt, args); 164 return buffer; 165 } 166 167 /// ditto 168 public mstring sformat (Args...) (ref Buffer!(char) buffer, cstring fmt, Args args) 169 { 170 scope FormatterSink sink = (in cstring s) 171 { 172 buffer ~= s; 173 }; 174 sformat(sink, fmt, args); 175 return buffer[]; 176 } 177 178 /******************************************************************************* 179 180 Write the processed (formatted) input into a fixed-length buffer 181 182 This function will not perform any allocation. 183 If the output does not fit in `buffer`, the extra output will simply 184 be discarded. 185 186 Params: 187 buffer = The buffer to write the formatted string into. 188 Unlike the sformat overloads, the buffer won't be extended. 189 This leads to a slightly different semantic for this 190 buffer (the others are only appended to, this one is 191 written to). 192 fmt = Format string to use 193 args = Variadic arguments to format according to `fmt` 194 195 Returns: 196 A reference to `buffer` 197 198 *******************************************************************************/ 199 200 public mstring snformat (Args...) (mstring buffer, cstring fmt, Args args) 201 { 202 size_t start; 203 204 scope FormatterSink sink = (in cstring s) 205 { 206 size_t left = buffer.length - start; 207 size_t wsize = left <= s.length ? left : s.length; 208 if (wsize > 0) 209 buffer[start .. start + wsize] = s[0 .. wsize]; 210 start += wsize; 211 }; 212 213 sformat(sink, fmt, args); 214 return buffer[0 .. start]; 215 } 216 217 /******************************************************************************* 218 219 Send the processed (formatted) input into a sink 220 221 Params: 222 sink = A delegate that will be called, possibly multiple 223 times, with a portion of the result string 224 fmt = Format string to use 225 args = Variadic arguments to format according to fmt 226 227 Returns: 228 If formatting was successful, returns `true`, `false` otherwise. 229 230 *******************************************************************************/ 231 232 public bool sformat (Args...) (scope FormatterSink sink, cstring fmt, Args args) 233 { 234 FormatInfo info; 235 size_t nextIndex; 236 237 // A delegate to write elements according to the FormatInfo 238 scope elemSink = (cstring str, ref const(FormatInfo) f) 239 { 240 widthSink(sink, str, f); 241 }; 242 243 // Main loop 244 while (fmt.length) 245 { 246 info = consume(sink, fmt); 247 248 if (info.flags & Flags.Error) 249 return false; 250 251 if (info.flags & Flags.Format) 252 { 253 // Handle index, the single source of pain 254 if (info.flags & Flags.Index) 255 nextIndex = info.index + 1; 256 else 257 info.index = nextIndex++; 258 259 /* 260 * The foreach within the switch is executed at compile time 261 * It allows accessing a compile-time known parameter (and most 262 * importantly, its type) using a runtime index. 263 * It is basically generating a jump table, which is exactly 264 * what the codegen will produce. 265 * Note that we need to use the break to label feature as 266 * `break` would otherwise break out of the static foreach 267 * (even if that doesn't make sense), and thus, execute the 268 * `default` statement in every case (!) 269 */ 270 JT: switch (info.index) 271 { 272 // NOTE: We could static foreach over `args` (the values) 273 // instead of `Args` (the type) but it currently triggers 274 // a DMD bug, and as a result a deprecation message: 275 // https://issues.dlang.org/show_bug.cgi?id=16521 276 foreach (idx, Tunused; Args) 277 { 278 case idx: 279 handle(args[idx], info, sink, elemSink); 280 break JT; 281 } 282 283 default: 284 sink("{invalid index}"); 285 } 286 } 287 } 288 return true; 289 } 290 291 /******************************************************************************* 292 293 A function that writes to a `Sink` according to the width limits 294 295 Params: 296 sink = Sink to write to 297 str = String to write to the sink 298 f = FormatInfo object from which to read the width and flags 299 300 *******************************************************************************/ 301 302 private void widthSink (scope FormatterSink sink, cstring str, ref const(FormatInfo) f) 303 { 304 if (f.flags & Flags.Width) 305 { 306 // "{.4}", "Hello" gives "Hell..." 307 // "{.-4}", "Hello" gives "...ello" 308 if (f.flags & Flags.Crop) 309 { 310 if (f.flags & Flags.AlignLeft) 311 { 312 if (str.length > f.width) 313 { 314 sink("..."); 315 sink(str[$ - f.width .. $]); 316 } 317 else 318 sink(str); 319 } 320 else 321 { 322 verify((f.flags & Flags.AlignRight) != 0); 323 if (str.length > f.width) 324 { 325 sink(str[0 .. f.width]); 326 sink("..."); 327 } 328 else 329 sink(str); 330 } 331 } 332 else if (f.width > str.length) 333 { 334 if (f.flags & Flags.AlignLeft) 335 { 336 sink(str); 337 writeSpace(sink, f.width - str.length); 338 } 339 else 340 { 341 verify((f.flags & Flags.AlignRight) != 0); 342 writeSpace(sink, f.width - str.length); 343 sink(str); 344 } 345 } 346 // Else fall back to just writing the string 347 else 348 { 349 sink(str); 350 } 351 } 352 else 353 sink(str); 354 } 355 356 357 /******************************************************************************* 358 359 Converts a value of a given type to its string representation 360 361 Params: 362 T = Type of the argument to convert 363 v = Value of the argument to convert 364 f = Format information gathered from parsing 365 sf = Fragment sink, to emit a part of the text without alignment 366 se = Element sink, to emit a single element with alignment 367 368 *******************************************************************************/ 369 370 private void handle (T) (T v, FormatInfo f, scope FormatterSink sf, scope ElemSink se) 371 { 372 /** The order in which the following conditions are applied matters. 373 * Explicit type checks (e.g. associative array, or `is(T == V)`) 374 * should go first as they are unambiguous. 375 * Multiple conditions could be matched by the same type. 376 */ 377 378 // `typeof(null)` matches way too many things 379 static if (is(T == typeof(null))) 380 se("null", f); 381 382 383 // Pretty print enum 384 // Note that switch is only available for string and integer based enums. 385 // However, since it expands to a jump table for integers and a binary 386 // search for strings, we still want to special case it. 387 else static if (is(T V == enum) && canSwitchOn!T) 388 { 389 sw: switch (v) 390 { 391 foreach (member; __traits(allMembers, T)) 392 { 393 case mixin("T." ~ member): 394 sf(T.stringof); 395 sf("."); 396 sf(member); 397 break sw; 398 } 399 default : 400 sf("cast("); 401 sf(T.stringof); 402 sf(") "); 403 handle!(V)(v, f, sf, se); 404 } 405 } 406 407 // Pretty print enum for non-integer, non-string base types 408 // This branch should be rarely, if ever, used. 409 else static if (is(T E == enum)) 410 { 411 foreach (member; __traits(allMembers, T)) 412 { 413 if (v == mixin("T." ~ member)) 414 { 415 sf(T.stringof); 416 sf("."); 417 sf(member); 418 return; 419 } 420 } 421 422 sf("cast("); 423 sf(T.stringof); 424 sf(") "); 425 handle!(E)(v, f, sf, se); 426 } 427 428 // Delegate / Function pointers 429 else static if (is(T == delegate)) 430 { 431 sf(T.stringof ~ ": { funcptr: "); 432 writePointer(v.funcptr, f, se); 433 sf(", ptr: "); 434 writePointer(v.ptr, f, se); 435 sf(" }"); 436 } 437 else static if (is(T U == return)) 438 { 439 sf(T.stringof ~ ": "); 440 writePointer(v, f, se); 441 } 442 443 // Pointers need to be at the top because `(int*).min` compiles 444 // and hence would match the integer rules 445 // In addition, thanks to automatic dereferencing, checks such as 446 // `v.toString()` and `T.IsTypedef` pass when `typeof(v)` is an `Object*`, 447 // and when `T` is a pointer to a typedef. 448 else static if (is (T P == P*)) 449 writePointer(v, f, se); 450 451 /** D1 + D2 support of typedef 452 * Note that another approach would be to handle `struct` at the very 453 * last stage and relying on `alias this` for implicit conversions. 454 * However this is not a reliable approach, as having an `alias this` 455 * doesn't mean that it will be a typedef, and a user might want the struct 456 * to be printed instead of the first matching `alias this`. 457 * In fact, there is no way to semantically express subtyping, 458 * but only the means to perform it. 459 * This could be solved later with a UDA, but it's at best a workaround. 460 */ 461 else static if (isTypedef!(T)) 462 handle!(TypedefBaseType!(T))(v, f, sf, se); 463 464 // toString hook: Give priority to the non-allocating one 465 // Note: sink `toString` overload should take a `scope` delegate 466 else static if (is(typeof(v.toString(sf)))) 467 nullWrapper(&v, 468 v.toString((in cstring e) { se(e, f); }), 469 se("null", f)); 470 else static if (is(typeof(v.toString()) : cstring)) 471 nullWrapper(&v, se(v.toString(), f), se("null", f)); 472 else static if (is(T == interface)) 473 handle!(Object)(cast(Object) v, f, sf, se); 474 475 // Aggregate should be matched before basic type to avoid 476 // `alias this` kicking in. See typedef support for more info. 477 else static if (is (T == struct)) 478 { 479 Flags old = f.flags; 480 f.flags |= Flags.Nested; 481 foreach (idx, ref m; v.tupleof) 482 { 483 static if (idx == 0) 484 sf("{ " ~ identifier!(T.tupleof[idx]) ~ ": "); 485 else 486 sf(", " ~ identifier!(T.tupleof[idx]) ~ ": "); 487 488 // A bit ugly but it makes string much more readable 489 handle(m, f, sf, se); 490 } 491 sf(v.tupleof.length ? " }" : "{ empty struct }"); 492 f.flags = old; 493 } 494 495 // Bool 496 else static if (is (Unqual!(T) == bool)) 497 se(v ? "true" : "false", f); 498 499 // Floating point values - Explicitly typed because we don't want 500 // to support imaginary and complex FP types 501 else static if (is(Unqual!(T) == float) || is(Unqual!(T) == double) 502 || is(Unqual!(T) == real)) 503 { 504 char[T.sizeof * 8] buff = void; 505 se(Float.format(buff, v, f.format), f); 506 } 507 508 // Associative array cannot be matched by IsExp in D1 509 else static if (isArrayType!(T) == ArrayKind.Associative) 510 { 511 bool started; 512 Flags old = f.flags; 513 f.flags |= Flags.Nested; 514 foreach (key, ref val; v) 515 { 516 if (!started) 517 { 518 started = true; 519 sf("[ "); 520 } 521 else 522 sf(", "); 523 524 handle(key, f, sf, se); 525 sf(": "); 526 handle(val, f, sf, se); 527 } 528 if (started) 529 sf(" ]"); 530 else // Empty or null 531 sf("[:]"); 532 f.flags = old; 533 } 534 535 // UTF-8 strings and chars (UTF-16 and UTF-32 unsupported) 536 else static if (is(T : cstring) 537 || is(T : const(wchar)[]) 538 || is(T : const(dchar)[])) 539 { 540 if (f.flags & Flags.Nested) sf(`"`); 541 UTF.toString(v, (cstring val) { se(val, f); return val.length; }); 542 if (f.flags & Flags.Nested) sf(`"`); 543 } 544 else static if (is(typeof((&v)[0 .. 1]) : cstring) 545 || is(typeof((&v)[0 .. 1]) : const(wchar)[]) 546 || is(typeof((&v)[0 .. 1]) : const(dchar)[])) 547 { 548 T[3] b = [ '\'', v, '\'' ]; 549 if (f.flags & Flags.Nested) 550 UTF.toString(b, (cstring val) { se(val, f); return val.length; }); 551 else 552 UTF.toString(b[1 .. 2], (cstring val) { se(val, f); return val.length; }); 553 } 554 555 // Signed integer 556 else static if (is(typeof(T.min)) && T.min < 0) 557 { 558 // Needs to support base 2 at most, plus an optional prefix 559 // of 2 chars max 560 char[T.sizeof * 8 + 2] buff = void; 561 se(Integer.format(buff, v, f.format), f); 562 } 563 // Unsigned integer 564 else static if (is(typeof(T.min)) && T.min == 0) 565 { 566 // Needs to support base 2 at most, plus an optional prefix of 2 chars 567 // max 568 char[T.sizeof * 8 + 2] buff = void; 569 se(Integer.format(buff, v, (f.format.length ? f.format : "u")), f); 570 } 571 572 // Arrays (dynamic and static) 573 else static if (isBasicArrayType!(T)) 574 { 575 alias ElementTypeOf!(T) A; 576 577 static if (is(Unqual!(A) == void)) 578 handle!(const(ubyte)[])(cast(const(ubyte)[]) v, f, sf, se); 579 else 580 { 581 sf("["); 582 if (v.length) 583 { 584 Flags old = f.flags; 585 f.flags |= Flags.Nested; 586 587 handle!(A)(v[0], f, sf, se); 588 foreach (idx, ref e; v[1 .. $]) 589 { 590 sf(", "); 591 handle!(A)(e, f, sf, se); 592 } 593 594 f.flags = old; 595 } 596 sf("]"); 597 } 598 } 599 600 else 601 static assert (0, "Type unsupported by ocean.text.convert.Formatter: " 602 ~ T.stringof); 603 } 604 605 606 /******************************************************************************* 607 608 Wrapper to call `toString` methods after checking for `null` 609 610 Before calling `toString`, a `null` check should be performed. 611 However, it shouldn't always be performed, as `T` might not be a ref type. 612 613 Params: 614 v = Object to check, might be a reference type 615 expr = Expression to call if `v` is not null or not a ref type 616 onNull = Expression to call if `v` is null 617 618 Returns: 619 Value returned by either `expr` or `onNull`, if any. 620 621 *******************************************************************************/ 622 623 private RetType nullWrapper (T, RetType) (T* v, lazy RetType expr, 624 lazy RetType onNull) 625 { 626 static if (is(typeof(*v is null))) 627 if (*v is null) 628 return onNull; 629 return expr; 630 } 631 632 633 /******************************************************************************* 634 635 Consumes the format string until a format specifier is found, 636 then returns information about that format specifier 637 638 Note: 639 This function iterates over 'char', and is *NOT* Unicode-correct. 640 However, neither is the original Tango one. 641 642 Params: 643 sink = An output delegate to write to 644 fmt = The format string to consume 645 646 Copyright: 647 This function was adapted from 648 `tango.text.convert.Layout.Layout.consume`. 649 The original was (c) Kris 650 651 Returns: 652 A description of the format specification, see `FormatInfo`'s 653 definition for more details 654 655 *******************************************************************************/ 656 657 private FormatInfo consume (scope FormatterSink sink, ref cstring fmt) 658 { 659 FormatInfo ret; 660 auto s = fmt.ptr; 661 auto end = s + fmt.length; 662 663 while (s < end && *s != '{') 664 ++s; 665 666 // Write all non-formatted content 667 sink(forwardSlice(fmt, s)); 668 669 if (s == end) 670 return ret; 671 672 // Tango format allowed escaping braces: "{{0}" would be turned 673 // into "{0}" 674 if (*++s == '{') 675 { 676 // Will always return "{{", but we only need the first char 677 sink(forwardSlice(fmt, s + 1)[0 .. 1]); 678 return ret; 679 } 680 681 ret.flags |= Flags.Format; 682 683 // extract index 684 if (readNumber(ret.index, s)) 685 ret.flags |= Flags.Index; 686 687 s = skipSpace(s, end); 688 689 // has minimum or maximum width? 690 if (*s == ',' || *s == '.') 691 { 692 if (*s == '.') 693 ret.flags |= Flags.Crop; 694 695 s = skipSpace(++s, end); 696 if (*s == '-') 697 { 698 ret.flags |= Flags.AlignLeft; 699 ++s; 700 } 701 else 702 ret.flags |= Flags.AlignRight; 703 704 // Extract expected width 705 if (readNumber(ret.width, s)) 706 ret.flags |= Flags.Width; 707 708 // skip spaces 709 s = skipSpace(s, end); 710 } 711 712 // Finally get the format string, if any 713 // e.g. for `{5:X} that would be 'X' 714 if (*s == ':') 715 ++s; 716 if (s < end) 717 { 718 auto fs = s; 719 // eat everything up to closing brace 720 while (s < end && *s != '}') 721 ++s; 722 ret.format = fs[0 .. cast(size_t) (s - fs)]; 723 } 724 725 forwardSlice(fmt, s); 726 727 // When the user-provided string is e.g. "Foobar {0:X" 728 if (*s != '}') 729 { 730 sink("{missing closing '}'}"); 731 ret.flags |= Flags.Error; 732 return ret; 733 } 734 735 // Eat the closing bracket ('}') 736 fmt = fmt[1 .. $]; 737 738 return ret; 739 } 740 741 742 /******************************************************************************* 743 744 Helper function to advance a slice to a pointer 745 746 Params: 747 s = Slice to advance 748 p = Internal pointer to 's' 749 750 Returns: 751 A slice to the data that was consumed (e.g. s[0 .. s.ptr - p]) 752 753 *******************************************************************************/ 754 755 private cstring forwardSlice (ref cstring s, const(char)* p) 756 out (ret) 757 { 758 assert(s.ptr == p); 759 assert(ret.ptr + ret.length == p); 760 } 761 do 762 { 763 verify(s.ptr <= p); 764 verify(s.ptr + s.length >= p); 765 766 cstring old = s.ptr[0 .. cast(size_t) (p - s.ptr)]; 767 s = s[old.length .. $]; 768 return old; 769 } 770 771 /******************************************************************************* 772 773 Helper function to advance a pointer to the next non-space character 774 775 Params: 776 s = Pointer to iterate 777 end = Pointer to the end of 's' 778 779 Returns: 780 's' pointing to a non-space character or 'end' 781 782 *******************************************************************************/ 783 784 private const(char)* skipSpace (const(char)* s, const(char)* end) 785 { 786 while (s < end && *s == ' ') 787 ++s; 788 return s; 789 } 790 791 /******************************************************************************* 792 793 Helper function to write a space to a sink 794 795 Allows one to pad a string. Writes in chunk of 32 chars at most. 796 797 Params: 798 s = Sink to write to 799 n = Amount of spaces to write 800 801 *******************************************************************************/ 802 803 private void writeSpace (scope FormatterSink s, size_t n) 804 { 805 static immutable istring Spaces32 = " "; 806 807 // Make 'n' a multiple of Spaces32.length (32) 808 s(Spaces32[0 .. n % Spaces32.length]); 809 n -= n % Spaces32.length; 810 811 verify((n % Spaces32.length) == 0); 812 813 while (n != 0) 814 { 815 s(Spaces32); 816 n -= Spaces32.length; 817 } 818 } 819 820 /******************************************************************************* 821 822 Helper function to read a number while consuming the input 823 824 Params: 825 f = Value in which to store the number 826 s = Pointer to consume / read from 827 828 Copyright: 829 Originally from `tango.text.convert.Layout`. 830 Copyright Kris 831 832 Returns: 833 `true` if a number was read, `false` otherwise 834 835 *******************************************************************************/ 836 837 private bool readNumber (out size_t f, ref const(char)* s) 838 { 839 if (*s >= '0' && *s <= '9') 840 { 841 do 842 f = f * 10 + *s++ -'0'; 843 while (*s >= '0' && *s <= '9'); 844 return true; 845 } 846 return false; 847 } 848 849 850 /******************************************************************************* 851 852 Write a pointer to the sink 853 854 Params: 855 v = Pointer to write 856 f = Format information gathered from parsing 857 se = Element sink, to emit a single element with alignment 858 859 *******************************************************************************/ 860 861 private void writePointer (in void* v, ref FormatInfo f, scope ElemSink se) 862 { 863 alias void* T; 864 865 enum l = (T.sizeof * 2); 866 enum defaultFormat = "X" ~ l.stringof ~ "#"; 867 868 if (v is null) 869 se("null", f); 870 else 871 { 872 // Needs to support base 2 at most, plus an optional prefix 873 // of 2 chars max 874 char[T.sizeof * 8 + 2] buff = void; 875 se(Integer.format(buff, cast(ptrdiff_t) v, 876 (f.format.length ? f.format : defaultFormat)), f); 877 } 878 } 879 880 881 /******************************************************************************* 882 883 Represent all possible boolean values that can be set in FormatInfo.flags 884 885 *******************************************************************************/ 886 887 private enum Flags : ubyte 888 { 889 None = 0x00, /// Default 890 Format = 0x01, /// There was a formatting string (even if empty) 891 Error = 0x02, /// An error happened during formatting, bail out 892 AlignLeft = 0x04, /// Left alignment requested (via ',-' or '.-') 893 AlignRight = 0x08, /// Right alignment requested (via ',' or '.') 894 Crop = 0x10, /// Crop to width (via '.') 895 Index = 0x20, /// An index was explicitly provided 896 Width = 0x40, /// A width was explicitly provided 897 Nested = 0x80, /// We are formatting something nested 898 /// (i.e. in an aggregate type or an array) 899 } 900 901 /******************************************************************************* 902 903 Internal struct to hold information about the format specification 904 905 *******************************************************************************/ 906 907 private struct FormatInfo 908 { 909 /*************************************************************************** 910 911 Format string, might be empty 912 913 E.g. "{}" gives an empty `format`, and so does "{0}" 914 The string "{d}" and "{0,10:f}" give 'd' and 'f', respectively. 915 916 ***************************************************************************/ 917 918 public cstring format; 919 920 /*************************************************************************** 921 922 Explicitly requested index to use, only meaningful if flags.Index is set 923 924 ***************************************************************************/ 925 926 public size_t index; 927 928 /*************************************************************************** 929 930 Output width explicitly requested, only meaningful if flags.Width is set 931 932 ***************************************************************************/ 933 934 public size_t width; 935 936 /*************************************************************************** 937 938 Grab bag of boolean values, check `Flags` enum for complete doc 939 940 ***************************************************************************/ 941 942 public Flags flags; 943 } 944 945 /// Returns: Whether or not `T` can be `switch`ed on 946 private template canSwitchOn (T) 947 { 948 enum canSwitchOn = is(typeof(() { switch (T.init) { default: break; }})); 949 } 950 951 unittest 952 { 953 static assert(canSwitchOn!string); 954 static assert(canSwitchOn!(immutable int)); 955 static assert(!canSwitchOn!(real)); 956 }