1 module decimal.ranges;
2 
3 
4 private import std.range.primitives: isInputRange, ElementType;
5 private import std.traits: isSomeChar, Unqual;
6 private import decimal.integrals: fma, uint128;
7 
8 package:
9 
10 //rewrite some range primitives because phobos is performing utf decoding and we are not interested
11 //in throwing UTFException and consequentely bring the garbage collector into equation
12 //Also, we don't need any decoding, we are working with the ASCII character set
13 
14 @safe pure nothrow @nogc
15 void popFront(T)(ref T[] s)
16 {
17     assert(s.length);
18     s = s[1 .. $];  
19 }
20 
21 @safe pure nothrow @nogc
22 @property T front(T)(const T[] s)
23 {
24     assert(s.length);
25     return s[0];
26 }
27 
28 @safe pure nothrow @nogc
29 @property bool empty(T)(const T[] s)
30 {
31     return !s.length;
32 }
33 
34 
35 
36 
37 //returns true and advance range if element is found
38 bool expect(R, T)(ref R range, T element) 
39 if (isInputRange!R && isSomeChar!T)
40 {
41     if (!range.empty && range.front == element)
42     {
43         range.popFront();
44         return true;
45     }
46     return false;
47 }
48 
49 
50 unittest
51 {
52     auto s = "abc";
53     assert(expect(s, 'a'));
54     assert(!expect(s, 'B'));
55     assert(expect(s, 'b'));
56     assert(expect(s, 'c'));
57     assert(!expect(s, 'd'));
58 }
59 
60 
61 //returns true and advance range if element is found case insensitive
62 bool expectInsensitive(R, T)(ref R range, T element) 
63 if (isInputRange!R && isSomeChar!T)
64 {
65     if (!range.empty && ((range.front | 32) == (element | 32)))
66     {
67         range.popFront();
68         return true;
69     }
70     return false;
71 }
72 
73 unittest
74 {
75     auto s = "abcABC";
76     assert(expectInsensitive(s, 'a'));
77     assert(!expectInsensitive(s, 'z'));
78     assert(expectInsensitive(s, 'B'));
79     assert(expectInsensitive(s, 'c'));
80     assert(expectInsensitive(s, 'A'));
81     assert(expectInsensitive(s, 'b'));
82     assert(expectInsensitive(s, 'C'));
83     assert(!expectInsensitive(s, 'd'));
84     assert(!expectInsensitive(s, 'D'));
85 }
86 
87 //returns parsed characters count and advance range
88 int expect(R, C)(ref R range, const(C)[] s) 
89 if (isInputRange!R && isSomeChar!C)
90 {
91     int cnt;
92     foreach (ch; s)
93     {
94         if (expect(range, ch))
95             ++cnt;
96         else
97             break;
98     }
99     return cnt;    
100 }
101 
102 unittest
103 {
104     auto s = "somestring";
105     assert(expect(s, "some") == 4);
106     assert(expect(s, "spring") == 1);
107     assert(expect(s, "bring") == 0);
108     assert(expect(s, "tring") == 5);
109 }
110 
111 //returns parsed characters count and advance range insensitive
112 int expectInsensitive(R, C)(ref R range, const(C)[] s) 
113 if (isInputRange!R && isSomeChar!C)
114 {
115     int cnt;
116     foreach(ch; s)
117     {
118         if (expectInsensitive(range, ch))
119             ++cnt;
120         else
121             break;
122     }
123     return cnt;    
124 }
125 
126 unittest
127 {
128     auto s = "sOmEsTrInG";
129     assert(expectInsensitive(s, "SoME") == 4);
130     assert(expectInsensitive(s, "SPRing") == 1);
131     assert(expectInsensitive(s, "bRING") == 0);
132     assert(expectInsensitive(s, "TRinG") == 5);
133 }
134 
135 bool parseSign(R)(ref R range, out bool isNegative) 
136 if (isInputRange!R && isSomeChar!(ElementType!R))
137 {
138     if (expect(range, '-'))
139     {
140         isNegative = true;
141         return true;
142     }
143     else if (expect(range, '+'))
144     {
145         isNegative = false;
146         return true;
147     }
148     else 
149         return false;
150 }
151 
152 unittest
153 {
154     bool isNegative;
155     auto s = "+-s";
156     assert (parseSign(s, isNegative) && !isNegative);
157     assert (parseSign(s, isNegative) && isNegative);
158     assert (!parseSign(s, isNegative));
159 }
160 
161 
162 //returns true and advances range if "inf" or "infinity" is encountered
163 bool parseInfinity(R)(ref R range) 
164 if (isInputRange!R && isSomeChar!(ElementType!R))
165 {
166     if (expectInsensitive(range, "inf") == 3)
167     {
168         auto parsed = expectInsensitive(range, "inity");
169         return parsed == 0 || parsed == 5;
170     }
171     return false;
172 }
173 
174 unittest
175 {
176     auto s = "inf/infinity/InF/iNfInITY/in/infinit/infig";
177     assert (parseInfinity(s)); s.popFront;
178     assert (parseInfinity(s)); s.popFront;
179     assert (parseInfinity(s)); s.popFront;
180     assert (parseInfinity(s)); s.popFront;
181     assert (!parseInfinity(s)); s.popFront;
182     assert (!parseInfinity(s)); s.popFront; 
183     assert (!parseInfinity(s));
184 }
185 
186 
187 //returns corresponding bracket and advances range if any of "([{<" is encountered, 0 otherwise
188 ElementType!R parseBracket(R)(ref R range) 
189 if (isInputRange!R && isSomeChar!(ElementType!R))
190 {
191     if (expect(range, '('))
192         return ')';
193     else if (expect(range, '['))
194         return ']';
195     else if (expect(range, '{'))
196         return '}';
197     else if (expect(range, '<'))
198         return '>';
199     else
200         return 0;
201 }
202 
203 unittest
204 {
205     auto s = "([{<a";
206     assert (parseBracket(s) == ')');
207     assert (parseBracket(s) == ']');
208     assert (parseBracket(s) == '}');
209     assert (parseBracket(s) == '>');
210     assert (parseBracket(s) == 0);
211 }
212 
213 
214 //returns a digit value and advances range if a digit is encountered, -1 otherwise, skips _
215 int parseDigit(R)(ref R range) 
216 if (isInputRange!R && isSomeChar!(ElementType!R))
217 {
218     while (expect(range, '_')) { }
219     if (!range.empty && range.front >= '0' && range.front <= '9')
220     {
221         int result = range.front - '0';
222         range.popFront();
223         return result;
224     }
225     return -1;
226 }
227 
228 unittest
229 {
230     auto s = "0123a";
231     assert (parseDigit(s) == 0);
232     assert (parseDigit(s) == 1);
233     assert (parseDigit(s) == 2);
234     assert (parseDigit(s) == 3);
235     assert (parseDigit(s) == -1);
236 }
237 
238 //returns a digit value and advances range if a hex digit is encountered, -1 otherwise, skips _
239 int parseHexDigit(R)(ref R range)
240 if (isInputRange!R && isSomeChar!(ElementType!R))
241 {
242     while (expect(range, '_')) { }
243     if (!range.empty)
244     {
245         if (range.front >= '0' && range.front <= '9')
246         {
247             int result = range.front - '0';
248             range.popFront();
249             return result;
250         }
251         if (range.front >= 'A' && range.front <= 'F')
252         {
253             int result = range.front - 'A' + 10;
254             range.popFront();
255             return result;
256         }
257         if (range.front >= 'a' && range.front <= 'f')
258         {
259             int result = range.front - 'a' + 10;
260             range.popFront();
261             return result;
262         }
263     }
264     return -1;
265 }
266 
267 unittest
268 {
269     auto s = "0123aBcg";
270     assert (parseHexDigit(s) == 0);
271     assert (parseHexDigit(s) == 1);
272     assert (parseHexDigit(s) == 2);
273     assert (parseHexDigit(s) == 3);
274     assert (parseHexDigit(s) == 10);
275     assert (parseHexDigit(s) == 11);
276     assert (parseHexDigit(s) == 12);
277     assert (parseHexDigit(s) == -1);
278 
279 }
280 
281 //returns how many zeros encountered and advances range, skips _
282 int parseZeroes(R)(ref R range)
283 if (isInputRange!R && isSomeChar!(ElementType!R))
284 {
285     int count = 0;
286     do
287     {
288         if (expect(range, '0'))
289             ++count;
290         else if (!expect(range, '_'))
291             break;
292     } while (true);
293     return count;
294 }
295 
296 unittest
297 {
298     auto s = "0__00_000_";
299     assert(parseZeroes(s) == 6);
300 }
301 
302 //returns true if a hex number can be read in value, stops if doesn't fit in value
303 bool parseHexNumber(R, T)(ref R range, out T value) 
304 if (isInputRange!R && isSomeChar!(ElementType!R))
305 {
306     bool atLeastOneDigit = parseZeroes(range) != 0;
307     enum maxWidth = T.sizeof * 2;
308     int width = 0;
309     while (width < maxWidth && !range.empty)
310     {
311         auto digit = parseHexDigit(range);
312         if (!atLeastOneDigit)
313             atLeastOneDigit = digit >= 0;
314         if (digit >= 0)
315         {
316             value <<= 4;
317             value |= cast(uint)digit;
318             ++width;
319         }
320         else if (range.front == '_')
321             range.popFront();
322         else
323             break;
324     }
325     return atLeastOneDigit;
326 }
327 
328 unittest
329 {
330     uint result;
331     auto s = "0123A/AB_C/1234_56780_Z";
332     assert(parseHexNumber(s, result) && result == 0x0123A); s.popFront();
333     assert(parseHexNumber(s, result) && result == 0xABC); s.popFront();
334     assert(parseHexNumber(s, result) && result == 0x1234_5678); 
335     assert(parseHexNumber(s, result) && result == 0x0); 
336     assert(!parseHexNumber(s, result)); 
337 
338     ulong result2;
339     s = "0123A/AB_C/1234_5678_9ABC_DEF10_Z";
340     assert(parseHexNumber(s, result2) && result2 == 0x0123A); s.popFront();
341     assert(parseHexNumber(s, result2) && result2 == 0xABC); s.popFront();
342     assert(parseHexNumber(s, result2) && result2 == 0x1234_5678_9ABC_DEF1); 
343     assert(parseHexNumber(s, result2) && result2 == 0x0); 
344     assert(!parseHexNumber(s, result2)); 
345 
346     uint128 result3;
347     s = "0123A/AB_C/1234_5678_9ABC_DEF1_2345_6789_ABCD_EF120_Z";
348     assert(parseHexNumber(s, result3) && result3 == 0x0123AU); s.popFront();
349     assert(parseHexNumber(s, result3) && result3 == 0xABCU); s.popFront();
350     assert(parseHexNumber(s, result3) && result3.hi == 0x1234_5678_9ABC_DEF1 && 
351            result3.lo == 0x2345_6789_ABCD_EF12, "a");
352     assert(parseHexNumber(s, result3) && result3 == 0x0U); 
353     assert(!parseHexNumber(s, result3)); 
354 }
355 
356 //returns true if a decimal number can be read in value, stops if doesn't fit in value
357 @safe
358 bool parseNumber(R, T)(ref R range, ref T value) 
359 if (isInputRange!R && isSomeChar!(ElementType!R))
360 {
361     bool atLeastOneDigit = parseZeroes(range) != 0;
362     bool overflow;
363     while (!range.empty)
364     {
365         if (range.front >= '0' && range.front <= '9')
366         {
367             uint digit = range.front - '0';
368             overflow = false;
369             Unqual!T v = fma(value, 10U, digit, overflow);
370             if (overflow)
371                 break;
372             range.popFront();
373             value = v;
374             atLeastOneDigit = true;
375         }
376         else if (range.front == '_')
377             range.popFront();
378         else
379             break;
380     }
381     return atLeastOneDigit;
382 }