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.algorithm : max;
18 import std.traits : Unqual, isSigned;
19 
20 import ae.utils.array : contains;
21 
22 // ************************************************************************
23 
24 /// Semantic alias for an array of immutable bytes containing some
25 /// ASCII-based 8-bit character encoding. Might be UTF-8, but not
26 /// necessarily - thus, is a semantic superset of the D "string" alias.
27 alias string ascii;
28 
29 // ************************************************************************
30 
31 /// Maximum number of characters needed to fit the decimal
32 /// representation of any number of this basic integer type.
33 template DecimalSize(T : ulong)
34 {
35 	alias U = Unqual!T;
36 	static if (is(U == ubyte))
37 		enum DecimalSize = 3;
38 	else
39 	static if (is(U == byte))
40 		enum DecimalSize = 4;
41 	else
42 	static if (is(U == ushort))
43 		enum DecimalSize = 5;
44 	else
45 	static if (is(U == short))
46 		enum DecimalSize = 6;
47 	else
48 	static if (is(U == uint))
49 		enum DecimalSize = 10;
50 	else
51 	static if (is(U == int))
52 		enum DecimalSize = 11;
53 	else
54 	static if (is(U == ulong))
55 		enum DecimalSize = 20;
56 	else
57 	static if (is(U == long))
58 		enum DecimalSize = 20;
59 	else
60 		static assert(false, "Unknown type for DecimalSize");
61 }
62 
63 unittest
64 {
65 	template DecimalSize2(T : ulong)
66 	{
67 		import std.conv : text;
68 		enum DecimalSize2 = max(text(T.min).length, text(T.max).length);
69 	}
70 
71 	static assert(DecimalSize!ubyte == DecimalSize2!ubyte);
72 	static assert(DecimalSize!byte == DecimalSize2!byte);
73 	static assert(DecimalSize!ushort == DecimalSize2!ushort);
74 	static assert(DecimalSize!short == DecimalSize2!short);
75 	static assert(DecimalSize!uint == DecimalSize2!uint);
76 	static assert(DecimalSize!int == DecimalSize2!int);
77 	static assert(DecimalSize!ulong == DecimalSize2!ulong);
78 	static assert(DecimalSize!long == DecimalSize2!long);
79 
80 	static assert(DecimalSize!(const(long)) == DecimalSize!long);
81 }
82 
83 /// Writes n as decimal number to buf (right-aligned), returns slice of buf containing result.
84 char[] toDec(N : ulong, size_t U)(N o, ref char[U] buf) pure
85 {
86 	static assert(U >= DecimalSize!N, "Buffer too small to fit any " ~ N.stringof ~ " value");
87 
88 	Unqual!N n = o;
89 	char* p = buf.ptr+buf.length;
90 
91 	if (isSigned!N && n<0)
92 	{
93 		do
94 		{
95 			*--p = '0' - n%10;
96 			n = n/10;
97 		} while (n);
98 		*--p = '-';
99 	}
100 	else
101 		do
102 		{
103 			*--p = '0' + n%10;
104 			n = n/10;
105 		} while (n);
106 
107 	return p[0 .. buf.ptr + buf.length - p];
108 }
109 
110 /// CTFE-friendly variant.
111 char[] toDecCTFE(N : ulong, size_t U)(N o, ref char[U] buf)
112 {
113 	static assert(U >= DecimalSize!N, "Buffer too small to fit any " ~ N.stringof ~ " value");
114 
115 	Unqual!N n = o;
116 	size_t p = buf.length;
117 
118 	if (isSigned!N && n<0)
119 	{
120 		do
121 		{
122 			buf[--p] = '0' - n%10;
123 			n = n/10;
124 		} while (n);
125 		buf[--p] = '-';
126 	}
127 	else
128 		do
129 		{
130 			buf[--p] = '0' + n%10;
131 			n = n/10;
132 		} while (n);
133 
134 	return buf[p..$];
135 }
136 
137 /// Basic integer-to-string conversion.
138 string toDec(T : ulong)(T n)
139 {
140 	if (__ctfe)
141 	{
142 		char[DecimalSize!T] buf;
143 		return toDecCTFE(n, buf).idup;
144 	}
145 	else
146 	{
147 		static struct Buf { char[DecimalSize!T] buf; } // Can't put static array on heap, use struct
148 		return toDec(n, (new Buf).buf);
149 	}
150 }
151 
152 unittest
153 {
154 	import std.conv : to;
155 	assert(toDec(42) == "42");
156 	assert(toDec(int.min) == int.min.to!string());
157 	static assert(toDec(42) == "42", toDec(42));
158 }
159 
160 /// Print an unsigned integer as a zero-padded, right-aligned decimal number into a buffer
161 void toDecFixed(N : ulong, size_t U)(N n, ref char[U] buf)
162 	if (!isSigned!N)
163 {
164 	import std.meta : Reverse;
165 	import ae.utils.meta : RangeTuple;
166 
167 	enum limit = 10^^U;
168 	assert(n < limit, "Number too large");
169 
170 	foreach (i; Reverse!(RangeTuple!U))
171 	{
172 		buf[i] = cast(char)('0' + (n % 10));
173 		n /= 10;
174 	}
175 }
176 
177 /// ditto
178 char[U] toDecFixed(size_t U, N : ulong)(N n)
179 	if (!isSigned!N)
180 {
181 	char[U] buf;
182 	toDecFixed(n, buf);
183 	return buf;
184 }
185 
186 unittest
187 {
188 	assert(toDecFixed!6(12345u) == "012345");
189 }
190 
191 // ************************************************************************
192 
193 /// Basic string-to-integer conversion.
194 /// Doesn't check for overflows.
195 T fromDec(T)(string s)
196 {
197 	static if (isSigned!T)
198 	{
199 		bool neg;
200 		if (s.length && s[0] == '-')
201 		{
202 			neg = true;
203 			s = s[1..$];
204 		}
205 	}
206 
207 	T n;
208 	foreach (i, c; s)
209 	{
210 		if (c < '0' || c > '9')
211 			throw new Exception("Bad digit");
212 		n = n * 10 + cast(T)(c - '0');
213 	}
214 	static if (isSigned!T)
215 		if (neg)
216 			n = -n;
217 	return n;
218 }
219 
220 unittest
221 {
222 	assert(fromDec!int("456") == 456);
223 	assert(fromDec!int("-42") == -42);
224 }
225 
226 // ************************************************************************
227 
228 bool containsOnlyChars(string s, string chars)
229 {
230 	foreach (c; s)
231 		if (!chars.contains(c))
232 			return false;
233 	return true;
234 }
235 
236 bool isUnsignedInteger(string s)
237 {
238 	foreach (c; s)
239 		if (c < '0' || c > '9')
240 			return false;
241 	return s.length > 0;
242 }
243 
244 bool isSignedInteger(string s)
245 {
246 	return s.length && isUnsignedInteger(s[0] == '-' ? s[1..$] : s);
247 }