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