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 private {
46     // Reverses closed interval [begin .. end]
47     void strreverse(char* begin, char* end)
48     {
49         char aux;
50         while (end > begin)
51             aux = *end, *end-- = *begin, *begin++ = aux;
52     }
53 
54     // Prints $(D value) at the address where $(D str) points to.
55     // Returns number of characters written.
56     size_t itoa(T)(T value, char* str)
57     {
58         char* wstr=str;
59 
60         static if (isSigned!T) { 
61             ulong uvalue = (value < 0) ? -cast(int)(value) : value;
62         } else {
63             ulong uvalue = value;
64         }
65 
66         do {
67             *wstr++ = cast(char)(48 + (uvalue % 10)); 
68         } while (uvalue /= 10);
69 
70         static if (isSigned!T) {
71             if (value < 0) *wstr++ = '-';
72         }
73 
74         strreverse(str,wstr-1);
75 
76         return wstr - str;
77     }
78 }
79 
80 ///
81 template isSomeSink(T) {
82     static if (__traits(compiles, T.init("string")))//T == void delegate(const(char)[])))
83         enum isSomeSink = true;
84     else static if (is(T == char*))
85         enum isSomeSink = true;
86     else
87         enum isSomeSink = false;
88 }
89 
90 private {
91     void writeFloat(T)(ref char* sink, T number) 
92         if (isFloatingPoint!T)
93     {
94         char[4] format;
95         format[0] = '%';
96         format[1] = 'g';
97         format[2] = '\0';
98         sink += sprintf(sink, format.ptr, number);
99     }
100 
101     void writeFloat(T)(scope void delegate(const(char)[]) sink, T number) 
102         if (isFloatingPoint!T)
103     {
104         char[1024] buffer = void;
105         int count;
106 
107         auto p = buffer.ptr;
108         auto psize = buffer.length;
109         for (;;)
110         {
111             version(Win32)
112             {
113                 count = _snprintf(p,psize,"%g", cast(double)number);
114                 if (count != -1)
115                     break;
116                 psize *= 2;
117                 p = cast(char *) alloca(psize);
118             }
119             version(Posix)
120             {
121                 count = snprintf(p,psize,"%g", cast(double)number);
122                 if (count == -1)
123                     psize *= 2;
124                 else if (count >= psize)
125                     psize = count + 1;
126                 else
127                     break;
128                 p = cast(char *) alloca(psize);
129             }
130         }
131 
132         sink(p[0 .. count]);
133     }
134 
135     void writeInteger(T)(ref char* sink, T integer)
136         if (isIntegral!T)
137     {
138         sink += itoa(integer, sink);
139     }
140 
141     void writeInteger(T)(scope void delegate(const(char)[]) sink, T integer) 
142         if (isIntegral!T)
143     {
144         char[32] buf = void;
145         auto len = itoa(integer, buf.ptr);
146         sink(buf[0 .. len]);
147     }
148 
149     void writeChar(T)(ref char* sink, T c)
150         if (isSomeChar!T)
151     {
152         *sink++ = c;
153     }
154 
155     void writeChar(T)(scope void delegate(const(char)[]) sink, T c) 
156         if (isSomeChar!T)
157     {
158         sink((&c)[0 .. 1]);
159     }
160 
161     void writeString(T)(ref char* sink, T s)
162         if (isSomeString!T)
163     {
164         auto str = cast(const(char)[])s;
165         core.stdc..string.memcpy(sink, str.ptr, str.length);
166         sink += str.length;
167     }
168 
169     void writeString(T)(scope void delegate(const(char)[]) sink, T s)
170         if (isSomeString!T)
171     {
172         sink(cast(const(char)[])s);
173     }
174 
175     void writeImpl(Sink, T)(auto ref Sink sink, T value) 
176         if (isSomeSink!Sink)
177     {
178         static if (isIntegral!T)
179             writeInteger(sink, value);
180         else static if (isFloatingPoint!T)
181             writeFloat(sink, value);
182         else static if (isSomeChar!T)
183             writeChar(sink, value);
184         else static if (isSomeString!T)
185             writeString(sink, value);
186         else static assert(false, 
187                     "only integers, floats, chars and strings are supported");
188     }
189 
190     // -------------------- JSON output utils ----------------------------------
191 
192     // JSON doesn't support NaN and +/- infinity.
193     // Therefore the approach taken here is to represent
194     // infinity as 1.0e+1024, and NaN as null.
195     void writeFloatJson(Sink, T)(auto ref Sink sink, T value) 
196         if (isFloatingPoint!T)
197     {
198         if (isFinite(value)) {
199             sink.write(value);
200         } else {
201             if (value == float.infinity) {
202                 sink.write("1.0e+1024");
203             } else if (value == -float.infinity) {
204                 sink.write("-1.0e+1024");
205             } else if (isNaN(value)) {
206                 sink.write("null");
207             } else {
208                 assert(0);
209             }
210         }
211     }
212 
213     immutable char[256] specialCharacterTable = [
214     /*   0-15  */    0,0,  0,0,0,0,0,0, 'b','t','n',0, 'f','r',0,  0, 
215     /*  16-31  */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0,
216     /*  32-47  */    0,0,'"',0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0, 
217     /*  48-63  */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,'/', 
218     /*  64-79  */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0,
219     /*  80-95  */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,'\\',  0,0,  0, 
220     /*  96-111 */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0,
221     /* 112-127 */    0,0,  0,0,0,0,0,0,   0,  0,  0,0,   0,  0,0,  0,
222 
223                      0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
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     ];
232 
233     void writeStringJson(Sink, T)(auto ref Sink sink, T s) 
234         if (isSomeString!T)
235     {
236         sink.write('"');
237         foreach (char c; s) {
238             auto sc = specialCharacterTable[cast(ubyte)c];
239             if (sc == 0) {
240                 sink.write(c);
241             } else {
242                 sink.write('\\');
243                 sink.write(sc);
244             }
245         }
246         sink.write('"');
247     }
248 
249     void writeCharJson(Sink, T)(auto ref Sink sink, T c)
250         if (isSomeChar!T)
251     {
252         sink.writeStringJson((&c)[0 .. 1]);
253     }
254 
255     void writeArrayJson(Sink, T)(auto ref Sink sink, T array)
256         if (isArray!T && __traits(compiles, sink.writeJson(array[0])))
257     {
258         if (array.length == 0) {
259             sink.write("[]");
260             return;
261         }
262 
263         sink.write('[');
264         foreach (elem; array[0 .. $ - 1]) {
265             sink.writeJson(elem);
266             sink.write(',');
267         }
268         sink.writeJson(array[$ - 1]);
269         sink.write(']');
270     }
271 
272     void writeJsonImpl(Sink, T)(auto ref Sink sink, T value) 
273         if (isSomeSink!Sink)
274     {
275         static if (isIntegral!T)
276             writeInteger(sink, value);
277         else static if (isFloatingPoint!T)
278             writeFloatJson(sink, value);
279         else static if (isSomeChar!T)
280             writeCharJson(sink, value);
281         else static if (isSomeString!T)
282             writeStringJson(sink, value);
283         else static if (isArray!T && __traits(compiles, sink.writeJsonImpl(value[0])))
284             writeArrayJson(sink, value);
285         else static assert(false, 
286                     "only numbers, chars, strings and arrays are supported");
287     }
288 }
289 
290 ///
291 void write(T)(ref char* sink, T value) { writeImpl(sink, value); }
292 ///
293 void write(T)(scope void delegate(const(char)[]) sink, T value) { writeImpl(sink, value); }
294 
295 ///
296 void writeArray(Sink, T, U)(auto ref Sink sink, T array, U delimiter)
297     if (isSomeSink!Sink && isArray!T && (isSomeChar!U || isSomeString!U) && 
298         __traits(compiles, sink.write(array[0])))
299 {
300     if (array.length == 0)
301         return;
302 
303     foreach (elem; array[0 .. $ - 1]) {
304         sink.write(elem);
305         sink.write(delimiter);
306     }
307     sink.write(array[$ - 1]);
308 }
309 
310 /// Supports numbers, strings, and arrays. No dictionary - because D doesn't have a good one.
311 void writeJson(T)(ref char* sink, T value) { writeJsonImpl(sink, value); }
312 /// ditto
313 void writeJson(T)(scope void delegate(const(char)[]) sink, T value) { writeJsonImpl(sink, value); }