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