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 }