1 module libd.data.conv;
2 
3 import libd.datastructures..string, libd.util.maths, libd.util.errorhandling, libd.meta, libd.algorithm, libd.console.ansi;
4 
5 private enum MAX_SIZE_T_STRING_LEN = "18446744073709551615".length;
6 alias IntToCharBuffer = char[MAX_SIZE_T_STRING_LEN];
7 
8 private immutable BASE10_CHARS = "0123456789";
9 
10 String to(StringT : String, ValueT)(auto ref ValueT value)
11 {
12     static if(is(ValueT == bool))
13         return value ? String("true") : String("false");
14     else static if(__traits(compiles, toBase10(value)))
15         return value.toBase10;
16     else static if(is(ValueT == AnsiTextLite))
17     {
18         String output;
19         ansiToString(value, output);
20         return output;
21     }
22     else static if(is(ValueT == struct))
23     {
24         String output;
25         structToString(value, output);
26         return output;
27     }
28     else static if(is(ValueT : bcstring))
29         return String(value);
30     else static if(is(ValueT == String))
31         return value;
32     else static if(is(ValueT : T[], T))
33     {
34         // TEMP
35         String str;
36         str.put('[');
37         foreach(element; value)
38         {
39             str.put(element.to!String);
40             str.put(", ");
41         }
42         str.put(']');
43 
44         return str;
45     }
46     else static if(is(ValueT : T*, T))
47     {
48         String output;
49         pointerToString(value, output);
50         return output;
51     }
52     else static assert(false, "Don't know how to convert '"~ValueT.stringof~"' into a String.");
53 }
54 ///
55 @("to!String")
56 unittest
57 {
58     static struct S
59     {
60         int a;
61         string b;
62         bool c;
63     }
64 
65     static struct SS
66     {
67         string name;
68         S s;
69     }
70 
71     assert(127.to!String == "127");
72     assert(S(29, "yolo", true).to!String == `S(29, "yolo", true)`);
73     assert(SS("ribena cow", S(69, "swag", false)).to!String == `SS("ribena cow", S(69, "swag", false))`);
74 }
75 
76 SimpleResult!NumT to(NumT, ValueT)(ValueT value)
77 if(__traits(isIntegral, NumT))
78 {
79     static if(is(ValueT : bcstring))
80         return fromBase10!NumT(value);
81     else static if(is(ValueT == String))
82         return fromBase10!NumT(value.range);
83     else static assert(false, "Don't know how to convert `"~ValueT.stringof~"` into a `"~NumT.stringof~"`");
84 }
85 ///
86 @("to!NumT")
87 unittest
88 {
89     assert("69".to!int.assumeValid == 69);
90     assert(String("-120").to!byte.assumeValid == -120);
91 }
92 
93 SimpleResult!EnumT to(EnumT, ValueT)(ValueT value)
94 if(is(EnumT == enum))
95 {
96     import libd.data.foramt;
97     switch(value)
98     {
99         static foreach(name; __traits(allMembers, EnumT))
100             case mixin("cast(ValueT)EnumT."~name): return mixin("EnumT."~name);
101 
102         default:
103             return typeof(return)(raise("Value '{0}' does not belong to enum {1}.".format(value, EnumT.stringof)));
104     }
105 }
106 
107 private void structToString(StructT, OutputT)(auto ref StructT value, ref OutputT output)
108 if(is(StructT == struct) && isOutputRange!(OutputT, bcstring))
109 {
110     output.put(__traits(identifier, StructT));
111     output.put("(");
112     foreach(i, ref v; value.tupleof)
113     {{
114         static if(is(typeof(v) : bcstring) || is(typeof(v) == String))
115         {
116             output.put("\"");
117             output.put(v);
118             output.put("\"");
119         }
120         else
121         {
122             String s = to!String(v);
123             output.put(s.range);
124         }
125 
126         static if(i < StructT.tupleof.length-1)
127             output.put(", ");
128     }}
129     output.put(")");
130 }
131 
132 String toBase10(NumT)(NumT num)
133 {
134     // Fun fact, because of SSO, this will always be small enough to go onto the stack.
135     // MAX_SIZE_T_STRING_LEN is 20, small strings are up to 22 chars.
136     IntToCharBuffer buffer;
137     return String(toBase10(num, buffer));
138 }
139 ///
140 @("toBase10 - String return")
141 unittest
142 {
143     assert((cast(byte)127).toBase10!byte == "127");
144     assert((cast(byte)-128).toBase10!byte == "-128");
145 }
146 
147 char[] toBase10(NumT)(NumT num_, scope ref return IntToCharBuffer buffer)
148 {
149     Unqual!NumT num = num_;
150     size_t cursor = buffer.length-1;
151     if(num == 0)
152     {
153         buffer[cursor] = '0';
154         return buffer[cursor..$];
155     }
156 
157     static if(__traits(isScalar, NumT))
158     {
159         static if(!__traits(isUnsigned, NumT))
160         {
161             const isNegative = num < 0;
162             auto numAbs = num.abs;
163         }
164         else
165             auto numAbs = num;
166 
167         while(numAbs != 0)
168         {
169             buffer[cursor--] = BASE10_CHARS[numAbs % 10];
170             numAbs /= 10;
171         }
172 
173         static if(!__traits(isUnsigned, NumT))
174         if(isNegative)
175             buffer[cursor--] = '-';
176     }
177     else static assert(false, "Don't know how to convert '"~NumT.stringof~"' into base-10");
178 
179     return buffer[cursor+1..$];    
180 }
181 ///
182 @("toBase10")
183 unittest
184 {
185     IntToCharBuffer buffer;
186     assert(toBase10!byte(byte.max, buffer) == "127");
187     assert(toBase10!byte(byte.min, buffer) == "-128");
188     assert(toBase10!ubyte(ubyte.max, buffer) == "255");
189     assert(toBase10!ubyte(ubyte.min, buffer) == "0");
190 
191     assert(toBase10!short(short.max, buffer) == "32767");
192     assert(toBase10!short(short.min, buffer) == "-32768");
193     assert(toBase10!ushort(ushort.max, buffer) == "65535");
194     assert(toBase10!ushort(ushort.min, buffer) == "0");
195 
196     assert(toBase10!int(int.max, buffer) == "2147483647");
197     assert(toBase10!int(int.min, buffer) == "-2147483648");
198     assert(toBase10!uint(uint.max, buffer) == "4294967295");
199     assert(toBase10!uint(uint.min, buffer) == "0");
200 
201     assert(toBase10!long(long.max, buffer) == "9223372036854775807");
202     assert(toBase10!long(long.min, buffer) == "-9223372036854775808");
203     assert(toBase10!ulong(ulong.max, buffer) == "18446744073709551615");
204     assert(toBase10!ulong(ulong.min, buffer) == "0");
205 }
206 
207 SimpleResult!NumT fromBase10(NumT)(bcstring str)
208 {
209     if(str.length == 0)
210         return raise("String is null.").result!NumT;
211 
212     ptrdiff_t cursor = cast(ptrdiff_t)str.length-1;
213     
214     const firstDigit = str[cursor--] - '0';
215     if(firstDigit >= 10 || firstDigit < 0)
216         return raise("String contains non-base10 characters.").result!NumT;
217 
218     NumT result = cast(NumT)firstDigit;
219     uint exponent = 10;
220     while(cursor >= 0)
221     {
222         if(cursor == 0 && str[cursor] == '-')
223         {
224             static if(__traits(isUnsigned, NumT))
225                 return raise("Cannot convert a negative number into an unsigned type.").result!NumT;
226             else
227             {
228                 result *= -1;
229                 break;
230             }
231         }
232 
233         const digit = str[cursor--] - '0';
234         if(digit >= 10 || digit < 0)
235             return raise("String contains non-base10 characters.").result!NumT;
236 
237         const oldResult = result;
238         result += digit * exponent;
239         if(result < oldResult)
240             return raise("Overflow. String contains a number greater than can fit into specified numeric type.").result!NumT;
241 
242         exponent *= 10;
243     }
244 
245     return result.result;
246 }
247 ///
248 @("fromBase10")
249 unittest
250 {
251     assert(!fromBase10!int(null).isValid);
252     assert(fromBase10!int("0").assumeValid == 0);
253     assert(fromBase10!int("1").assumeValid == 1);
254     assert(fromBase10!int("21").assumeValid == 21);
255     assert(fromBase10!int("321").assumeValid == 321);
256     assert(!fromBase10!ubyte("256").isValid);
257     assert(fromBase10!ubyte("255").assumeValid == 255);
258     assert(!fromBase10!int("yolo").isValid);
259     assert(!fromBase10!uint("-20").isValid);
260     assert(fromBase10!int("-231").assumeValid == -231);
261 }
262 
263 void pointerToString(T, OutputT)(T* pointer, ref OutputT output)
264 {
265     IntToCharBuffer buffer;
266     output.put(toBase10(cast(size_t)pointer));
267 } 
268 
269 void ansiToString(OutputT)(AnsiTextLite ansi, ref OutputT output)
270 {
271     output.put(ansi.toRange());
272 }