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 string 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 { 468 // Some `toString` signatures expect a `ref` sink 469 scope sinkRef = (in cstring e) { se(e, f); }; 470 nullWrapper(&v, 471 v.toString(sinkRef), 472 se("null", f)); 473 } 474 else static if (is(typeof(v.toString()) : cstring)) 475 nullWrapper(&v, se(v.toString(), f), se("null", f)); 476 else static if (is(T == interface)) 477 handle!(Object)(cast(Object) v, f, sf, se); 478 479 // Aggregate should be matched before basic type to avoid 480 // `alias this` kicking in. See typedef support for more info. 481 else static if (is (T == struct)) 482 { 483 Flags old = f.flags; 484 f.flags |= Flags.Nested; 485 foreach (idx, ref m; v.tupleof) 486 { 487 static if (idx == 0) 488 sf("{ " ~ identifier!(T.tupleof[idx]) ~ ": "); 489 else 490 sf(", " ~ identifier!(T.tupleof[idx]) ~ ": "); 491 492 // A bit ugly but it makes string much more readable 493 handle(m, f, sf, se); 494 } 495 sf(v.tupleof.length ? " }" : "{ empty struct }"); 496 f.flags = old; 497 } 498 499 // Bool 500 else static if (is (Unqual!(T) == bool)) 501 se(v ? "true" : "false", f); 502 503 // Floating point values - Explicitly typed because we don't want 504 // to support imaginary and complex FP types 505 else static if (is(Unqual!(T) == float) || is(Unqual!(T) == double) 506 || is(Unqual!(T) == real)) 507 { 508 char[T.sizeof * 8] buff = void; 509 se(Float.format(buff, v, f.format), f); 510 } 511 512 // Associative array cannot be matched by IsExp in D1 513 else static if (isArrayType!(T) == ArrayKind.Associative) 514 { 515 bool started; 516 Flags old = f.flags; 517 f.flags |= Flags.Nested; 518 foreach (key, ref val; v) 519 { 520 if (!started) 521 { 522 started = true; 523 sf("[ "); 524 } 525 else 526 sf(", "); 527 528 handle(key, f, sf, se); 529 sf(": "); 530 handle(val, f, sf, se); 531 } 532 if (started) 533 sf(" ]"); 534 else // Empty or null 535 sf("[:]"); 536 f.flags = old; 537 } 538 539 // UTF-8 strings and chars (UTF-16 and UTF-32 unsupported) 540 else static if (is(T : cstring) 541 || is(T : const(wchar)[]) 542 || is(T : const(dchar)[])) 543 { 544 if (f.flags & Flags.Nested) sf(`"`); 545 UTF.toString(v, (cstring val) { se(val, f); return val.length; }); 546 if (f.flags & Flags.Nested) sf(`"`); 547 } 548 else static if (is(typeof((&v)[0 .. 1]) : cstring) 549 || is(typeof((&v)[0 .. 1]) : const(wchar)[]) 550 || is(typeof((&v)[0 .. 1]) : const(dchar)[])) 551 { 552 T[3] b = [ '\'', v, '\'' ]; 553 if (f.flags & Flags.Nested) 554 UTF.toString(b, (cstring val) { se(val, f); return val.length; }); 555 else 556 UTF.toString(b[1 .. 2], (cstring val) { se(val, f); return val.length; }); 557 } 558 559 // Signed integer 560 else static if (is(typeof(T.min)) && T.min < 0) 561 { 562 // Needs to support base 2 at most, plus an optional prefix 563 // of 2 chars max 564 char[T.sizeof * 8 + 2] buff = void; 565 se(Integer.format(buff, v, f.format), f); 566 } 567 // Unsigned integer 568 else static if (is(typeof(T.min)) && T.min == 0) 569 { 570 // Needs to support base 2 at most, plus an optional prefix of 2 chars 571 // max 572 char[T.sizeof * 8 + 2] buff = void; 573 se(Integer.format(buff, v, (f.format.length ? f.format : "u")), f); 574 } 575 576 // Arrays (dynamic and static) 577 else static if (isBasicArrayType!(T)) 578 { 579 alias ElementTypeOf!(T) A; 580 581 static if (is(Unqual!(A) == void)) 582 handle!(const(ubyte)[])(cast(const(ubyte)[]) v, f, sf, se); 583 else 584 { 585 sf("["); 586 if (v.length) 587 { 588 Flags old = f.flags; 589 f.flags |= Flags.Nested; 590 591 handle!(A)(v[0], f, sf, se); 592 foreach (idx, ref e; v[1 .. $]) 593 { 594 sf(", "); 595 handle!(A)(e, f, sf, se); 596 } 597 598 f.flags = old; 599 } 600 sf("]"); 601 } 602 } 603 604 else 605 static assert (0, "Type unsupported by ocean.text.convert.Formatter: " 606 ~ T.stringof); 607 } 608 609 610 /******************************************************************************* 611 612 Wrapper to call `toString` methods after checking for `null` 613 614 Before calling `toString`, a `null` check should be performed. 615 However, it shouldn't always be performed, as `T` might not be a ref type. 616 617 Params: 618 v = Object to check, might be a reference type 619 expr = Expression to call if `v` is not null or not a ref type 620 onNull = Expression to call if `v` is null 621 622 Returns: 623 Value returned by either `expr` or `onNull`, if any. 624 625 *******************************************************************************/ 626 627 private RetType nullWrapper (T, RetType) (T* v, lazy RetType expr, 628 lazy RetType onNull) 629 { 630 static if (is(typeof(*v is null))) 631 if (*v is null) 632 return onNull; 633 return expr; 634 } 635 636 637 /******************************************************************************* 638 639 Consumes the format string until a format specifier is found, 640 then returns information about that format specifier 641 642 Note: 643 This function iterates over 'char', and is *NOT* Unicode-correct. 644 However, neither is the original Tango one. 645 646 Params: 647 sink = An output delegate to write to 648 fmt = The format string to consume 649 650 Copyright: 651 This function was adapted from 652 `tango.text.convert.Layout.Layout.consume`. 653 The original was (c) Kris 654 655 Returns: 656 A description of the format specification, see `FormatInfo`'s 657 definition for more details 658 659 *******************************************************************************/ 660 661 private FormatInfo consume (scope FormatterSink sink, ref cstring fmt) 662 { 663 FormatInfo ret; 664 auto s = fmt.ptr; 665 auto end = s + fmt.length; 666 667 while (s < end && *s != '{') 668 ++s; 669 670 // Write all non-formatted content 671 sink(forwardSlice(fmt, s)); 672 673 if (s == end) 674 return ret; 675 676 // Tango format allowed escaping braces: "{{0}" would be turned 677 // into "{0}" 678 if (*++s == '{') 679 { 680 // Will always return "{{", but we only need the first char 681 sink(forwardSlice(fmt, s + 1)[0 .. 1]); 682 return ret; 683 } 684 685 ret.flags |= Flags.Format; 686 687 // extract index 688 if (readNumber(ret.index, s)) 689 ret.flags |= Flags.Index; 690 691 s = skipSpace(s, end); 692 693 // has minimum or maximum width? 694 if (*s == ',' || *s == '.') 695 { 696 if (*s == '.') 697 ret.flags |= Flags.Crop; 698 699 s = skipSpace(++s, end); 700 if (*s == '-') 701 { 702 ret.flags |= Flags.AlignLeft; 703 ++s; 704 } 705 else 706 ret.flags |= Flags.AlignRight; 707 708 // Extract expected width 709 if (readNumber(ret.width, s)) 710 ret.flags |= Flags.Width; 711 712 // skip spaces 713 s = skipSpace(s, end); 714 } 715 716 // Finally get the format string, if any 717 // e.g. for `{5:X} that would be 'X' 718 if (*s == ':') 719 ++s; 720 if (s < end) 721 { 722 auto fs = s; 723 // eat everything up to closing brace 724 while (s < end && *s != '}') 725 ++s; 726 ret.format = fs[0 .. cast(size_t) (s - fs)]; 727 } 728 729 forwardSlice(fmt, s); 730 731 // When the user-provided string is e.g. "Foobar {0:X" 732 if (*s != '}') 733 { 734 sink("{missing closing '}'}"); 735 ret.flags |= Flags.Error; 736 return ret; 737 } 738 739 // Eat the closing bracket ('}') 740 fmt = fmt[1 .. $]; 741 742 return ret; 743 } 744 745 746 /******************************************************************************* 747 748 Helper function to advance a slice to a pointer 749 750 Params: 751 s = Slice to advance 752 p = Internal pointer to 's' 753 754 Returns: 755 A slice to the data that was consumed (e.g. s[0 .. s.ptr - p]) 756 757 *******************************************************************************/ 758 759 private cstring forwardSlice (ref cstring s, const(char)* p) 760 out (ret) 761 { 762 assert(s.ptr == p); 763 assert(ret.ptr + ret.length == p); 764 } 765 do 766 { 767 verify(s.ptr <= p); 768 verify(s.ptr + s.length >= p); 769 770 cstring old = s.ptr[0 .. cast(size_t) (p - s.ptr)]; 771 s = s[old.length .. $]; 772 return old; 773 } 774 775 /******************************************************************************* 776 777 Helper function to advance a pointer to the next non-space character 778 779 Params: 780 s = Pointer to iterate 781 end = Pointer to the end of 's' 782 783 Returns: 784 's' pointing to a non-space character or 'end' 785 786 *******************************************************************************/ 787 788 private const(char)* skipSpace (const(char)* s, const(char)* end) 789 { 790 while (s < end && *s == ' ') 791 ++s; 792 return s; 793 } 794 795 /******************************************************************************* 796 797 Helper function to write a space to a sink 798 799 Allows one to pad a string. Writes in chunk of 32 chars at most. 800 801 Params: 802 s = Sink to write to 803 n = Amount of spaces to write 804 805 *******************************************************************************/ 806 807 private void writeSpace (scope FormatterSink s, size_t n) 808 { 809 static immutable string Spaces32 = " "; 810 811 // Make 'n' a multiple of Spaces32.length (32) 812 s(Spaces32[0 .. n % Spaces32.length]); 813 n -= n % Spaces32.length; 814 815 verify((n % Spaces32.length) == 0); 816 817 while (n != 0) 818 { 819 s(Spaces32); 820 n -= Spaces32.length; 821 } 822 } 823 824 /******************************************************************************* 825 826 Helper function to read a number while consuming the input 827 828 Params: 829 f = Value in which to store the number 830 s = Pointer to consume / read from 831 832 Copyright: 833 Originally from `tango.text.convert.Layout`. 834 Copyright Kris 835 836 Returns: 837 `true` if a number was read, `false` otherwise 838 839 *******************************************************************************/ 840 841 private bool readNumber (out size_t f, ref const(char)* s) 842 { 843 if (*s >= '0' && *s <= '9') 844 { 845 do 846 f = f * 10 + *s++ -'0'; 847 while (*s >= '0' && *s <= '9'); 848 return true; 849 } 850 return false; 851 } 852 853 854 /******************************************************************************* 855 856 Write a pointer to the sink 857 858 Params: 859 v = Pointer to write 860 f = Format information gathered from parsing 861 se = Element sink, to emit a single element with alignment 862 863 *******************************************************************************/ 864 865 private void writePointer (in void* v, ref FormatInfo f, scope ElemSink se) 866 { 867 alias void* T; 868 869 enum l = (T.sizeof * 2); 870 enum defaultFormat = "X" ~ l.stringof ~ "#"; 871 872 if (v is null) 873 se("null", f); 874 else 875 { 876 // Needs to support base 2 at most, plus an optional prefix 877 // of 2 chars max 878 char[T.sizeof * 8 + 2] buff = void; 879 se(Integer.format(buff, cast(ptrdiff_t) v, 880 (f.format.length ? f.format : defaultFormat)), f); 881 } 882 } 883 884 885 /******************************************************************************* 886 887 Represent all possible boolean values that can be set in FormatInfo.flags 888 889 *******************************************************************************/ 890 891 private enum Flags : ubyte 892 { 893 None = 0x00, /// Default 894 Format = 0x01, /// There was a formatting string (even if empty) 895 Error = 0x02, /// An error happened during formatting, bail out 896 AlignLeft = 0x04, /// Left alignment requested (via ',-' or '.-') 897 AlignRight = 0x08, /// Right alignment requested (via ',' or '.') 898 Crop = 0x10, /// Crop to width (via '.') 899 Index = 0x20, /// An index was explicitly provided 900 Width = 0x40, /// A width was explicitly provided 901 Nested = 0x80, /// We are formatting something nested 902 /// (i.e. in an aggregate type or an array) 903 } 904 905 /******************************************************************************* 906 907 Internal struct to hold information about the format specification 908 909 *******************************************************************************/ 910 911 private struct FormatInfo 912 { 913 /*************************************************************************** 914 915 Format string, might be empty 916 917 E.g. "{}" gives an empty `format`, and so does "{0}" 918 The string "{d}" and "{0,10:f}" give 'd' and 'f', respectively. 919 920 ***************************************************************************/ 921 922 public cstring format; 923 924 /*************************************************************************** 925 926 Explicitly requested index to use, only meaningful if flags.Index is set 927 928 ***************************************************************************/ 929 930 public size_t index; 931 932 /*************************************************************************** 933 934 Output width explicitly requested, only meaningful if flags.Width is set 935 936 ***************************************************************************/ 937 938 public size_t width; 939 940 /*************************************************************************** 941 942 Grab bag of boolean values, check `Flags` enum for complete doc 943 944 ***************************************************************************/ 945 946 public Flags flags; 947 } 948 949 /// Returns: Whether or not `T` can be `switch`ed on 950 private template canSwitchOn (T) 951 { 952 enum canSwitchOn = is(typeof(() { switch (T.init) { default: break; }})); 953 } 954 955 unittest 956 { 957 static assert(canSwitchOn!string); 958 static assert(canSwitchOn!(immutable int)); 959 static assert(!canSwitchOn!(real)); 960 }