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