locutus
Version:
Locutus other languages' standard libraries to JavaScript for fun and educational purposes
117 lines (116 loc) • 3.78 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Unquote = Unquote;
function Unquote(s) {
// discuss at: https://locutus.io/golang/strconv/Unquote
// parity verified: Go 1.23
// original by: Kevin van Zonneveld (https://kvz.io)
// note 1: Interprets Go single-quoted, double-quoted, or backquoted string literals.
// note 2: Returns [value, null] on success, ["", error] on invalid syntax.
// example 1: Unquote(`"hello"`)[0]
// returns 1: 'hello'
// example 2: Unquote(`'\\n'`)[0]
// returns 2: '\n'
// example 3: Unquote(`'é'`)[0]
// returns 3: 'é'
const input = String(s);
if (input.length < 2) {
return ['', new Error(`invalid syntax: ${input}`)];
}
const quote = input[0];
const endQuote = input.at(-1);
if (!quote || quote !== endQuote || (quote !== '"' && quote !== "'" && quote !== '`')) {
return ['', new Error(`invalid syntax: ${input}`)];
}
const body = input.slice(1, -1);
if (quote === '`') {
return [body, null];
}
const [decoded, decodeError] = decodeGoEscapes(body);
if (decodeError) {
return ['', decodeError];
}
if (quote === "'" && Array.from(decoded).length !== 1) {
return ['', new Error(`invalid rune literal: ${input}`)];
}
return [decoded, null];
}
const ESCAPE_MAP = {
a: '\x07',
b: '\b',
f: '\f',
n: '\n',
r: '\r',
t: '\t',
v: '\v',
'\\': '\\',
"'": "'",
'"': '"',
'`': '`',
};
function isHex(char) {
return /[0-9a-fA-F]/.test(char);
}
function isOctal(char) {
return /[0-7]/.test(char);
}
function decodeGoEscapes(value) {
let out = '';
for (let i = 0; i < value.length; i++) {
const char = value[i];
if (char !== '\\') {
out += char;
continue;
}
const esc = value[i + 1];
if (!esc) {
return ['', new Error('invalid trailing backslash escape')];
}
const mapped = ESCAPE_MAP[esc];
if (mapped !== undefined) {
out += mapped;
i += 1;
continue;
}
if (esc === 'x') {
const hex = value.slice(i + 2, i + 4);
if (hex.length !== 2 || !isHex(hex[0] ?? '') || !isHex(hex[1] ?? '')) {
return ['', new Error(`invalid \\x escape: \\x${hex}`)];
}
out += String.fromCodePoint(Number.parseInt(hex, 16));
i += 3;
continue;
}
if (esc === 'u' || esc === 'U') {
const width = esc === 'u' ? 4 : 8;
const hex = value.slice(i + 2, i + 2 + width);
if (hex.length !== width || !/^[0-9a-fA-F]+$/.test(hex)) {
return ['', new Error(`invalid \\${esc} escape: \\${esc}${hex}`)];
}
const codePoint = Number.parseInt(hex, 16);
if (codePoint > 0x10ffff) {
return ['', new Error(`invalid Unicode code point: ${hex}`)];
}
out += String.fromCodePoint(codePoint);
i += 1 + width;
continue;
}
if (isOctal(esc)) {
let octal = esc;
let consumed = 1;
while (consumed < 3) {
const next = value[i + 1 + consumed];
if (!next || !isOctal(next)) {
break;
}
octal += next;
consumed += 1;
}
out += String.fromCodePoint(Number.parseInt(octal, 8));
i += consumed;
continue;
}
return ['', new Error(`invalid escape sequence: \\${esc}`)];
}
return [out, null];
}