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 }