1 /++
2 +/
3 module mir.format;
4 
5 import std.traits;
6 
7 import mir.format_impl;
8 
9 ///
10 struct GetData {}
11 
12 ///
13 enum getData = GetData();
14 
15 /++
16 +/
17 struct _stringBuf(C)
18 {
19     import mir.appender: ScopedBuffer;
20 
21     ///
22     ScopedBuffer!C buffer;
23 
24     ///
25     alias buffer this;
26 
27     ///
28     mixin StreamFormatOp!C;
29 }
30 
31 ///ditto
32 alias stringBuf = _stringBuf!char;
33 ///ditto
34 alias wstringBuf = _stringBuf!wchar;
35 ///ditto
36 alias dstringBuf = _stringBuf!dchar;
37 
38 /++
39 +/
40 mixin template StreamFormatOp(C)
41 {
42     ///
43     ref typeof(this) opBinary(string op : "<<", T)(scope ref const T c) scope
44     {
45         return print!C(this, c);
46     }
47 
48     ///
49     ref typeof(this) opBinary(string op : "<<", T)(const T c) scope
50     {
51         return print!C(this, c);
52     }
53 
54     /// ditto
55     const(C)[] opBinary(string op : "<<", T : GetData)(const T c) scope
56     {
57         return buffer.data;
58     }
59 }
60 
61 ///
62 @safe pure nothrow @nogc
63 unittest
64 {
65     auto name = "D";
66     auto ver = 2.0;
67     assert(stringBuf() << "Hi " << name << ver << "!\n" << getData == "Hi D2!\n");
68 }
69 
70 ///
71 @safe pure nothrow @nogc
72 unittest
73 {
74     auto name = "D"w;
75     auto ver = 2;
76     assert(wstringBuf() << "Hi "w << name << ver << "!\n"w << getData == "Hi D2!\n"w);
77 }
78 
79 ///
80 @safe pure nothrow @nogc
81 unittest
82 {
83     auto name = "D"d;
84     auto ver = 2;
85     assert(dstringBuf() << "Hi "d  << name << ver << "!\n"d << getData == "Hi D2!\n");
86 }
87 
88 @safe pure nothrow @nogc
89 unittest
90 {
91     assert(stringBuf() << -1234567890 << getData == "-1234567890");
92 }
93 
94 // 16-bytes
95 /// C's compatible format specifier.
96 struct FormatSpec
97 {
98     ///
99     bool dash;
100     ///
101     bool plus;
102     ///
103     bool space;
104     ///
105     bool hash;
106     ///
107     bool zero;
108     ///
109     char format = 's';
110     ///
111     char separator = '\0';
112     ///
113     ubyte unitSize;
114     ///
115     int width;
116     ///
117     int precision = -1;
118 }
119 
120 /++
121 +/
122 enum SwitchLU : bool
123 {
124     ///
125     lower,
126     ///
127     upper,
128 }
129 
130 /++
131 +/
132 struct FormattedFloating(T)
133     if(is(T == float) || is(T == double) || is(T == real))
134 {
135     ///
136     T value;
137     ///
138     FormatSpec spec;
139 
140     ///
141     void toString(C = char, W)(scope ref W w) scope const
142     {
143         C[512] buf = void;
144         auto n = printFloatingPoint(value, spec, buf);
145         w.put(buf[0 ..  n]);
146     }
147 }
148 
149 /// ditto
150 FormattedFloating!T withFormat(T)(const T value, FormatSpec spec)
151 {
152     version(LDC) pragma(inline);
153     return typeof(return)(value, spec);
154 }
155 
156 /++
157 +/
158 struct HexAddress(T)
159     if (isUnsigned!T && !is(T == enum))
160 {
161     ///
162     T value;
163     ///
164     SwitchLU switchLU = SwitchLU.upper;
165 
166     ///
167     void toString(C = char, W)(scope ref W w) scope const
168     {
169         enum N = T.sizeof * 2;
170         static if(isFastBuffer!W)
171         {
172             w.advance(printHexAddress(value, w.getBuffer(N).getStaticBuf!N, cast(bool) switchLU));
173         }
174         else
175         { 
176             C[N] buf = void;
177             printHexAddress(value, buf, cast(bool) switchLU);
178             w.put(buf[]);
179         }
180     }
181 }
182 
183 /++
184 Note: Non-ASCII Unicode characters are encoded as sequence of \xXX bytes. This may be fixed in the future.
185 +/
186 pragma(inline, false)
187 ref W printEscaped(C = char, W)(scope return ref W w, scope const(char)[] str)
188 {
189     // TODO: replace with Mir implementation.
190     import std.uni: isGraphical;
191     w.put('\"');
192     foreach (char c; str[])
193     {
194         if (c >= 0x20)
195         {
196             if (c < 0x7F)
197             {
198                 if (c == '\"' || c == '\\')
199                 {
200                 L:
201                     w.put('\\');
202                 }
203                 w.put(c);
204             }
205             else
206             {
207             M:
208                 printStaticStringInternal!(C, "\\x")(w);
209                 print!C(w, HexAddress!ubyte(cast(ubyte)c));
210             }
211         }
212         else
213         {
214             switch(c)
215             {
216                 case '\n': c = 'n'; goto L;
217                 case '\r': c = 'r'; goto L;
218                 case '\t': c = 't'; goto L;
219                 case '\a': c = 'a'; goto L;
220                 case '\b': c = 'b'; goto L;
221                 case '\f': c = 'f'; goto L;
222                 case '\v': c = 'v'; goto L;
223                 case '\0': c = '0'; goto L;
224                 default: goto M;
225             }
226         }
227     }
228     w.put('\"');
229     return w;
230 }
231 
232 ///
233 @safe pure nothrow @nogc
234 unittest
235 {
236     import mir.appender: ScopedBuffer;
237     ScopedBuffer!char w;
238     assert(w.printEscaped("Hi\t" ~ `"@nogc"`).data == `"Hi\t\"@nogc\""`, w.data);
239     w.reset;
240     assert(w.printEscaped("\xF3").data == `"\xF3"`, w.data);
241 }
242 
243 ///
244 ref W printElement(C = char, W, T)(scope return ref W w, scope auto ref const T c)
245 {
246     static if (isSomeString!T)
247     {
248         return printEscaped!C(w, c);
249     }
250     else
251     {
252         return print!C(w, c);
253     }
254 }
255 
256 /++
257 Multiargument overload.
258 +/
259 ref W print(C = char, W, Args...)(scope return ref W w, scope auto ref const Args args)
260     if (Args.length > 1)
261 {
262     foreach(i, ref c; args)
263         static if (i < Args.length - 1)
264             print!C(w, c);
265         else
266             return print!C(w, c);
267 }
268 
269 ///
270 ref W print(C = char, W, T)(scope return ref W w, const T c)
271     if (is(T == enum))
272 {
273     static assert(!is(OriginalType!T == enum));
274     string s;
275     S: switch (c)
276     {
277         static foreach(member; __traits(allMembers, T))
278         {
279             case __traits(getMember, T, member):
280             s = member;
281             break S;
282         }
283         default:
284             static immutable C[] str = T.stringof;
285             w.put(str[]);
286             w.put('(');
287             print(w, cast(OriginalType!T) c);
288             w.put(')');
289             return w;
290     }
291     w.put(s);
292     return w;
293 }
294 
295 ///
296 @safe pure nothrow @nogc
297 unittest
298 {
299     enum Flag
300     {
301         no,
302         yes,
303     }
304 
305     import mir.appender: ScopedBuffer;
306     ScopedBuffer!char w;
307     w.print(Flag.yes);
308     assert(w.data == "yes", w.data);
309 }
310 
311 /// ditto
312 ref W print(C = char, W)(scope return ref W w, bool c)
313 {
314     enum N = 5;
315     static if(isFastBuffer!W)
316     {
317         w.advance(printBoolean(c, w.getBuffer(N).getStaticBuf!N));
318     }
319     else
320     {
321         C[N] buf = void;
322         auto n = printBoolean(c, buf);
323         w.put(buf[0 .. n]);
324     }
325     return w;
326 }
327 
328 ///
329 @safe pure nothrow @nogc
330 unittest
331 {
332     import mir.appender: ScopedBuffer;
333     ScopedBuffer!char w;
334     assert(w.print(true).data == `true`, w.data);
335     w.reset;
336     assert(w.print(false).data == `false`, w.data);
337 }
338 
339 /// ditto
340 pragma(inline, false)
341 ref W print(C = char, W, V, K)(scope return ref W w, scope const V[K] c)
342 {
343     enum C left = '[';
344     enum C right = ']';
345     enum C[2] sep = ", ";
346     enum C[2] mid = ": ";
347     w.put(left);
348     bool first = true;
349     foreach (ref key, ref value; c)
350     {
351         if (!first)
352             printStaticStringInternal!(C, sep)(w);
353         first = false;
354         printElement!C(w, key);
355         printStaticStringInternal!(C, mid)(w);
356         printElement!C(w, value);
357     }
358     w.put(right);
359     return w;
360 }
361 
362 ///
363 @safe pure
364 unittest
365 {
366     import mir.appender: ScopedBuffer;
367     ScopedBuffer!char w;
368     w.print(["a": 1, "b": 2]);
369     assert(w.data == `["a": 1, "b": 2]` || w.data == `["b": 2, "a": 1]`, w.data);
370 }
371 
372 /// ditto
373 pragma(inline, false)
374 ref W print(C = char, W, T)(scope return ref W w, scope const(T)[] c)
375     if (!isSomeChar!T)
376 {
377     enum C left = '[';
378     enum C right = ']';
379     enum C[2] sep = ", ";
380     w.put(left);
381     bool first = true;
382     foreach (ref e; c)
383     {
384         if (!first)
385             printStaticStringInternal!(C, sep)(w);
386         first = false;
387         printElement!C(w, e);
388     }
389     w.put(right);
390     return w;
391 }
392 
393 ///
394 @safe pure nothrow @nogc
395 unittest
396 {
397     import mir.appender: ScopedBuffer;
398     ScopedBuffer!char w;
399     string[2] array = ["a\ta", "b"];
400     assert(w.print(array[]).data == `["a\ta", "b"]`, w.data);
401 }
402 
403 /// ditto
404 pragma(inline, false)
405 ref W print(C = char, W)(scope return ref W w, char c)
406 {
407     w.put('\'');
408     if (c >= 0x20)
409     {
410         if (c < 0x7F)
411         {
412             if (c == '\'' || c == '\\')
413             {
414             L:
415                 w.put('\\');
416             }
417             w.put(c);
418         }
419         else
420         {
421         M:
422             printStaticStringInternal!(C, "\\x")(w);
423             print!C(w, HexAddress!ubyte(cast(ubyte)c));
424         }
425     }
426     else
427     {
428         switch(c)
429         {
430             case '\n': c = 'n'; goto L;
431             case '\r': c = 'r'; goto L;
432             case '\t': c = 't'; goto L;
433             case '\a': c = 'a'; goto L;
434             case '\b': c = 'b'; goto L;
435             case '\f': c = 'f'; goto L;
436             case '\v': c = 'v'; goto L;
437             case '\0': c = '0'; goto L;
438             default: goto M;
439         }
440     }
441     w.put('\'');
442     return w;
443 }
444 
445 ///
446 @safe pure nothrow @nogc
447 unittest
448 {
449     import mir.appender: ScopedBuffer;
450     ScopedBuffer!char w;
451     assert(w
452         .print('\n')
453         .print('\'')
454         .print('a')
455         .print('\xF4')
456         .data == `'\n''\'''a''\xF4'`);
457 }
458 
459 /// ditto
460 ref W print(C = char, W)(scope return ref W w, scope const(C)[] c)
461     if (isSomeChar!C)
462 {
463     w.put(c);
464     return w;
465 }
466 
467 /// ditto
468 ref W print(C = char, W, I)(scope return ref W w, const I c)
469     if (isIntegral!I && !is(I == enum))
470 {
471     static if (I.sizeof == 16)
472         enum N = 39;
473     else
474     static if (I.sizeof == 8)
475         enum N = 20;
476     else
477         enum N = 10;
478     C[N + !__traits(isUnsigned, I)] buf = void;
479     static if (__traits(isUnsigned, I))
480         auto n = printUnsignedToTail(c, buf);
481     else
482         auto n = printSignedToTail(c, buf);
483     w.put(buf[$ - n ..  $]);
484     return w;
485 }
486 
487 /// ditto
488 ref W print(C = char, W, T)(scope return ref W w, const T c)
489     if(is(T == float) || is(T == double) || is(T == real))
490 {
491     auto ff = FormattedFloating!T(c);
492     return print!C(w, ff);
493 }
494 
495 /// ditto
496 pragma(inline, false)
497 ref W print(C = char, W, T)(scope return ref W w, scope ref const T c)
498     if (is(T == struct) || is(T == union))
499 {
500     static if (__traits(hasMember, T, "toString"))
501     {
502         static if (is(typeof(c.toString!C(w))))
503             c.toString!C(w);
504         else
505         static if (is(typeof(c.toString(w))))
506             c.toString(w);
507         else
508         static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); }))))
509             c.toString((scope const(C)[] s) { w.put(s); });
510         else
511         static if (is(typeof(w.put(c.toString))))
512             w.put(c.toString);
513         else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing.");
514         return w;
515     }
516     else
517     static if (__traits(compiles, { scope const(C)[] string_of_c = c; }))
518     {
519         scope const(C)[] string_of_c = c;
520         return print(w, string_of_c);
521     }
522     else
523     static if (hasIterableLightConst!T)
524     {
525         enum C left = '[';
526         enum C right = ']';
527         enum C[2] sep = ", ";
528         w.put(left);
529         bool first = true;
530         foreach (ref e; c.lightConst)
531         {
532             if (!first)
533                 printStaticStringInternal!(C, sep)(w);
534             first = false;
535             print!C(w, e);
536         }
537         w.put(right);
538         return w;
539     }
540     else
541     {
542         enum C left = '(';
543         enum C right = ')';
544         enum C[2] sep = ", ";
545         w.put(left);
546         foreach (i, ref e; c.tupleof)
547         {
548             static if (i)
549                 printStaticStringInternal!(C, sep)(w);
550             print!C(w, e);
551         }
552         w.put(right);
553         return w;
554     }
555 }
556 
557 /// ditto
558 // FUTURE: remove it
559 pragma(inline, false)
560 ref W print(C = char, W, T)(scope return ref W w, scope const T c)
561     if (is(T == struct) || is(T == union))
562 {
563     return print!(C, W, T)(w, c);
564 }
565 
566 ///
567 @safe pure nothrow @nogc
568 unittest
569 {
570     static struct A { scope const void toString(C, W)(scope ref W w) { w.put(C('a')); } }
571     static struct S { scope const void toString(W)(scope ref W w) { w.put("s"); } }
572     static struct D { scope const void toString(Dg)(scope Dg sink) { sink("d"); } }
573     static struct F { scope const const(char)[] toString()() return { return "f"; } }
574     static struct G { const(char)[] s = "g"; alias s this; }
575 
576     import mir.appender: ScopedBuffer;
577     ScopedBuffer!char w;
578     assert(stringBuf() << A() << S() << D() << F() << G() << getData == "asdfg");
579 }
580 
581 /// ditto
582 pragma(inline, false)
583 ref W print(C = char, W, T)(scope return ref W w, scope const T c)
584     if (is(T == class) || is(T == interface))
585 {
586     static if (__traits(hasMember, T, "toString") || __traits(compiles, { scope const(C)[] string_of_c = c; }))
587     {
588         if (c is null)
589             printStaticStringInternal!(C, "null")(w);
590         else
591         static if (is(typeof(c.toString!C(w))))
592             c.toString!C(w);
593         else
594         static if (is(typeof(c.toString(w))))
595             c.toString(w);
596         else
597         static if (is(typeof(c.toString((scope const(C)[] s) { w.put(s); }))))
598             c.toString((scope const(C)[] s) { w.put(s); });
599         else
600         static if (is(typeof(w.put(c.toString))))
601             w.put(c.toString);
602         else
603         static if (__traits(compiles, { scope const(C)[] string_of_c = c; }))
604         {
605             scope const(C)[] string_of_c = c;
606             return print(w, string_of_c);
607         }
608         else static assert(0, T.stringof ~ ".toString definition is wrong: 'const scope' qualifier may be missing.");
609     }
610     else
611     static if (hasIterableLightConst!T)
612     {
613         enum C left = '[';
614         enum C right = ']';
615         enum C[2] sep = ", ";
616         w.put(left);
617         bool first = true;
618         foreach (ref e; c.lightConst)
619         {
620             if (!first)
621                 printStaticStringInternal!(C, sep)(w);
622             first = false;
623             print!C(w, e);
624         }
625         w.put(right);
626     }
627     else
628     {
629         w.put(T.stringof);
630     }
631     return w;
632 }
633 
634 ///
635 @safe pure nothrow
636 unittest
637 {
638     static class A { scope const void toString(C, W)(scope ref W w) { w.put(C('a')); } }
639     static class S { scope const void toString(W)(scope ref W w) { w.put("s"); } }
640     static class D { scope const void toString(Dg)(scope Dg sink) { sink("d"); } }
641     static class F { scope const const(char)[] toString()() return { return "f"; } }
642     static class G { const(char)[] s = "g"; alias s this; }
643 
644     import mir.appender: ScopedBuffer;
645     ScopedBuffer!char w;
646     assert(stringBuf() << new A() << new S() << new D() << new F() << new G() << getData == "asdfg");
647 }
648 
649 private template hasIterableLightConst(T)
650 {
651     static if (__traits(hasMember, T, "lightConst"))
652     {
653         enum hasIterableLightConst = isIterable!(ReturnType!((const T t) => t.lightConst));
654     }
655     else
656     {
657         enum hasIterableLightConst = false;
658     }
659 }
660 
661 private ref W printStaticStringInternal(C, immutable(C)[] c, W)(scope return ref W w)
662     if (C.sizeof * c.length <= 512)
663 {
664     static if (isFastBuffer!W)
665     {
666         printStaticString!c(w.getBuffer(c.length).getStaticBuf!(c.length));
667         w.advance(c.length);
668     }
669     else
670     static if (c.length <= 4)
671     {
672         static foreach(i; 0 .. c.length)
673             w.put(c[i]);
674     }
675     else
676     {
677         w.put(c[]);
678     }
679     return w;
680 }
681 
682 private @trusted ref C[N] getStaticBuf(size_t N, C)(scope return ref C[] buf)
683 {
684     assert(buf.length >= N);
685     return buf.ptr[0 .. N];
686 }
687 
688 template isFastBuffer(W)
689 {
690     enum isFastBuffer = __traits(hasMember, W, "getBuffer") && __traits(hasMember, W, "advance");
691 }
692 
693 ///
694 ref W printZeroPad(C = char, W, I)(scope return ref W w, const I c, size_t minimalLength)
695     if (isIntegral!I && !is(I == enum))
696 {
697     static if (I.sizeof == 16)
698         enum N = 39;
699     else
700     static if (I.sizeof == 8)
701         enum N = 20;
702     else
703         enum N = 10;
704     C[N + !__traits(isUnsigned, I)] buf = void;
705     static if (__traits(isUnsigned, I))
706         auto n = printUnsignedToTail(c, buf);
707     else
708         auto n = printSignedToTail(c, buf);
709     sizediff_t zeros = minimalLength - n;
710 
711     if (zeros > 0)
712     {
713         static if (!__traits(isUnsigned, I))
714         {
715             if (c < 0)
716             {
717                 n--;
718                 w.put(C('-'));
719             }
720         }
721         do w.put(C('0'));
722         while(--zeros);
723     }
724     w.put(buf[$ - n ..  $]);
725     return w;
726 }
727 
728 ///
729 unittest
730 {
731     import mir.appender;
732     ScopedBuffer!char w;
733 
734     w.printZeroPad(-123, 5);
735     w.put(' ');
736     w.printZeroPad(123, 5);
737 
738     assert(w.data == "-0123 00123", w.data);
739 }
740 
741 ///
742 size_t printBoolean(C)(bool c, ref C[5] buf)
743     if(is(C == char) || is(C == wchar) || is(C == dchar))
744 {
745     version(LDC) pragma(inline, true);
746     if (c)
747     {
748         buf[0] = 't';
749         buf[1] = 'r';
750         buf[2] = 'u';
751         buf[3] = 'e';
752         return 4;
753     }
754     else
755     {
756         buf[0] = 'f';
757         buf[1] = 'a';
758         buf[2] = 'l';
759         buf[3] = 's';
760         buf[4] = 'e';
761         return 5;
762     }
763 }
764 
765 ///
766 size_t printStaticString(string str, C)(scope ref C[str.length] buf)
767     if((is(C == char) || is(C == wchar) || is(C == dchar)) && (C[str.length]).sizeof <= 512)
768 {
769     version(LDC) pragma(inline, true);
770     static foreach (i, e; str) buf[i] = e;
771     return buf.length;
772 }
773 
774 /// ditto
775 size_t printStaticString(wstring str, C)(scope ref C[str.length] buf)
776     if((is(C == wchar) || is(C == dchar)) && (C[str.length]).sizeof <= 512)
777 {
778     version(LDC) pragma(inline, true);
779     static foreach (i, e; str) buf[i] = e;
780     return buf.length;
781 }
782 
783 /// ditto
784 size_t printStaticString(dstring str)(scope ref dchar[str.length] buf)
785     if((dchar[str.length]).sizeof <= 512)
786 {
787     version(LDC) pragma(inline, true);
788     static foreach (i, e; str) buf[i] = e;
789     return buf.length;
790 }