Skip to content

Instantly share code, notes, and snippets.

@Cauterite
Created January 15, 2018 14:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Cauterite/c55423d0c74762f928f261081e1517ad to your computer and use it in GitHub Desktop.
Save Cauterite/c55423d0c74762f928f261081e1517ad to your computer and use it in GitHub Desktop.
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