UNPKG

locutus

Version:

Locutus other languages' standard libraries to JavaScript for fun and educational purposes

117 lines (116 loc) 3.78 kB
"use strict"; 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]; }