1 /++
2 $(H1 @nogc Formatting Utilities)
3 
4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
5 Authors: Ilya Yaroshenko
6 +/
7 module mir.format;
8 
9 import std.traits;
10 import mir.primitives: isOutputRange;
11 
12 /// `mir.conv: to` extension.
13 version(mir_test)
14 @safe pure @nogc
15 unittest
16 {
17     import mir.conv: to;
18     import mir.small_string;
19     alias S = SmallString!32;
20 
21     // Floating-point numbers are formatted to
22     // the shortest precise exponential notation.
23     assert(123.0.to!S == "123.0");
24     assert(123.to!(immutable S) == "123");
25     assert(true.to!S == "true");
26     assert(true.to!string == "true");
27 
28     assert((cast(S)"str")[] == "str");
29 }
30 
31 /// `mir.conv: to` extension.
32 version(mir_test)
33 @safe pure
34 unittest
35 {
36     import mir.conv: to;
37     import mir.small_string;
38     alias S = SmallString!32;
39 
40     auto str = S("str");
41     assert(str.to!(const(char)[]) == "str"); // GC allocated result
42     assert(str.to!(char[]) == "str"); // GC allocated result
43 }
44 
45 /// ditto
46 version(mir_test)
47 @safe pure
48 unittest
49 {
50     import mir.conv: to;
51     import mir.small_string;
52     alias S = SmallString!32;
53 
54     // Floating-point numbers are formatted to
55     // the shortest precise exponential notation.
56     assert(123.0.to!string == "123.0");
57     assert(123.to!(char[]) == "123");
58 
59     assert(S("str").to!string == "str"); // GC allocated result
60 }
61 
62 /// Concatenated string results
63 string text(string separator = "", Args...)(auto ref Args args)
64     if (Args.length > 0)
65 {
66     static if (Args.length == 1)
67     {
68         import mir.functional: forward;
69         import mir.conv: to;
70         return to!string(forward!args);
71     }
72     else
73     {
74         import mir.appender: scopedBuffer;
75         auto buffer = scopedBuffer!char;
76         foreach (i, ref arg; args)
77         {
78             buffer.print(arg);
79             static if (separator.length && i + 1 < args.length)
80             {
81                 buffer.printStaticString!char(separator);
82             }
83         }
84         return buffer.data.idup;
85     }
86 }
87 
88 ///
89 @safe pure nothrow unittest
90 {
91     const i = 100;
92     assert(text("str ", true, " ", i, " ", 124.1) == "str true 100 124.1", text("str ", true, " ", 100, " ", 124.1));
93     assert(text!" "("str", true, 100, 124.1) == "str true 100 124.1");
94 }
95 
96 import mir.format_impl;
97 
98 ///
99 struct GetData {}
100 
101 ///
102 enum getData = GetData();
103 
104 /++
105 +/
106 struct _stringBuf(C)
107 {
108     import mir.appender: ScopedBuffer;
109 
110     ///
111     ScopedBuffer!(C, 256) buffer;
112 
113     ///
114     alias buffer this;
115 
116     ///
117     mixin StreamFormatOp!C;
118 }
119 
120 ///ditto
121 alias stringBuf = _stringBuf!char;
122 ///ditto
123 alias wstringBuf = _stringBuf!wchar;
124 ///ditto
125 alias dstringBuf = _stringBuf!dchar;
126 
127 /++
128 +/
129 mixin template StreamFormatOp(C)
130 {
131     ///
132     ref typeof(this) opBinary(string op : "<<", T)(ref const T c) scope
133     {
134         return print!C(this, c);
135     }
136 
137     ///
138     ref typeof(this) opBinary(string op : "<<", T)(const T c) scope
139     {
140         return print!C(this, c);
141     }
142 
143     /// ditto
144     const(C)[] opBinary(string op : "<<", T : GetData)(const T c) scope
145     {
146         return buffer.data;
147     }
148 }
149 
150 ///
151 @safe pure nothrow @nogc
152 version (mir_test) unittest
153 {
154     auto name = "D";
155     auto ver = 2.0;
156     assert(stringBuf() << "Hi " << name << ver << "!\n" << getData == "Hi D2.0!\n");
157 }
158 
159 ///
160 @safe pure nothrow @nogc
161 version (mir_test) unittest
162 {
163     auto name = "D"w;
164     auto ver = 2;
165     assert(wstringBuf() << "Hi "w << name << ver << "!\n"w << getData == "Hi D2!\n"w);
166 }
167 
168 ///
169 @safe pure nothrow @nogc
170 version (mir_test) unittest
171 {
172     auto name = "D"d;
173     auto ver = 2UL;
174     assert(dstringBuf() << "Hi "d  << name << ver << "!\n"d << getData == "Hi D2!\n");
175 }
176 
177 @safe pure nothrow @nogc
178 version (mir_test) unittest
179 {
180     assert(stringBuf() << -1234567890 << getData == "-1234567890");
181 }
182 
183 /++
184 Mir's numeric format specification
185 
186 Note: the specification isn't complete an may be extended in the future.
187 +/
188 struct NumericSpec
189 {
190     ///
191     enum Format
192     {
193         /++
194         Human-frindly precise output.
195         Examples: `0.000001`, `600000.0`, but `1e-7` and `6e7`.
196         +/
197         human,
198         /++
199         Precise output with explicit exponent.
200         Examples: `1e-6`, `6e6`, `1.23456789e-100`.
201         +/
202         exponent,
203     }
204 
205     ///
206     Format format;
207 
208     /// Default valus is '\0' (no separators)
209     char separatorChar = '\0';
210 
211     /// Defaults to 'e'
212     char exponentChar = 'e';
213 
214     /// Adds '+' to positive numbers and `+0`.
215     bool plus;
216 
217     /// Separator count
218     ubyte separatorCount = 3;
219 
220     /++
221     Precise output with explicit exponent.
222     Examples: `1e-6`, `6e6`, `1.23456789e-100`.
223     +/
224     enum NumericSpec exponent = NumericSpec(Format.exponent);
225 
226     /++
227     Human-frindly precise output.
228     +/
229     enum NumericSpec human = NumericSpec(Format.human);
230 }
231 
232 // 16-bytes
233 /// C's compatible format specifier.
234 struct FormatSpec
235 {
236     ///
237     bool dash;
238     ///
239     bool plus;
240     ///
241     bool space;
242     ///
243     bool hash;
244     ///
245     bool zero;
246     ///
247     char format = 's';
248     ///
249     char separator = '\0';
250     ///
251     ubyte unitSize;
252     ///
253     int width;
254     ///
255     int precision = -1;
256 }
257 
258 /++
259 +/
260 enum SwitchLU : bool
261 {
262     ///
263     lower,
264     ///
265     upper,
266 }
267 
268 /++
269 Wrapper to format floating point numbers using C's library.
270 +/
271 struct FormattedFloating(T)
272     if(is(T == float) || is(T == double) || is(T == real))
273 {
274     ///
275     T value;
276     ///
277     FormatSpec spec;
278 
279     ///
280     void toString(C = char, W)(scope ref W w) scope const
281     if (isSomeChar!C)
282     {
283         C[512] buf = void;
284         auto n = printFloatingPoint(value, spec, buf);
285         w.put(buf[0 ..  n]);
286     }
287 }
288 
289 /// ditto
290 FormattedFloating!T withFormat(T)(const T value, FormatSpec spec)
291 {
292     version(LDC) pragma(inline);
293     return typeof(return)(value, spec);
294 }
295 
296 /++
297 +/
298 struct HexAddress(T)
299     if (isUnsigned!T && !is(T == enum))
300 {
301     ///
302     T value;
303     ///
304     SwitchLU switchLU = SwitchLU.upper;
305 
306     ///
307     void toString(C = char, W)(scope ref W w) scope const
308         if (isSomeChar!C)
309     {
310         enum N = T.sizeof * 2;
311         static if(isFastBuffer!W)
312         {
313             w.advance(printHexAddress(value, w.getStaticBuf!N, cast(bool) switchLU));
314         }
315         else
316         {
317             C[N] buf = void;
318             printHexAddress(value, buf, cast(bool) switchLU);
319             w.put(buf[]);
320         }
321     }
322 }
323 
324 /++
325 Escaped string formats
326 +/
327 enum EscapeFormat
328 {
329     /// JSON escaped string format
330     json,
331     /// Amzn Ion CLOB format
332     ionClob,
333     /// Amzn Ion symbol format
334     ionSymbol,
335     /// Amzn Ion string format
336     ion,
337 }
338 
339 enum escapeFormatQuote(EscapeFormat escapeFormat) = escapeFormat == EscapeFormat.ionSymbol ? '\'' : '\"';
340 
341 /++
342 +/
343 ref W printEscaped(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope return ref W w, scope const(C)[] str)
344     if (isOutputRange!(W, C))
345 {
346     import mir.utility: _expect;
347     foreach (C c; str)
348     {
349         if (_expect(c == escapeFormatQuote!escapeFormat || c == '\\', false))
350             goto E;
351         if (_expect(c < ' ', false))
352             goto C;
353         static if (escapeFormat == EscapeFormat.ionClob)
354         {
355             if (c >= 127)
356                 goto A;
357         }
358     P:
359         w.put(c);
360         continue;
361     E:
362         {
363             C[2] pair;
364             pair[0] = '\\';
365             pair[1] = c;
366             w.printStaticString!C(pair);
367             continue;
368         }
369     C:
370         switch (c)
371         {
372             static if (escapeFormat != EscapeFormat.json)
373             {
374                 case '\0':
375                     c = '0';
376                     goto E;
377                 case '\a':
378                     c = 'a';
379                     goto E;
380                 case '\v':
381                     c = 'v';
382                     goto E;
383             }
384             case '\b':
385                 c = 'b';
386                 goto E;
387             case '\t':
388                 c = 't';
389                 goto E;
390             case '\n':
391                 c = 'n';
392                 goto E;
393             case '\f':
394                 c = 'f';
395                 goto E;
396             case '\r':
397                 c = 'r';
398                 goto E;
399             default:
400     A:
401                 static if (escapeFormat == EscapeFormat.json)
402                     put_uXXXX!C(w, cast(char)c);
403                 else
404                     put_xXX!C(w, cast(char)c);
405         }
406     }
407     return w;
408 }
409 
410 ///
411 @safe pure nothrow @nogc
412 version (mir_test) unittest
413 {
414     import mir.format: stringBuf;
415     stringBuf w;
416     assert(w.printEscaped("Hi \a\v\0\f\t\b \\\r\n" ~ `"@nogc"`).data == `Hi \a\v\0\f\t\b \\\r\n\"@nogc\"`);
417     w.reset;
418     assert(w.printEscaped("\x03").data == `\x03`, w.data);
419 }
420 
421 /++
422 Decodes `char` `c` to the form `u00XX`, where `XX` is 2 hexadecimal characters.
423 +/
424 ref W put_xXX(C = char, W)(scope return ref W w, char c)
425     if (isSomeChar!C)
426 {
427     ubyte[2] spl;
428     spl[0] = c >> 4;
429     spl[1] = c & 0xF;
430     C[4] buffer;
431     buffer[0] = '\\';
432     buffer[1] = 'x';
433     buffer[2] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A');
434     buffer[3] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A');
435     return w.printStaticString(buffer);
436 }
437 
438 /++
439 Decodes `char` `c` to the form `u00XX`, where `XX` is 2 hexadecimal characters.
440 +/
441 ref W put_uXXXX(C = char, W)(scope return ref W w, char c)
442     if (isSomeChar!C)
443 {
444     ubyte[2] spl;
445     spl[0] = c >> 4;
446     spl[1] = c & 0xF;
447     C[6] buffer;
448     buffer[0] = '\\';
449     buffer[1] = 'u';
450     buffer[2] = '0';
451     buffer[3] = '0';
452     buffer[4] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A');
453     buffer[5] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A');
454     return w.printStaticString(buffer);
455 }
456 
457 /++
458 Decodes ushort `c` to the form `uXXXX`, where `XXXX` is 2 hexadecimal characters.
459 +/
460 ref W put_uXXXX(C = char, W)(scope return ref W w, ushort c)
461     if (isSomeChar!C)
462 {
463     ubyte[4] spl;
464     spl[0] = (c >> 12) & 0xF;
465     spl[1] = (c >> 8) & 0xF;
466     spl[2] = (c >> 4) & 0xF;
467     spl[3] = c & 0xF;
468     C[6] buffer;
469     buffer[0] = '\\';
470     buffer[1] = 'u';
471     buffer[2] = cast(ubyte)(spl[0] < 10 ? spl[0] + '0' : spl[0] - 10 + 'A');
472     buffer[3] = cast(ubyte)(spl[1] < 10 ? spl[1] + '0' : spl[1] - 10 + 'A');
473     buffer[4] = cast(ubyte)(spl[2] < 10 ? spl[2] + '0' : spl[2] - 10 + 'A');
474     buffer[5] = cast(ubyte)(spl[3] < 10 ? spl[3] + '0' : spl[3] - 10 + 'A');
475     return w.printStaticString(buffer);
476 }
477 
478 ///
479 ref W printElement(C, EscapeFormat escapeFormat = EscapeFormat.ion, W)(scope return ref W w, scope const(C)[] c)
480     if (isSomeChar!C)
481 {
482     static immutable C[1] quote = '\"';
483     return w
484         .printStaticString!C(quote)
485         .printEscaped!(C, escapeFormat)(c)
486         .printStaticString!C(quote);
487 }
488 
489 ///
490 ref W printElement(C = char, EscapeFormat escapeFormat = EscapeFormat.ion, W, T)(scope return ref W w, scope auto ref const T c)
491     if (!isSomeString!T)
492 {
493     return w.print!C(c);
494 }
495 
496 /++
497 Multiargument overload.
498 +/
499 ref W print(C = char, W, Args...)(scope return ref W w, scope auto ref const Args args)
500     if (isSomeChar!C && Args.length > 1)
501 {
502     foreach(i, ref c; args)
503         static if (i < Args.length - 1)
504             w.print!C(c);
505         else
506             return w.print!C(c);
507 }
508 
509 /// Prints enums
510 ref W print(C = char, W, T)(scope return ref W w, const T c)
511     if (isSomeChar!C && is(T == enum))
512 {
513     import mir.enums: getEnumIndex, enumStrings;
514     import mir.utility: _expect;
515 
516     static assert(!is(OriginalType!T == enum));
517     uint index = void;
518     if (getEnumIndex(c, index)._expect(true))
519     {
520         w.put(enumStrings!T[index]);
521         return w;
522     }
523     static immutable C[] str = T.stringof ~ "(";
524     w.put(str[]);
525     print!C(w, cast(OriginalType!T) c);
526     w.put(')');
527     return w;
528 }
529 
530 ///
531 @safe pure nothrow @nogc
532 version (mir_test) unittest
533 {
534     enum Flag
535     {
536         no,
537         yes,
538     }
539 
540     import mir.appender: scopedBuffer;
541     auto w = scopedBuffer!char;
542     w.print(Flag.yes);
543     assert(w.data == "yes", w.data);
544 }
545 
546 /// Prints boolean
547 ref W print(C = char, W)(scope return ref W w, bool c)
548     if (isSomeChar!C)
549 {
550     enum N = 5;
551     static if(isFastBuffer!W)
552     {
553         w.advance(printBoolean(c, w.getStaticBuf!N));
554     }
555     else
556     {
557         C[N] buf = void;
558         auto n = printBoolean(c, buf);
559         w.put(buf[0 .. n]);
560     }
561     return w;
562 }
563 
564 ///
565 @safe pure nothrow @nogc
566 version (mir_test) unittest
567 {
568     import mir.appender: scopedBuffer;
569     auto w = scopedBuffer!char;
570     assert(w.print(true).data == `true`, w.data);
571     w.reset;
572     assert(w.print(false).data == `false`, w.data);
573 }
574 
575 /// Prints associative array
576 pragma(inline, false)
577 ref W print(C = char, W, V, K)(scope return ref W w, scope const V[K] c)
578     if (isSomeChar!C)
579 {
580     enum C left = '[';
581     enum C right = ']';
582     enum C[2] sep = ", ";
583     enum C[2] mid = ": ";
584     w.put(left);
585     bool first = true;
586     foreach (ref key, ref value; c)
587     {
588         if (!first)
589             w.printStaticString!C(sep);
590         first = false;
591         w
592             .printElement!C(key)
593             .printStaticString!C(mid)
594             .printElement!C(value);
595     }
596     w.put(right);
597     return w;
598 }
599 
600 ///
601 @safe pure
602 version (mir_test) unittest
603 {
604     import mir.appender: scopedBuffer;
605     auto w = scopedBuffer!char;
606     w.print(["a": 1, "b": 2]);
607     assert(w.data == `["a": 1, "b": 2]` || w.data == `["b": 2, "a": 1]`, w.data);
608 }
609 
610 /// Prints array
611 pragma(inline, false)
612 ref W print(C = char, W, T)(scope return ref W w, scope const(T)[] c)
613     if (isSomeChar!C && !isSomeChar!T)
614 {
615     enum C left = '[';
616     enum C right = ']';
617     enum C[2] sep = ", ";
618     w.put(left);
619     bool first = true;
620     foreach (ref e; c)
621     {
622         if (!first)
623             w.printStaticString!C(sep);
624         first = false;
625         printElement!C(w, e);
626     }
627     w.put(right);
628     return w;
629 }
630 
631 ///
632 @safe pure nothrow @nogc
633 version (mir_test) unittest
634 {
635     import mir.appender: scopedBuffer;
636     auto w = scopedBuffer!char;
637     string[2] array = ["a\na", "b"];
638     assert(w.print(array[]).data == `["a\na", "b"]`, w.data);
639 }
640 
641 /// Prints escaped character in the form `'c'`.
642 pragma(inline, false)
643 ref W print(C = char, W)(scope return ref W w, char c)
644     if (isSomeChar!C)
645 {
646     w.put('\'');
647     if (c >= 0x20)
648     {
649         if (c < 0x7F)
650         {
651             if (c == '\'' || c == '\\')
652             {
653             L:
654                 w.put('\\');
655             }
656             w.put(c);
657         }
658         else
659         {
660         M:
661             w.printStaticString!C(`\x`);
662             w.print!C(HexAddress!ubyte(cast(ubyte)c));
663         }
664     }
665     else
666     {
667         switch(c)
668         {
669             case '\n': c = 'n'; goto L;
670             case '\r': c = 'r'; goto L;
671             case '\t': c = 't'; goto L;
672             case '\a': c = 'a'; goto L;
673             case '\b': c = 'b'; goto L;
674             case '\f': c = 'f'; goto L;
675             case '\v': c = 'v'; goto L;
676             case '\0': c = '0'; goto L;
677             default: goto M;
678         }
679     }
680     w.put('\'');
681     return w;
682 }
683 
684 ///
685 @safe pure nothrow @nogc
686 version (mir_test) unittest
687 {
688     import mir.appender: scopedBuffer;
689     auto w = scopedBuffer!char;
690     assert(w
691         .print('\n')
692         .print('\'')
693         .print('a')
694         .print('\xF4')
695         .data == `'\n''\'''a''\xF4'`);
696 }
697 
698 /// Prints some string
699 ref W print(C = char, W)(scope return ref W w, scope const(C)[] c)
700     if (isSomeChar!C)
701 {
702     w.put(c);
703     return w;
704 }
705 
706 /// Prints integers
707 ref W print(C = char, W, I)(scope return ref W w, const I c)
708     if (isSomeChar!C && isIntegral!I && !is(I == enum))
709 {
710     static if (I.sizeof == 16)
711         enum N = 39;
712     else
713     static if (I.sizeof == 8)
714         enum N = 20;
715     else
716         enum N = 10;
717     C[N + !__traits(isUnsigned, I)] buf = void;
718     static if (__traits(isUnsigned, I))
719         auto n = printUnsignedToTail(c, buf);
720     else
721         auto n = printSignedToTail(c, buf);
722     w.put(buf[$ - n ..  $]);
723     return w;
724 }
725 
726 /// Prints floating point numbers
727 ref W print(C = char, W, T)(scope return ref W w, const T c, NumericSpec spec = NumericSpec.init)
728     if(isSomeChar!C && is(T == float) || is(T == double) || is(T == real))
729 {
730     import mir.bignum.decimal;
731     auto decimal = Decimal!(T.mant_dig < 64 ? 1 : 2)(c);
732     decimal.toString(w, spec);
733     return w;
734 }
735 
736 /// Human friendly precise output (default)
737 version(mir_bignum_test)
738 @safe pure nothrow @nogc
739 unittest
740 {
741     auto spec = NumericSpec.human;
742     stringBuf buffer;
743 
744     void check(double num, string value)
745     {
746         assert(buffer.print(num, spec).data == value, buffer.data); buffer.reset;
747     }
748 
749     check(-0.0, "-0.0");
750     check(0.0, "0.0");
751     check(-0.01, "-0.01");
752     check(0.0125, "0.0125");
753     check(0.000003, "0.000003");
754     check(-3e-7, "-3e-7");
755     check(123456.0, "123456.0");
756     check(123456.1, "123456.1");
757     check(12.3456, "12.3456");
758     check(-0.123456, "-0.123456");
759     check(0.1234567, "0.1234567");
760     check(0.01234567, "0.01234567");
761     check(0.001234567, "0.001234567");
762     check(1.234567e-4, "1.234567e-4");
763     check(-1234567.0, "-1.234567e+6");
764     check(123456.7890123, "123456.7890123");
765     check(1234567.890123, "1.234567890123e+6");
766     check(1234567890123.0, "1.234567890123e+12");
767     check(0.30000000000000004, "0.30000000000000004");
768     check(0.030000000000000002, "0.030000000000000002");
769     check(0.0030000000000000005, "0.0030000000000000005");
770     check(3.0000000000000003e-4, "3.0000000000000003e-4");
771     check(+double.nan, "nan");
772     check(-double.nan, "nan");
773     check(+double.infinity, "+inf");
774     check(-double.infinity, "-inf");
775 
776     spec.separatorChar = ',';
777 
778     check(-0.0, "-0.0");
779     check(0.0, "0.0");
780     check(-0.01, "-0.01");
781     check(0.0125, "0.0125");
782     check(0.000003, "0.000003");
783     check(-3e-7, "-3e-7");
784     check(123456.0, "123,456.0");
785     check(123456e5, "12,345,600,000.0");
786     check(123456.1, "123,456.1");
787     check(12.3456, "12.3456");
788     check(-0.123456, "-0.123456");
789     check(0.1234567, "0.1234567");
790     check(0.01234567, "0.01234567");
791     check(0.001234567, "0.001234567");
792     check(1.234567e-4, "0.0001234567");
793     check(-1234567.0, "-1,234,567.0");
794     check(123456.7890123, "123,456.7890123");
795     check(1234567.890123, "1,234,567.890123");
796     check(123456789012.0, "123,456,789,012.0");
797     check(1234567890123.0, "1.234567890123e+12");
798     check(0.30000000000000004, "0.30000000000000004");
799     check(0.030000000000000002, "0.030000000000000002");
800     check(0.0030000000000000005, "0.0030000000000000005");
801     check(3.0000000000000003e-4, "0.00030000000000000003");
802     check(3.0000000000000005e-6, "0.0000030000000000000005");
803     check(3.0000000000000004e-7, "3.0000000000000004e-7");
804     check(+double.nan, "nan");
805     check(-double.nan, "nan");
806     check(+double.infinity, "+inf");
807     check(-double.infinity, "-inf");
808 
809     spec.separatorChar = '_';
810     spec.separatorCount = 2;
811     check(123456e5, "1_23_45_60_00_00.0");
812 
813     spec.plus = true;
814     check(0.0125, "+0.0125");
815     check(-0.0125, "-0.0125");
816 }
817 
818 /// Prints structs and unions
819 pragma(inline, false)
820 ref W print(C = char, W, T)(scope return ref W w, ref const T c)
821     if (isSomeChar!C && is(T == struct) || is(T == union) && !is(T : NumericSpec))
822 {
823     static if (__traits(hasMember, T, "toString"))
824     {
825         static if (is(typeof(c.toString!C(w))))
826             c.toString!C(w);
827         else
828         static if (is(typeof(c.toString(w))))
829             c.toString(w);
830         else
831         static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); }))))
832             c.toString((scope const(C)[] s) { w.put(s); });
833         else
834         static if (is(typeof(w.put(c.toString))))
835             w.put(c.toString);
836         else static assert(0, T.stringof ~ ".toString definition is wrong: 'const' qualifier may be missing.");
837         return w;
838     }
839     else
840     static if (__traits(compiles, { scope const(C)[] string_of_c = c; }))
841     {
842         scope const(C)[] string_of_c = c;
843         return w.print!C(string_of_c);
844     }
845     else
846     static if (hasIterableLightConst!T)
847     {
848         enum C left = '[';
849         enum C right = ']';
850         enum C[2] sep = ", ";
851         w.put(left);
852         bool first = true;
853         foreach (ref e; c.lightConst)
854         {
855             if (!first)
856                 printStaticString!C(w, sep);
857             first = false;
858             print!C(w, e);
859         }
860         w.put(right);
861         return w;
862     }
863     else
864     {
865         enum C left = '(';
866         enum C right = ')';
867         enum C[2] sep = ", ";
868         w.put(left);
869         foreach (i, ref e; c.tupleof)
870         {
871             static if (i)
872                 w.printStaticString!C(sep);
873             print!C(w, e);
874         }
875         w.put(right);
876         return w;
877     }
878 }
879 
880 /// ditto
881 // FUTURE: remove it
882 pragma(inline, false)
883 ref W print(C = char, W, T)(scope return ref W w, scope const T c)
884     if (isSomeChar!C && is(T == struct) || is(T == union))
885 {
886     return print!(C, W, T)(w, c);
887 }
888 
889 ///
890 @safe pure nothrow @nogc
891 version (mir_test) unittest
892 {
893     static struct A { scope void toString(C, W)(scope ref W w) const { w.put(C('a')); } }
894     static struct S { scope void toString(W)(scope ref W w) const { w.put("s"); } }
895     static struct D { scope void toString(Dg)(scope Dg sink) const { sink("d"); } }
896     static struct F { scope const(char)[] toString()() const return { return "f"; } }
897     static struct G { const(char)[] s = "g"; alias s this; }
898 
899     import mir.appender: scopedBuffer;
900     auto w = scopedBuffer!char;
901     assert(stringBuf() << A() << S() << D() << F() << G() << getData == "asdfg");
902 }
903 
904 /// Prints classes and interfaces
905 pragma(inline, false)
906 ref W print(C = char, W, T)(scope return ref W w, scope const T c)
907     if (isSomeChar!C && is(T == class) || is(T == interface))
908 {
909     enum C[4] Null = "null";
910     static if (__traits(hasMember, T, "toString") || __traits(compiles, { scope const(C)[] string_of_c = c; }))
911     {
912         if (c is null)
913             w.printStaticString!C(Null);
914         else
915         static if (is(typeof(c.toString!C(w))))
916             c.toString!C(w);
917         else
918         static if (is(typeof(c.toString(w))))
919             c.toString(w);
920         else
921         static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); }))))
922             c.toString((scope const(C)[] s) { w.put(s); });
923         else
924         static if (is(typeof(w.put(c.toString))))
925             w.put(c.toString);
926         else
927         static if (__traits(compiles, { scope const(C)[] string_of_c = c; }))
928         {
929             scope const(C)[] string_of_c = c;
930             return w.print!C(string_of_c);
931         }
932         else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing.");
933     }
934     else
935     static if (hasIterableLightConst!T)
936     {
937         enum C left = '[';
938         enum C right = ']';
939         enum C[2] sep = ", ";
940         w.put(left);
941         bool first = true;
942         foreach (ref e; c.lightConst)
943         {
944             if (!first)
945                 w.printStaticString!C(sep);
946             first = false;
947             print!C(w, e);
948         }
949         w.put(right);
950     }
951     else
952     {
953         w.put(T.stringof);
954     }
955     return w;
956 }
957 
958 ///
959 @safe pure nothrow
960 version (mir_test) unittest
961 {
962     static class A { void toString(C, W)(scope ref W w) const { w.put(C('a')); } }
963     static class S { void toString(W)(scope ref W w) const { w.put("s"); } }
964     static class D { void toString(Dg)(scope Dg sink) const { sink("d"); } }
965     static class F { const(char)[] toString()() const return { return "f"; } }
966     static class G { const(char)[] s = "g"; alias s this; }
967 
968     assert(stringBuf() << new A() << new S() << new D() << new F() << new G() << getData == "asdfg");
969 }
970 
971 ///
972 ref W printStaticString(C, size_t N, W)(scope return ref W w, ref scope const C[N] c)
973     if (isSomeChar!C && is(C == char) || is(C == wchar) || is(C == dchar))
974 {
975     static if (isFastBuffer!W)
976     {
977         enum immutable(ForeachType!(typeof(w.getBuffer(size_t.init))))[] value = c;
978         w.getStaticBuf!(value.length) = value;
979         w.advance(c.length);
980     }
981     else
982     {
983         w.put(c[]);
984     }
985     return w;
986 }
987 
988 private template hasIterableLightConst(T)
989 {
990     static if (__traits(hasMember, T, "lightConst"))
991     {
992         enum hasIterableLightConst = isIterable!(ReturnType!((const T t) => t.lightConst));
993     }
994     else
995     {
996         enum hasIterableLightConst = false;
997     }
998 }
999 
1000 private ref C[N] getStaticBuf(size_t N, C, W)(scope return ref W w)
1001     if (isFastBuffer!W)
1002 {
1003     auto buf = w.getBuffer(N);
1004     assert(buf.length >= N);
1005     return buf.ptr[0 .. N];
1006 }
1007 
1008 private @trusted ref C[N] getStaticBuf(size_t N, C)(scope return ref C[] buf)
1009 {
1010     assert(buf.length >= N);
1011     return buf.ptr[0 .. N];
1012 }
1013 
1014 template isFastBuffer(W)
1015 {
1016     enum isFastBuffer = __traits(hasMember, W, "getBuffer") && __traits(hasMember, W, "advance");
1017 }
1018 
1019 ///
1020 ref W printZeroPad(C = char, W, I)(scope return ref W w, const I c, size_t minimalLength)
1021     if (isSomeChar!C && isIntegral!I && !is(I == enum))
1022 {
1023     static if (I.sizeof == 16)
1024         enum N = 39;
1025     else
1026     static if (I.sizeof == 8)
1027         enum N = 20;
1028     else
1029         enum N = 10;
1030     C[N + !__traits(isUnsigned, I)] buf = void;
1031     static if (__traits(isUnsigned, I))
1032         auto n = printUnsignedToTail(c, buf);
1033     else
1034         auto n = printSignedToTail(c, buf);
1035     sizediff_t zeros = minimalLength - n;
1036 
1037     if (zeros > 0)
1038     {
1039         static if (!__traits(isUnsigned, I))
1040         {
1041             if (c < 0)
1042             {
1043                 n--;
1044                 w.put(C('-'));
1045             }
1046         }
1047         do w.put(C('0'));
1048         while(--zeros);
1049     }
1050     w.put(buf[$ - n ..  $]);
1051     return w;
1052 }
1053 
1054 ///
1055 version (mir_test) unittest
1056 {
1057     import mir.appender;
1058     auto w = scopedBuffer!char;
1059 
1060     w.printZeroPad(-123, 5);
1061     w.put(' ');
1062     w.printZeroPad(123, 5);
1063 
1064     assert(w.data == "-0123 00123", w.data);
1065 }
1066 
1067 ///
1068 size_t printBoolean(C)(bool c, ref C[5] buf)
1069     if(is(C == char) || is(C == wchar) || is(C == dchar))
1070 {
1071     version(LDC) pragma(inline, true);
1072     if (c)
1073     {
1074         buf[0] = 't';
1075         buf[1] = 'r';
1076         buf[2] = 'u';
1077         buf[3] = 'e';
1078         return 4;
1079     }
1080     else
1081     {
1082         buf[0] = 'f';
1083         buf[1] = 'a';
1084         buf[2] = 'l';
1085         buf[3] = 's';
1086         buf[4] = 'e';
1087         return 5;
1088     }
1089 }