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 debug(ae_unittest) 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 debug(ae_unittest) 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 debug(ae_unittest) 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 debug(ae_unittest) 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