export function Unquote(s: string): [string, Error | null] { // 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: Record = { a: '\x07', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t', v: '\v', '\\': '\\', "'": "'", '"': '"', '`': '`', } function isHex(char: string): boolean { return /[0-9a-fA-F]/.test(char) } function isOctal(char: string): boolean { return /[0-7]/.test(char) } function decodeGoEscapes(value: string): [string, Error | null] { 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] }