1 /*
2     This file is part of BioD.
3     Copyright (C) 2012    Artem Tarasov <lomereiter@gmail.com>
4 
5     Permission is hereby granted, free of charge, to any person obtaining a
6     copy of this software and associated documentation files (the "Software"),
7     to deal in the Software without restriction, including without limitation
8     the rights to use, copy, modify, merge, publish, distribute, sublicense,
9     and/or sell copies of the Software, and to permit persons to whom the
10     Software is furnished to do so, subject to the following conditions:
11 
12     The above copyright notice and this permission notice shall be included in
13     all copies or substantial portions of the Software.
14 
15     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21     DEALINGS IN THE SOFTWARE.
22 
23 */
24 /**
25   $(P This module provides fast formatting functions.)
26 
27   $(P Each function has two overloads:
28     $(UL
29         $(LI $(D ref char*) - in this case, function starts
30             writing at the location, and updates the pointer.
31             No checks are done, it's user's responsibility that this is safe.)
32         $(LI $(D scope void delegate(const(char)[])) - formatted data
33             is passed to the delegate for further processing.)))
34  */
35 module bio.core.utils.format;
36 
37 import core.stdc.stdio;
38 import core.stdc.stdlib;
39 static import core.stdc..string;
40 import std..string;
41 import std.traits;
42 import std.array;
43 import std.math;
44 
45 ///
46 template isSomeSink(T) {
47     static if (__traits(compiles, T.init("string")))//T == void delegate(const(char)[])))
48         enum isSomeSink = true;
49     else static if (is(T == char*))
50         enum isSomeSink = true;
51     else
52         enum isSomeSink = false;
53 }
54 
55 private {
56     // Reverses closed interval [begin .. end]
57     void strreverse(char* begin, char* end)
58     {
59         char aux;
60         while (end > begin)
61             aux = *end, *end-- = *begin, *begin++ = aux;
62     }
63 
64     // Prints $(D value) at the address where $(D str) points to.
65     // Returns number of characters written.
66     size_t itoa(T)(T value, char* str)
67     {
68         char* wstr=str;
69 
70         static if (isSigned!T) {
71             ulong uvalue = (value < 0) ? -cast(int)(value) : value;
72         } else {
73             ulong uvalue = value;
74         }
75 
76         do {
77             *wstr++ = cast(char)(48 + (uvalue % 10));
78         } while (uvalue /= 10);
79 
80         static if (isSigned!T) {
81             if (value < 0) *wstr++ = '-';
82         }
83 
84         strreverse(str,wstr-1);
85 
86         return wstr - str;
87     }
88 }
89 
90 
91 private {
92     void writeFloat(T)(ref char* sink, T number)
93         if (isFloatingPoint!T)
94     {
95         char[4] format;
96         format[0] = '%';
97         format[1] = 'g';
98         format[2] = '\0';
99         sink += sprintf(sink, format.ptr, number);
100     }
101 
102     void writeFloat(T)(scope void delegate(const(char)[]) sink, T number)
103         if (isFloatingPoint!T)
104     {
105         char[1024] buffer = void;
106         int count;
107 
108         auto p = buffer.ptr;
109         auto psize = buffer.length;
110         for (;;)
111         {
112             version(Win32)
113             {
114                 count = _snprintf(p,psize,"%g", cast(double)number);
115                 if (count != -1)
116                     break;
117                 psize *= 2;
118                 p = cast(char *) alloca(psize);
119             }
120             version(Posix)
121             {
122                 count = snprintf(p,psize,"%g", cast(double)number);
123                 if (count == -1)
124                     psize *= 2;
125                 else if (count >= psize)
126                     psize = count + 1;
127                 else
128                     break;
129                 p = cast(char *) alloca(psize);
130             }
131         }
132 
133         sink(p[0 .. count]);
134     }
135 
136     void writeInteger(T)(ref char* sink, T integer)
137         if (isIntegral!T)
138     {
139         sink += itoa(integer, sink);
140     }
141 
142     void writeInteger(T)(scope void delegate(const(char)[]) sink, T integer)
143         if (isIntegral!T)
144     {
145         char[32] buf = void;
146         auto len = itoa(integer, buf.ptr);
147         sink(buf[0 .. len]);
148     }
149 
150     void writeChar(T)(ref char* sink, T c)
151         if (isSomeChar!T)
152     {
153         *sink++ = c;
154     }
155 
156     void writeChar(T)(scope void delegate(const(char)[]) sink, T c)
157         if (isSomeChar!T)
158     {
159         sink((&c)[0 .. 1]);
160     }
161 
162     void writeString(T)(ref char* sink, T s)
163         if (isSomeString!T)
164     {
165         auto str = cast(const(char)[])s;
166         core.stdc..string.memcpy(sink, str.ptr, str.length);
167         sink += str.length;
168     }
169 
170     void writeString(T)(scope void delegate(const(char)[]) sink, T s)
171         if (isSomeString!T)
172     {
173         sink(cast(const(char)[])s);
174     }
175 
176     void writeImpl(Sink, T)(auto ref Sink sink, T value)
177         if (isSomeSink!Sink)
178     {
179         static if (isIntegral!T)
180             writeInteger(sink, value);
181         else static if (isFloatingPoint!T)
182             writeFloat(sink, value);
183         else static if (isSomeChar!T)
184             writeChar(sink, value);
185         else static if (isSomeString!T)
186             writeString(sink, value);
187         else static assert(false,
188                     "only integers, floats, chars and strings are supported");
189     }
190 
191     // -------------------- JSON output utils ----------------------------------
192 
193     // JSON doesn't support NaN and +/- infinity.
194     // Therefore the approach taken here is to represent
195     // infinity as 1.0e+1024, and NaN as null.
196     void writeFloatJson(Sink, T)(auto ref Sink sink, T value)
197         if (isFloatingPoint!T)
198     {
199         if (isFinite(value)) {
200             sink.write(value);
201         } else {
202             if (value == float.infinity) {
203                 sink.write("1.0e+1024");
204             } else if (value == -float.infinity) {
205                 sink.write("-1.0e+1024");
206             } else if (isNaN(value)) {
207                 sink.write("null");
208             } else {
209                 assert(0);
210             }
211         }
212     }
213 
214     immutable char[256] specialCharacterTable = [
215     /*   0-15  */    0,0,  0,0,0,0,0,0, 'b','t','n',0, 'f','r',0,  0,
216     /*  16-31  */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0,
217     /*  32-47  */    0,0,'"',0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0,
218     /*  48-63  */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,'/',
219     /*  64-79  */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0,
220     /*  80-95  */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,'\\',  0,0,  0,
221     /*  96-111 */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0,
222     /* 112-127 */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0,
223 
224                      0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
225                      0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
226                      0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
227                      0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
228                      0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
229                      0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
230                      0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
231                      0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0
232     ];
233 
234     void writeStringJson(Sink, T)(auto ref Sink sink, T s)
235         if (isSomeString!T)
236     {
237         sink.write('"');
238         foreach (char c; s) {
239             auto sc = specialCharacterTable[cast(ubyte)c];
240             if (sc == 0) {
241                 sink.write(c);
242             } else {
243                 sink.write('\\');
244                 sink.write(sc);
245             }
246         }
247         sink.write('"');
248     }
249 
250     void writeCharJson(Sink, T)(auto ref Sink sink, T c)
251         if (isSomeChar!T)
252     {
253         sink.writeStringJson((&c)[0 .. 1]);
254     }
255 
256     void writeArrayJson(Sink, T)(auto ref Sink sink, T array)
257         if (isArray!T && __traits(compiles, sink.writeJson(array[0])))
258     {
259         if (array.length == 0) {
260             sink.write("[]");
261             return;
262         }
263 
264         sink.write('[');
265         foreach (elem; array[0 .. $ - 1]) {
266             sink.writeJson(elem);
267             sink.write(',');
268         }
269         sink.writeJson(array[$ - 1]);
270         sink.write(']');
271     }
272 
273     void writeJsonImpl(Sink, T)(auto ref Sink sink, T value)
274         if (isSomeSink!Sink)
275     {
276         static if (isIntegral!T)
277             writeInteger(sink, value);
278         else static if (isFloatingPoint!T)
279             writeFloatJson(sink, value);
280         else static if (isSomeChar!T)
281             writeCharJson(sink, value);
282         else static if (isSomeString!T)
283             writeStringJson(sink, value);
284         else static if (isArray!T && __traits(compiles, sink.writeJsonImpl(value[0])))
285             writeArrayJson(sink, value);
286         else static assert(false,
287                     "only numbers, chars, strings and arrays are supported");
288     }
289 }
290 
291 ///
292 void write(T)(ref char* sink, T value) { writeImpl(sink, value); }
293 ///
294 void write(T)(scope void delegate(const(char)[]) sink, T value) { writeImpl(sink, value); }
295 
296 ///
297 void writeArray(Sink, T, U)(auto ref Sink sink, T array, U delimiter)
298     if (isSomeSink!Sink && isArray!T && (isSomeChar!U || isSomeString!U) &&
299         __traits(compiles, sink.write(array[0])))
300 {
301     if (array.length == 0)
302         return;
303 
304     foreach (elem; array[0 .. $ - 1]) {
305         sink.write(elem);
306         sink.write(delimiter);
307     }
308     sink.write(array[$ - 1]);
309 }
310 
311 /// Supports numbers, strings, and arrays. No dictionary - because D doesn't have a good one.
312 void writeJson(T)(ref char* sink, T value) { writeJsonImpl(sink, value); }
313 /// ditto
314 void writeJson(T)(scope void delegate(const(char)[]) sink, T value) { writeJsonImpl(sink, value); }