1 /*******************************************************************************
2 
3     Lightweight, memory friendly string formatting module
4 
5     This module provides 4 possible semantics:
6     - For pedestrian usage which doesn't care about allocations, see `format`
7     - For allocation-friendly semantic where the data is output either to
8       a sink or to a `ref char[]`, see the `sformat` overloads
9     - To ensure absolutely no allocation happens, see `snformat`
10 
11     Users of Phobos' `std.format` will find many similarities in the API:
12     - `format` is equivalent to `std.format.format`
13     - `snformat` is equivalent to `std.format.sformat`
14     - `sformat` is roughly equivalent to `std.format.formattedWrite`
15 
16     Format_specifier:
17 
18     The Formatter uses a format specification similar to C#/.NET over
19     the traditional `printf` style.
20     As a result, the most simple usage is to call:
21     ---
22     format("This value will be default formatted: {}", value);
23     ---
24 
25     More specific formatting options are however available.
26 
27     The format specifier is defined as follows:
28 
29     '{'[INDEX][WIDTH_CHAR[ALIGN_LEFT_CHAR][ALIGN_WIDTH][' '*]][':'][FORMAT_STRING]'}'
30 
31     In more details:
32     - `INDEX` is the positive, decimal and 0 based index of the argument
33       to format.
34     - `WIDTH_CHAR` is either ',' (comma) if a minimum width is requested,
35       in which case the output will be padded with spaces,
36       or '.' if a maximum width is requested, in which case the output will be
37       cropped and cropping will be noted by the presence of "..."
38     - `ALIGN_LEFT_CHAR` is '-'. If present, padding / cropping will be done
39       on the left side of the string, otherwise it will be the right side
40       This can only be used after a `WIDTH_CHAR`.
41     - `ALIGN_WIDTH` is the positive, decimal width the argument should have.
42     - ':' can optionally be used to separate the index / width specification
43       from the format string. So `{}`, `{:}` and `{0:X}` are all valid.
44     - `FORMAT_STRING` is an argument-defined format string
45 
46     Format_string:
47 
48     The format string defines how the argument will be formatted, and thus
49     is dependent on the argument type.
50 
51     Currently the following formatting strings are supported:
52         - 'X' or 'x' are used for hexadecimal formatting of the output.
53           'X' outputs uppercase, 'x' will output lowercase.
54           Applies to integer types and pointers, and will also output
55           the hexadecimal.
56         - 'e' for floating point type will display exponential notation.
57           Using a number will set the precision, so for example the string
58           `"{:2}"` with argument `0.123456` will output `"0.12"`
59           Finally, '.' will prevent padding.
60     Unrecognized formatting strings should be ignored. For composed types,
61     the formatting string is passed along, so using `X` on a `struct` or an
62     array will display any integer / pointer members in uppercase hexadecimal.
63 
64     Copyright:
65         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
66         Some parts (marked explicitly) copyright Kris and/or Larsivi.
67         All rights reserved.
68 
69     License:
70         Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
71         See LICENSE_TANGO.txt for details.
72 
73 *******************************************************************************/
74 
75 module ocean.text.convert.Formatter;
76 
77 import ocean.meta.types.Qualifiers;
78 import ocean.core.Buffer;
79 import Integer = ocean.text.convert.Integer_tango;
80 import Float = ocean.text.convert.Float;
81 import UTF = ocean.text.convert.Utf;
82 import ocean.core.Verify;
83 
84 import ocean.meta.traits.Basic;
85 import ocean.meta.types.Typedef;
86 import ocean.meta.types.Arrays;
87 import ocean.meta.codegen.Identifier;
88 
89 /*******************************************************************************
90 
91     Type of 'sink' that can be passed to `format`, that will just format a
92     string into the provided sink.
93 
94 *******************************************************************************/
95 
96 public alias void delegate(in cstring) FormatterSink;
97 
98 /*******************************************************************************
99 
100     Internal sink type that wraps the user-provided one and takes care
101     of cropping and width
102 
103     This sink expects to receive a full element as the first parameter,
104     in other words, the full chunk of text that needs a fixed size.
105     This is why one cannot format a whole aggregate (struct, arrays etc.), but
106     only individual elements.
107 
108 *******************************************************************************/
109 
110 private alias void delegate(cstring, ref const(FormatInfo)) ElemSink;
111 
112 
113 /*******************************************************************************
114 
115     Formats an input string into a newly-allocated string and returns it
116 
117     Params:
118         fmt     = Format string to use
119         args    = Variadic arguments to format according to `fmt`
120 
121     Returns:
122         A newly allocated, immutable formatted string
123 
124 *******************************************************************************/
125 
126 public istring format (Args...) (cstring fmt, Args args)
127 {
128     import ocean.core.TypeConvert : assumeUnique;
129 
130     mstring buffer;
131 
132     scope FormatterSink sink = (in cstring s)
133     {
134         buffer ~= s;
135     };
136 
137     sformat(sink, fmt, args);
138     return assumeUnique(buffer);
139 }
140 
141 
142 /*******************************************************************************
143 
144     Append the processed (formatted) input onto the end of the provided buffer
145 
146     Params:
147         buffer  = The buffer to which to append the formatted string; its
148                   capacity will be increased if necessary
149         fmt     = Format string to use
150         args    = Variadic arguments to format according to `fmt`
151 
152     Returns:
153         A reference to `buffer`
154 
155 *******************************************************************************/
156 
157 public mstring sformat (Args...) (ref mstring buffer, cstring fmt, Args args)
158 {
159     scope FormatterSink sink = (in cstring s)
160     {
161         buffer ~= s;
162     };
163     sformat(sink, fmt, args);
164     return buffer;
165 }
166 
167 /// ditto
168 public mstring sformat (Args...) (ref Buffer!(char) buffer, cstring fmt, Args args)
169 {
170     scope FormatterSink sink = (in cstring s)
171     {
172         buffer ~= s;
173     };
174     sformat(sink, fmt, args);
175     return buffer[];
176 }
177 
178 /*******************************************************************************
179 
180     Write the processed (formatted) input into a fixed-length buffer
181 
182     This function will not perform any allocation.
183     If the output does not fit in `buffer`, the extra output will simply
184     be discarded.
185 
186     Params:
187         buffer  = The buffer to write the formatted string into.
188                   Unlike the sformat overloads, the buffer won't be extended.
189                   This leads to a slightly different semantic for this
190                   buffer (the others are only appended to, this one is
191                   written to).
192         fmt     = Format string to use
193         args    = Variadic arguments to format according to `fmt`
194 
195     Returns:
196         A reference to `buffer`
197 
198 *******************************************************************************/
199 
200 public mstring snformat (Args...) (mstring buffer, cstring fmt, Args args)
201 {
202     size_t start;
203 
204     scope FormatterSink sink = (in cstring s)
205     {
206         size_t left = buffer.length - start;
207         size_t wsize = left <= s.length ? left : s.length;
208         if (wsize > 0)
209             buffer[start .. start + wsize] = s[0 .. wsize];
210         start += wsize;
211     };
212 
213     sformat(sink, fmt, args);
214     return buffer[0 .. start];
215 }
216 
217 /*******************************************************************************
218 
219     Send the processed (formatted) input into a sink
220 
221     Params:
222         sink    = A delegate that will be called, possibly multiple
223                     times, with a portion of the result string
224         fmt     = Format string to use
225         args    = Variadic arguments to format according to fmt
226 
227     Returns:
228         If formatting was successful, returns `true`, `false` otherwise.
229 
230 *******************************************************************************/
231 
232 public bool sformat (Args...) (scope FormatterSink sink, cstring fmt, Args args)
233 {
234     FormatInfo info;
235     size_t nextIndex;
236 
237     // A delegate to write elements according to the FormatInfo
238     scope elemSink = (cstring str, ref const(FormatInfo) f)
239     {
240         widthSink(sink, str, f);
241     };
242 
243     // Main loop
244     while (fmt.length)
245     {
246         info = consume(sink, fmt);
247 
248         if (info.flags & Flags.Error)
249             return false;
250 
251         if (info.flags & Flags.Format)
252         {
253             // Handle index, the single source of pain
254             if (info.flags & Flags.Index)
255                 nextIndex = info.index + 1;
256             else
257                 info.index = nextIndex++;
258 
259             /*
260              * The foreach within the switch is executed at compile time
261              * It allows accessing a compile-time known parameter (and most
262              * importantly, its type) using a runtime index.
263              * It is basically generating a jump table, which is exactly
264              * what the codegen will produce.
265              * Note that we need to use the break to label feature as
266              * `break` would otherwise break out of the static foreach
267              * (even if that doesn't make sense), and thus, execute the
268              * `default` statement in every case (!)
269              */
270         JT: switch (info.index)
271             {
272                 // NOTE: We could static foreach over `args` (the values)
273                 // instead of `Args` (the type) but it currently triggers
274                 // a DMD bug, and as a result a deprecation message:
275                 // https://issues.dlang.org/show_bug.cgi?id=16521
276                 foreach (idx, Tunused; Args)
277                 {
278                 case idx:
279                     handle(args[idx], info, sink, elemSink);
280                     break JT;
281                 }
282 
283             default:
284                 sink("{invalid index}");
285             }
286         }
287     }
288     return true;
289 }
290 
291 /*******************************************************************************
292 
293     A function that writes to a `Sink` according to the width limits
294 
295     Params:
296         sink = Sink to write to
297         str  = String to write to the sink
298         f    = FormatInfo object from which to read the width and flags
299 
300 *******************************************************************************/
301 
302 private void widthSink (scope FormatterSink sink, cstring str, ref const(FormatInfo) f)
303 {
304     if (f.flags & Flags.Width)
305     {
306         // "{.4}",  "Hello" gives "Hell..."
307         // "{.-4}", "Hello" gives "...ello"
308         if (f.flags & Flags.Crop)
309         {
310             if (f.flags & Flags.AlignLeft)
311             {
312                 if (str.length > f.width)
313                 {
314                     sink("...");
315                     sink(str[$ - f.width .. $]);
316                 }
317                 else
318                     sink(str);
319             }
320             else
321             {
322                 verify((f.flags & Flags.AlignRight) != 0);
323                 if (str.length > f.width)
324                 {
325                     sink(str[0 .. f.width]);
326                     sink("...");
327                 }
328                 else
329                     sink(str);
330             }
331         }
332         else if (f.width > str.length)
333         {
334             if (f.flags & Flags.AlignLeft)
335             {
336                 sink(str);
337                 writeSpace(sink, f.width - str.length);
338             }
339             else
340             {
341                 verify((f.flags & Flags.AlignRight) != 0);
342                 writeSpace(sink, f.width - str.length);
343                 sink(str);
344             }
345         }
346         // Else fall back to just writing the string
347         else
348         {
349             sink(str);
350         }
351     }
352     else
353         sink(str);
354 }
355 
356 
357 /*******************************************************************************
358 
359     Converts a value of a given type to its string representation
360 
361     Params:
362         T   = Type of the argument to convert
363         v   = Value of the argument to convert
364         f   = Format information gathered from parsing
365         sf  = Fragment sink, to emit a part of the text without alignment
366         se  = Element sink, to emit a single element with alignment
367 
368 *******************************************************************************/
369 
370 private void handle (T) (T v, FormatInfo f, scope FormatterSink sf, scope ElemSink se)
371 {
372     /** The order in which the following conditions are applied matters.
373      * Explicit type checks (e.g. associative array, or `is(T == V)`)
374      * should go first as they are unambiguous.
375      * Multiple conditions could be matched by the same type.
376      */
377 
378     // `typeof(null)` matches way too many things
379     static if (is(T == typeof(null)))
380         se("null", f);
381 
382 
383     // Pretty print enum
384     // Note that switch is only available for string and integer based enums.
385     // However, since it expands to a jump table for integers and a binary
386     // search for strings, we still want to special case it.
387     else static if (is(T V == enum) && canSwitchOn!T)
388     {
389         sw: switch (v)
390         {
391             foreach (member; __traits(allMembers, T))
392             {
393             case mixin("T." ~ member):
394                 sf(T.stringof);
395                 sf(".");
396                 sf(member);
397                 break sw;
398             }
399             default :
400                 sf("cast(");
401                 sf(T.stringof);
402                 sf(") ");
403                 handle!(V)(v, f, sf, se);
404         }
405     }
406 
407     // Pretty print enum for non-integer, non-string base types
408     // This branch should be rarely, if ever, used.
409     else static if (is(T E == enum))
410     {
411         foreach (member; __traits(allMembers, T))
412         {
413             if (v == mixin("T." ~ member))
414             {
415                 sf(T.stringof);
416                 sf(".");
417                 sf(member);
418                 return;
419             }
420         }
421 
422         sf("cast(");
423         sf(T.stringof);
424         sf(") ");
425         handle!(E)(v, f, sf, se);
426     }
427 
428     // Delegate / Function pointers
429     else static if (is(T == delegate))
430     {
431         sf(T.stringof ~ ": { funcptr: ");
432         writePointer(v.funcptr, f, se);
433         sf(", ptr: ");
434         writePointer(v.ptr, f, se);
435         sf(" }");
436     }
437     else static if (is(T U == return))
438     {
439         sf(T.stringof ~ ": ");
440         writePointer(v, f, se);
441     }
442 
443     // Pointers need to be at the top because `(int*).min` compiles
444     // and hence would match the integer rules
445     // In addition, thanks to automatic dereferencing, checks such as
446     // `v.toString()` and `T.IsTypedef` pass when `typeof(v)` is an `Object*`,
447     // and when `T` is a pointer to a typedef.
448     else static if (is (T P == P*))
449         writePointer(v, f, se);
450 
451     /** D1 + D2 support of typedef
452      * Note that another approach would be to handle `struct` at the very
453      * last stage and relying on `alias this` for implicit conversions.
454      * However this is not a reliable approach, as having an `alias this`
455      * doesn't mean that it will be a typedef, and a user might want the struct
456      * to be printed instead of the first matching `alias this`.
457      * In fact, there is no way to semantically express subtyping,
458      * but only the means to perform it.
459      * This could be solved later with a UDA, but it's at best a workaround.
460      */
461     else static if (isTypedef!(T))
462         handle!(TypedefBaseType!(T))(v, f, sf, se);
463 
464     // toString hook: Give priority to the non-allocating one
465     // Note: sink `toString` overload should take a `scope` delegate
466     else static if (is(typeof(v.toString(sf))))
467         nullWrapper(&v,
468                     v.toString((in 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         T[3] b = [ '\'', v, '\'' ];
549         if (f.flags & Flags.Nested)
550             UTF.toString(b, (cstring val) { se(val, f); return val.length; });
551         else
552             UTF.toString(b[1 .. 2], (cstring val) { se(val, f); return val.length; });
553     }
554 
555     // Signed integer
556     else static if (is(typeof(T.min)) && T.min < 0)
557     {
558         // Needs to support base 2 at most, plus an optional prefix
559         // of 2 chars max
560         char[T.sizeof * 8 + 2] buff = void;
561         se(Integer.format(buff, v, f.format), f);
562     }
563     // Unsigned integer
564     else static if (is(typeof(T.min)) && T.min == 0)
565     {
566         // Needs to support base 2 at most, plus an optional prefix of 2 chars
567         // max
568         char[T.sizeof * 8 + 2] buff = void;
569         se(Integer.format(buff, v, (f.format.length ? f.format : "u")), f);
570     }
571 
572     // Arrays (dynamic and static)
573     else static if (isBasicArrayType!(T))
574     {
575         alias ElementTypeOf!(T) A;
576 
577         static if (is(Unqual!(A) == void))
578             handle!(const(ubyte)[])(cast(const(ubyte)[]) v, f, sf, se);
579         else
580         {
581             sf("[");
582             if (v.length)
583             {
584                 Flags old = f.flags;
585                 f.flags |= Flags.Nested;
586 
587                 handle!(A)(v[0], f, sf, se);
588                 foreach (idx, ref e; v[1 .. $])
589                 {
590                     sf(", ");
591                     handle!(A)(e, f, sf, se);
592                 }
593 
594                 f.flags = old;
595             }
596             sf("]");
597         }
598     }
599 
600     else
601         static assert (0, "Type unsupported by ocean.text.convert.Formatter: "
602                        ~ T.stringof);
603 }
604 
605 
606 /*******************************************************************************
607 
608     Wrapper to call `toString` methods after checking for `null`
609 
610     Before calling `toString`, a `null` check should be performed.
611     However, it shouldn't always be performed, as `T` might not be a ref type.
612 
613     Params:
614         v = Object to check, might be a reference type
615         expr = Expression to call if `v` is not null or not a ref type
616         onNull = Expression to call if `v` is null
617 
618     Returns:
619         Value returned by either `expr` or `onNull`, if any.
620 
621 *******************************************************************************/
622 
623 private RetType nullWrapper (T, RetType) (T* v, lazy RetType expr,
624                                           lazy RetType onNull)
625 {
626     static if (is(typeof(*v is null)))
627         if (*v is null)
628             return onNull;
629     return expr;
630 }
631 
632 
633 /*******************************************************************************
634 
635         Consumes the format string until a format specifier is found,
636         then returns information about that format specifier
637 
638         Note:
639           This function iterates over 'char', and is *NOT* Unicode-correct.
640           However, neither is the original Tango one.
641 
642         Params:
643             sink    = An output delegate to write to
644             fmt     = The format string to consume
645 
646         Copyright:
647             This function was adapted from
648             `tango.text.convert.Layout.Layout.consume`.
649             The original was (c) Kris
650 
651         Returns:
652             A description of the format specification, see `FormatInfo`'s
653             definition for more details
654 
655 *******************************************************************************/
656 
657 private FormatInfo consume (scope FormatterSink sink, ref cstring fmt)
658 {
659     FormatInfo ret;
660     auto s = fmt.ptr;
661     auto end = s + fmt.length;
662 
663     while (s < end && *s != '{')
664         ++s;
665 
666     // Write all non-formatted content
667     sink(forwardSlice(fmt, s));
668 
669     if (s == end)
670         return ret;
671 
672     // Tango format allowed escaping braces: "{{0}" would be turned
673     // into "{0}"
674     if (*++s == '{')
675     {
676         // Will always return "{{", but we only need the first char
677         sink(forwardSlice(fmt, s + 1)[0 .. 1]);
678         return ret;
679     }
680 
681     ret.flags |= Flags.Format;
682 
683     // extract index
684     if (readNumber(ret.index, s))
685         ret.flags |= Flags.Index;
686 
687     s = skipSpace(s, end);
688 
689     // has minimum or maximum width?
690     if (*s == ',' || *s == '.')
691     {
692         if (*s == '.')
693             ret.flags |= Flags.Crop;
694 
695         s = skipSpace(++s, end);
696         if (*s == '-')
697         {
698             ret.flags |= Flags.AlignLeft;
699             ++s;
700         }
701         else
702             ret.flags |= Flags.AlignRight;
703 
704         // Extract expected width
705         if (readNumber(ret.width, s))
706             ret.flags |= Flags.Width;
707 
708         // skip spaces
709         s = skipSpace(s, end);
710     }
711 
712     // Finally get the format string, if any
713     // e.g. for `{5:X} that would be 'X'
714     if (*s == ':')
715         ++s;
716     if (s < end)
717     {
718         auto fs = s;
719         // eat everything up to closing brace
720         while (s < end && *s != '}')
721             ++s;
722         ret.format = fs[0 .. cast(size_t) (s - fs)];
723     }
724 
725     forwardSlice(fmt, s);
726 
727     // When the user-provided string is e.g. "Foobar {0:X"
728     if (*s != '}')
729     {
730         sink("{missing closing '}'}");
731         ret.flags |= Flags.Error;
732         return ret;
733     }
734 
735     // Eat the closing bracket ('}')
736     fmt = fmt[1 .. $];
737 
738     return ret;
739 }
740 
741 
742 /*******************************************************************************
743 
744         Helper function to advance a slice to a pointer
745 
746         Params:
747             s   = Slice to advance
748             p   = Internal pointer to 's'
749 
750         Returns:
751             A slice to the data that was consumed (e.g. s[0 .. s.ptr - p])
752 
753 *******************************************************************************/
754 
755 private cstring forwardSlice (ref cstring s, const(char)* p)
756 out (ret)
757 {
758     assert(s.ptr == p);
759     assert(ret.ptr + ret.length == p);
760 }
761 do
762 {
763     verify(s.ptr <= p);
764     verify(s.ptr + s.length >= p);
765 
766     cstring old = s.ptr[0 .. cast(size_t) (p - s.ptr)];
767     s = s[old.length .. $];
768     return old;
769 }
770 
771 /*******************************************************************************
772 
773         Helper function to advance a pointer to the next non-space character
774 
775         Params:
776             s   = Pointer to iterate
777             end = Pointer to the end of 's'
778 
779         Returns:
780             's' pointing to a non-space character or 'end'
781 
782 *******************************************************************************/
783 
784 private const(char)* skipSpace (const(char)* s, const(char)* end)
785 {
786     while (s < end && *s == ' ')
787         ++s;
788     return s;
789 }
790 
791 /*******************************************************************************
792 
793         Helper function to write a space to a sink
794 
795         Allows one to pad a string. Writes in chunk of 32 chars at most.
796 
797         Params:
798             s   = Sink to write to
799             n   = Amount of spaces to write
800 
801 *******************************************************************************/
802 
803 private void writeSpace (scope FormatterSink s, size_t n)
804 {
805     static immutable istring Spaces32 = "                                ";
806 
807     // Make 'n' a multiple of Spaces32.length (32)
808     s(Spaces32[0 .. n % Spaces32.length]);
809     n -= n % Spaces32.length;
810 
811     verify((n % Spaces32.length) == 0);
812 
813     while (n != 0)
814     {
815         s(Spaces32);
816         n -= Spaces32.length;
817     }
818 }
819 
820 /*******************************************************************************
821 
822         Helper function to read a number while consuming the input
823 
824         Params:
825             f = Value in which to store the number
826             s = Pointer to consume / read from
827 
828         Copyright:
829             Originally from `tango.text.convert.Layout`.
830             Copyright Kris
831 
832         Returns:
833             `true` if a number was read, `false` otherwise
834 
835 *******************************************************************************/
836 
837 private bool readNumber (out size_t f, ref const(char)* s)
838 {
839     if (*s >= '0' && *s <= '9')
840     {
841         do
842             f = f * 10 + *s++ -'0';
843         while (*s >= '0' && *s <= '9');
844         return true;
845     }
846     return false;
847 }
848 
849 
850 /*******************************************************************************
851 
852         Write a pointer to the sink
853 
854         Params:
855             v   = Pointer to write
856             f   = Format information gathered from parsing
857             se  = Element sink, to emit a single element with alignment
858 
859 *******************************************************************************/
860 
861 private void writePointer (in void* v, ref FormatInfo f, scope ElemSink se)
862 {
863     alias void* T;
864 
865     enum l = (T.sizeof * 2);
866     enum defaultFormat = "X" ~ l.stringof ~ "#";
867 
868     if (v is null)
869         se("null", f);
870     else
871     {
872         // Needs to support base 2 at most, plus an optional prefix
873         // of 2 chars max
874         char[T.sizeof * 8 + 2] buff = void;
875         se(Integer.format(buff, cast(ptrdiff_t) v,
876                           (f.format.length ? f.format : defaultFormat)), f);
877     }
878 }
879 
880 
881 /*******************************************************************************
882 
883     Represent all possible boolean values that can be set in FormatInfo.flags
884 
885 *******************************************************************************/
886 
887 private enum Flags : ubyte
888 {
889     None        = 0x00,     /// Default
890     Format      = 0x01,     /// There was a formatting string (even if empty)
891     Error       = 0x02,     /// An error happened during formatting, bail out
892     AlignLeft   = 0x04,     /// Left alignment requested (via ',-' or '.-')
893     AlignRight  = 0x08,     /// Right alignment requested (via ',' or '.')
894     Crop        = 0x10,     /// Crop to width (via '.')
895     Index       = 0x20,     /// An index was explicitly provided
896     Width       = 0x40,     /// A width was explicitly provided
897     Nested      = 0x80,     /// We are formatting something nested
898                             ///   (i.e. in an aggregate type or an array)
899 }
900 
901 /*******************************************************************************
902 
903     Internal struct to hold information about the format specification
904 
905 *******************************************************************************/
906 
907 private struct FormatInfo
908 {
909     /***************************************************************************
910 
911         Format string, might be empty
912 
913         E.g. "{}" gives an empty `format`, and so does "{0}"
914         The string "{d}" and "{0,10:f}" give 'd' and 'f', respectively.
915 
916     ***************************************************************************/
917 
918     public cstring format;
919 
920     /***************************************************************************
921 
922         Explicitly requested index to use, only meaningful if flags.Index is set
923 
924     ***************************************************************************/
925 
926     public size_t index;
927 
928     /***************************************************************************
929 
930         Output width explicitly requested, only meaningful if flags.Width is set
931 
932     ***************************************************************************/
933 
934     public size_t width;
935 
936     /***************************************************************************
937 
938         Grab bag of boolean values, check `Flags` enum for complete doc
939 
940     ***************************************************************************/
941 
942     public Flags flags;
943 }
944 
945 /// Returns: Whether or not `T` can be `switch`ed on
946 private template canSwitchOn (T)
947 {
948     enum canSwitchOn = is(typeof(() { switch (T.init) { default: break; }}));
949 }
950 
951 unittest
952 {
953     static assert(canSwitchOn!string);
954     static assert(canSwitchOn!(immutable int));
955     static assert(!canSwitchOn!(real));
956 }