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