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 }