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