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