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