use crate::error::{Error, Result, SyntaxErrorKind};
fn hex4(raw: &[u8], at: usize) -> Option<u32> {
if at + 4 > raw.len() {
return None;
}
let mut value = 0u32;
for &b in &raw[at..at + 4] {
value = value * 16 + (b as char).to_digit(16)?;
}
Some(value)
}
pub(crate) fn unescape(raw: &[u8], base: u32) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(raw.len());
let mut i = 0usize;
while i < raw.len() {
let b = raw[i];
if b < 0x20 {
return Err(Error::Syntax {
offset: u64::from(base) + i as u64,
kind: SyntaxErrorKind::ControlCharacterInString,
});
}
if b != b'\\' {
out.push(b);
i += 1;
continue;
}
let escape_error = || Error::Syntax {
offset: u64::from(base) + i as u64,
kind: SyntaxErrorKind::InvalidStringEscape,
};
let designator = *raw.get(i + 1).ok_or_else(escape_error)?;
match designator {
b'"' | b'\\' | b'/' => {
out.push(designator);
i += 2;
}
b'b' => {
out.push(0x08);
i += 2;
}
b'f' => {
out.push(0x0C);
i += 2;
}
b'n' => {
out.push(0x0A);
i += 2;
}
b'r' => {
out.push(0x0D);
i += 2;
}
b't' => {
out.push(0x09);
i += 2;
}
b'u' => {
let first = hex4(raw, i + 2).ok_or_else(escape_error)?;
let (code_point, consumed) = match first {
0xD800..=0xDBFF => {
if raw.get(i + 6) != Some(&b'\\') || raw.get(i + 7) != Some(&b'u') {
return Err(escape_error());
}
let low = hex4(raw, i + 8).ok_or_else(escape_error)?;
if !(0xDC00..=0xDFFF).contains(&low) {
return Err(escape_error());
}
(0x10000 + ((first - 0xD800) << 10) + (low - 0xDC00), 12)
}
0xDC00..=0xDFFF => return Err(escape_error()),
_ => (first, 6),
};
let ch = char::from_u32(code_point)
.expect("surrogate-free code point <= U+10FFFF is a valid char");
let mut buf = [0u8; 4];
out.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
i += consumed;
}
_ => return Err(escape_error()),
}
}
Ok(out)
}