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