locutus
Version:
Locutus other languages' standard libraries to JavaScript for fun and educational purposes
166 lines (165 loc) • 5.38 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.sub = sub;
const PY_RE_IGNORECASE = 2;
const PY_RE_MULTILINE = 8;
const PY_RE_DOTALL = 16;
function sub(pattern, replacement, source, count = 0, flags = 0) {
// discuss at: https://locutus.io/python/re/sub/
// parity verified: Python 3.12
// original by: Kevin van Zonneveld (https://kvz.io)
// note 1: Replaces regex matches in a string, similar to Python's re.sub.
// note 2: Supports count-limited substitution and a subset of numeric flags (IGNORECASE=2, MULTILINE=8, DOTALL=16).
// example 1: sub('a+', '-', 'caaab')
// returns 1: 'c-b'
// example 2: sub('(\\d+)', '#', 'a1b22c333')
// returns 2: 'a#b#c#'
// example 3: sub('x', 'y', 'xxx', 2)
// returns 3: 'yyx'
// example 4: sub('abc', 'X', 'ABC abc', 0, 2)
// returns 4: 'X X'
const input = String(source);
const maxCount = normalizeCount(count);
const regex = createGlobalRegex(pattern, flags);
const replacementFn = typeof replacement === 'function' ? replacement : null;
const replacementTemplate = typeof replacement === 'string' ? replacement : '';
let out = '';
let lastIndex = 0;
let replaced = 0;
regex.lastIndex = 0;
while (maxCount === 0 || replaced < maxCount) {
const match = regex.exec(input);
if (!match) {
break;
}
const full = match[0] ?? '';
const start = match.index;
const end = start + full.length;
out += input.slice(lastIndex, start);
out +=
replacementFn !== null
? String(replacementFn(full, ...match.slice(1)))
: applyPythonReplacementTemplate(replacementTemplate, full, match.slice(1), match.groups);
lastIndex = end;
replaced += 1;
if (full === '') {
if (regex.lastIndex >= input.length) {
break;
}
regex.lastIndex += 1;
}
}
out += input.slice(lastIndex);
return replaced > 0 ? out : input;
}
function normalizeCount(count) {
const n = Number(count);
if (!Number.isFinite(n) || n <= 0) {
return 0;
}
return Math.floor(n);
}
function createGlobalRegex(pattern, flags) {
const source = pattern instanceof RegExp ? pattern.source : String(pattern);
const base = pattern instanceof RegExp ? pattern.flags : '';
const extra = normalizeRegexFlags(flags);
const combined = dedupeFlags((base + extra).replace(/g/g, '').replace(/y/g, ''));
return new RegExp(source, `${combined}g`);
}
function normalizeRegexFlags(flags) {
if (typeof flags === 'string') {
return dedupeFlags(flags.replace(/[^dgimsuvy]/g, ''));
}
let out = '';
if (flags & PY_RE_IGNORECASE) {
out += 'i';
}
if (flags & PY_RE_MULTILINE) {
out += 'm';
}
if (flags & PY_RE_DOTALL) {
out += 's';
}
return out;
}
function dedupeFlags(flags) {
const seen = new Set();
let out = '';
for (const flag of flags) {
if (!seen.has(flag)) {
seen.add(flag);
out += flag;
}
}
return out;
}
function applyPythonReplacementTemplate(template, full, groups, named) {
let out = '';
for (let i = 0; i < template.length; i++) {
const char = template[i];
if (char !== '\\') {
out += char;
continue;
}
const next = template[i + 1];
if (!next) {
out += '\\';
continue;
}
if (next === '0') {
let token = '0';
let j = i + 2;
while (j < template.length && /[0-7]/.test(template[j] ?? '') && token.length < 3) {
token += template[j];
j += 1;
}
out += String.fromCharCode(Number.parseInt(token, 8));
i = j - 1;
continue;
}
if (/\d/.test(next)) {
let token = next;
let j = i + 2;
while (j < template.length && /\d/.test(template[j] ?? '') && token.length < 2) {
token += template[j];
j += 1;
}
out += groups[Number.parseInt(token, 10) - 1] ?? '';
i = j - 1;
continue;
}
if (next === 'g' && template[i + 2] === '<') {
const close = template.indexOf('>', i + 3);
if (close > -1) {
const token = template.slice(i + 3, close);
if (/^\d+$/.test(token)) {
const index = Number.parseInt(token, 10);
out += index === 0 ? full : (groups[index - 1] ?? '');
}
else {
out += named?.[token] ?? '';
}
i = close;
continue;
}
}
const simpleEscapes = {
'\\': '\\',
n: '\n',
r: '\r',
t: '\t',
f: '\f',
v: '\v',
a: '\x07',
b: '\b',
};
if (simpleEscapes[next] !== undefined) {
out += simpleEscapes[next];
i += 1;
continue;
}
out += next;
i += 1;
}
return out;
}