1 /** 2 * Simple (ASCII-only) text-processing functions, 3 * for speed and CTFE. 4 * 5 * License: 6 * This Source Code Form is subject to the terms of 7 * the Mozilla Public License, v. 2.0. If a copy of 8 * the MPL was not distributed with this file, You 9 * can obtain one at http://mozilla.org/MPL/2.0/. 10 * 11 * Authors: 12 * Vladimir Panteleev <vladimir@thecybershadow.net> 13 */ 14 15 module ae.utils.text.ascii; 16 17 import std.algorithm : max; 18 import std.traits : Unqual, isSigned; 19 20 import ae.utils.array : contains; 21 22 // ************************************************************************ 23 24 /// Semantic alias for an array of immutable bytes containing some 25 /// ASCII-based 8-bit character encoding. Might be UTF-8, but not 26 /// necessarily - thus, is a semantic superset of the D "string" alias. 27 alias string ascii; 28 29 // ************************************************************************ 30 31 /// Maximum number of characters needed to fit the decimal 32 /// representation of any number of this basic integer type. 33 template DecimalSize(T : ulong) 34 { 35 alias U = Unqual!T; 36 static if (is(U == ubyte)) 37 enum DecimalSize = 3; 38 else 39 static if (is(U == byte)) 40 enum DecimalSize = 4; 41 else 42 static if (is(U == ushort)) 43 enum DecimalSize = 5; 44 else 45 static if (is(U == short)) 46 enum DecimalSize = 6; 47 else 48 static if (is(U == uint)) 49 enum DecimalSize = 10; 50 else 51 static if (is(U == int)) 52 enum DecimalSize = 11; 53 else 54 static if (is(U == ulong)) 55 enum DecimalSize = 20; 56 else 57 static if (is(U == long)) 58 enum DecimalSize = 20; 59 else 60 static assert(false, "Unknown type for DecimalSize"); 61 } 62 63 unittest 64 { 65 template DecimalSize2(T : ulong) 66 { 67 import std.conv : text; 68 enum DecimalSize2 = max(text(T.min).length, text(T.max).length); 69 } 70 71 static assert(DecimalSize!ubyte == DecimalSize2!ubyte); 72 static assert(DecimalSize!byte == DecimalSize2!byte); 73 static assert(DecimalSize!ushort == DecimalSize2!ushort); 74 static assert(DecimalSize!short == DecimalSize2!short); 75 static assert(DecimalSize!uint == DecimalSize2!uint); 76 static assert(DecimalSize!int == DecimalSize2!int); 77 static assert(DecimalSize!ulong == DecimalSize2!ulong); 78 static assert(DecimalSize!long == DecimalSize2!long); 79 80 static assert(DecimalSize!(const(long)) == DecimalSize!long); 81 } 82 83 /// Writes n as decimal number to buf (right-aligned), returns slice of buf containing result. 84 char[] toDec(N : ulong, size_t U)(N o, ref char[U] buf) pure 85 { 86 static assert(U >= DecimalSize!N, "Buffer too small to fit any " ~ N.stringof ~ " value"); 87 88 Unqual!N n = o; 89 char* p = buf.ptr+buf.length; 90 91 if (isSigned!N && n<0) 92 { 93 do 94 { 95 *--p = '0' - n%10; 96 n = n/10; 97 } while (n); 98 *--p = '-'; 99 } 100 else 101 do 102 { 103 *--p = '0' + n%10; 104 n = n/10; 105 } while (n); 106 107 return p[0 .. buf.ptr + buf.length - p]; 108 } 109 110 /// CTFE-friendly variant. 111 char[] toDecCTFE(N : ulong, size_t U)(N o, ref char[U] buf) 112 { 113 static assert(U >= DecimalSize!N, "Buffer too small to fit any " ~ N.stringof ~ " value"); 114 115 Unqual!N n = o; 116 size_t p = buf.length; 117 118 if (isSigned!N && n<0) 119 { 120 do 121 { 122 buf[--p] = '0' - n%10; 123 n = n/10; 124 } while (n); 125 buf[--p] = '-'; 126 } 127 else 128 do 129 { 130 buf[--p] = '0' + n%10; 131 n = n/10; 132 } while (n); 133 134 return buf[p..$]; 135 } 136 137 /// Basic integer-to-string conversion. 138 string toDec(T : ulong)(T n) 139 { 140 if (__ctfe) 141 { 142 char[DecimalSize!T] buf; 143 return toDecCTFE(n, buf).idup; 144 } 145 else 146 { 147 static struct Buf { char[DecimalSize!T] buf; } // Can't put static array on heap, use struct 148 return toDec(n, (new Buf).buf); 149 } 150 } 151 152 unittest 153 { 154 import std.conv : to; 155 assert(toDec(42) == "42"); 156 assert(toDec(int.min) == int.min.to!string()); 157 static assert(toDec(42) == "42", toDec(42)); 158 } 159 160 /// Print an unsigned integer as a zero-padded, right-aligned decimal number into a buffer 161 void toDecFixed(N : ulong, size_t U)(N n, ref char[U] buf) 162 if (!isSigned!N) 163 { 164 import std.meta : Reverse; 165 import ae.utils.meta : RangeTuple; 166 167 enum limit = 10^^U; 168 assert(n < limit, "Number too large"); 169 170 foreach (i; Reverse!(RangeTuple!U)) 171 { 172 buf[i] = cast(char)('0' + (n % 10)); 173 n /= 10; 174 } 175 } 176 177 /// ditto 178 char[U] toDecFixed(size_t U, N : ulong)(N n) 179 if (!isSigned!N) 180 { 181 char[U] buf; 182 toDecFixed(n, buf); 183 return buf; 184 } 185 186 unittest 187 { 188 assert(toDecFixed!6(12345u) == "012345"); 189 } 190 191 // ************************************************************************ 192 193 /// Basic string-to-integer conversion. 194 /// Doesn't check for overflows. 195 T fromDec(T)(string s) 196 { 197 static if (isSigned!T) 198 { 199 bool neg; 200 if (s.length && s[0] == '-') 201 { 202 neg = true; 203 s = s[1..$]; 204 } 205 } 206 207 T n; 208 foreach (i, c; s) 209 { 210 if (c < '0' || c > '9') 211 throw new Exception("Bad digit"); 212 n = n * 10 + cast(T)(c - '0'); 213 } 214 static if (isSigned!T) 215 if (neg) 216 n = -n; 217 return n; 218 } 219 220 unittest 221 { 222 assert(fromDec!int("456") == 456); 223 assert(fromDec!int("-42") == -42); 224 } 225 226 // ************************************************************************ 227 228 bool containsOnlyChars(string s, string chars) 229 { 230 foreach (c; s) 231 if (!chars.contains(c)) 232 return false; 233 return true; 234 } 235 236 bool isUnsignedInteger(string s) 237 { 238 foreach (c; s) 239 if (c < '0' || c > '9') 240 return false; 241 return s.length > 0; 242 } 243 244 bool isSignedInteger(string s) 245 { 246 return s.length && isUnsignedInteger(s[0] == '-' ? s[1..$] : s); 247 }