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 }