Created
January 15, 2018 14:36
-
-
Save Cauterite/c55423d0c74762f928f261081e1517ad to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const chAt = ``.charCodeAt; | |
const parseArrayIndex = function(s) { | |
/* assumes s is a string | |
if s is a valid index, returns parseInt(s) | |
otherwise returns some negative integer | |
s is valid if: | |
/^(0|[1-9][0-9]*)$/.test(s) && 0 <= parseInt(s) <= 0x7fffffff | |
technically ECMAScript does allow array indexes up to 2^32-2 (4294967294), | |
but JS doesn't have uints, and we want to avoid floating-point arithmetic, | |
so we'll only support the 32-bit signed integer range for now */ | |
const makeTestMask = function(digitValue) { | |
/* make a bitmask to test: 0 <= digitValue <= 9 */ | |
/* if bit3 is 1, bit2 and bit1 must be 0 */ | |
const bit3 = digitValue & 0b1000; | |
return (0b111|bit3) & ~(bit3>>1 | bit3>>2); | |
}; | |
const f = function(acc, s, i, mag) { | |
/* if the highest bit of acc is set, the parsing has already failed and | |
our calculations here don't matter, as long as we preserve that bit */ | |
const n = chAt.call(s, i) - 48; | |
/* if s[i] is a decimal digit and acc >= 0: | |
bit32 = 1<<31 | |
else: | |
bit32 = 0 */ | |
const bit32 = 1<<31 & (acc | -(n & ~makeTestMask(n))); | |
return bit32 | (acc + n * mag); | |
}; | |
let rv = 0; | |
let mag = 1; | |
let finalMask = ~0; | |
switch (s.length) { | |
/* unrolled loop */ | |
case 10 : rv = f(rv, s, 9, mag); mag *= 10; | |
finalMask = 0b11; /* detect overflow when s[0] >= 4 */ | |
case 9 : rv = f(rv, s, 8, mag); mag *= 10; | |
case 8 : rv = f(rv, s, 7, mag); mag *= 10; | |
case 7 : rv = f(rv, s, 6, mag); mag *= 10; | |
case 6 : rv = f(rv, s, 5, mag); mag *= 10; | |
case 5 : rv = f(rv, s, 4, mag); mag *= 10; | |
case 4 : rv = f(rv, s, 3, mag); mag *= 10; | |
case 3 : rv = f(rv, s, 2, mag); mag *= 10; | |
case 2 : rv = f(rv, s, 1, mag); mag *= 10; | |
{/* string cannot start with '0' */ | |
const n = chAt.call(s, 0) - 48; | |
const b32 = 1<<31 & ( | |
rv | | |
-(n & ~makeTestMask(n)) | | |
(n - 1) | /* check for zero */ | |
-(n & ~finalMask) /* detect overflow */); | |
rv = b32 | (rv + n * mag); | |
}; | |
break; | |
case 1 : | |
/* string can start with '0' */ | |
rv = f(rv, s, 0, 1); | |
break; | |
default : rv = -1; | |
}; | |
return rv; | |
}; | |
test(_ => { | |
const pred = (s) => | |
/^(0|[1-9][0-9]*)$/.test(s) | |
&& 0 <= parseInt(s) | |
&& parseInt(s) <= 0x7fffffff; | |
const good = (s) => { | |
assert(parseArrayIndex(s) === parseInt(s)); | |
assert(pred(s)); | |
}; | |
const bad = (s) => { | |
assert(parseArrayIndex(s) < 0); | |
assert(!pred(s)); | |
}; | |
bad(``); | |
bad(` `); | |
bad(`:`); | |
bad(`/`); | |
bad(`\xff`); | |
bad(`\0`); | |
bad(` 0`); | |
bad(`0 `); | |
bad(`-0`); | |
bad(`+0`); | |
bad(`+1`); | |
bad(`0.0`); | |
bad(`01`); | |
bad(`00`); | |
bad(`587/85`); | |
bad(`587:85`); | |
bad(`/87385`); | |
bad(`:87385`); | |
bad(`0147483647`); | |
bad(`2147483648`); | |
bad(`2247483647`); | |
bad(`10000000000`); | |
bad(`21\0x00748\0x00647`); | |
bad(`0900000000`); | |
bad(`00000001`); | |
bad(`0000000001`); | |
bad(`2999999999`); | |
bad(`3000000000`); | |
bad(`3000000001`); | |
bad(`3999999999`); | |
bad(`4000000000`); | |
bad(`4000000001`); | |
bad(`4999999999`); | |
bad(`5000000001`); | |
bad(`6000000001`); | |
bad(`7000000001`); | |
bad(`8000000001`); | |
bad(`9000000001`); | |
bad(`9147483647`); | |
bad(`9999999999`); | |
good(`0`); | |
good(`1`); | |
good(`2`); | |
good(`3`); | |
good(`4`); | |
good(`7`); | |
good(`9`); | |
good(`10`); | |
good(`20`); | |
good(`111`); | |
good(`587385`); | |
good(`900000000`); | |
good(`1000000000`); | |
good(`1999999999`); | |
good(`2000000000`); | |
good(`2147483647`); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment