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