subscript
Version:
Modular expression parser & evaluator
59 lines (51 loc) • 2.19 kB
JavaScript
/**
* Numbers with configurable prefix notation
*
* Configurable via parse.number: { '0x': 16, '0b': 2, '0o': 8 }
*/
import { parse, lookup, next, err, skip, idx, cur } from '../parse.js';
const PERIOD = 46, _0 = 48, _9 = 57, _E = 69, _e = 101, PLUS = 43, MINUS = 45, UNDERSCORE = 95, _n = 110;
const _a = 97, _f = 102, _A = 65, _F = 70;
// Strip underscores only if present (avoid allocation for common case)
const strip = s => s.indexOf('_') < 0 ? s : s.replaceAll('_', '');
// Decimal number - check for .. range operator (don't consume . if followed by .)
// Supports numeric separators: 1_000_000 and BigInt suffix: 123n
const num = a => {
let str = strip(next(c =>
// . is decimal only if NOT range (..) and NOT member access (.name)
// Allows trailing decimal: 1. → 1, 0.95.toFixed → stops at second .
(c === PERIOD && (c = cur.charCodeAt(idx + 1)) !== PERIOD && !(parse.id(c) && c > _9 && c !== _e && c !== _E)) ||
(c >= _0 && c <= _9) ||
c === UNDERSCORE ||
((c === _E || c === _e) && ((c = cur.charCodeAt(idx + 1)) >= _0 && c <= _9 || c === PLUS || c === MINUS) ? 2 : 0)
));
// BigInt suffix
if (cur.charCodeAt(idx) === _n) { skip(); return [, BigInt(str)]; }
return (a = +str) != a ? err() : [, a];
};
// Char test for prefix base (with underscore support)
const charTest = base => c =>
c === UNDERSCORE ||
(c >= _0 && c <= _9 && c - _0 < base) ||
(base === 16 && (c >= _a && c <= _f || c >= _A && c <= _F));
// Default: no prefixes
parse.number = null;
// .1 (but not .. range)
lookup[PERIOD] = a => !a && cur.charCodeAt(idx + 1) >= _0 && cur.charCodeAt(idx + 1) <= _9 && num();
// 0-9: check parse.number for prefix config
for (let i = _0; i <= _9; i++) lookup[i] = a => a ? void 0 : num();
lookup[_0] = a => {
if (a) return;
const cfg = parse.number;
if (cfg) {
for (const [pre, base] of Object.entries(cfg)) {
if (pre[0] === '0' && cur[idx + 1]?.toLowerCase() === pre[1]) {
skip(2);
const str = strip(next(charTest(base)));
if (cur.charCodeAt(idx) === _n) { skip(); return [, BigInt('0' + pre[1] + str)]; }
return [, parseInt(str, base)];
}
}
}
return num();
};