use std::ops::Range;
use std::str::Chars;
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct EscapeError;
fn unescape_1(chars: &mut Chars<'_>) -> Result<char, EscapeError> {
let c = chars.next().ok_or(EscapeError)?;
if c == '0' {
Ok('\0')
} else {
simple_escape(c).or_else(|c| match c {
'x' => hex2unit(hex_escape(chars)?),
'u' => {
let value = unicode_escape(chars)?;
if value > char::MAX as u32 {
Err(EscapeError)
} else {
char::from_u32(value).ok_or(EscapeError)
}
}
_ => Err(EscapeError),
})
}
}
pub(crate) fn unescape(
src: &str,
mut callback: impl FnMut(Range<usize>, char),
) -> Result<(), EscapeError> {
let mut chars = src.chars();
while let Some(c) = chars.next() {
let start = src.len() - chars.as_str().len() - c.len_utf8();
let res = match c {
'\\' => {
if let Some(b'\n') = chars.as_str().as_bytes().first() {
let _ = chars.next();
skip_ascii_whitespace(&mut chars)?;
continue;
} else {
unescape_1(&mut chars)?
}
}
'"' => return Err(EscapeError),
'\r' => return Err(EscapeError),
c => c,
};
let end = src.len() - chars.as_str().len();
callback(start..end, res);
}
Ok(())
}
#[inline] fn simple_escape(c: char) -> Result<char, char> {
match c {
'"' => Ok('"'),
'n' => Ok('\n'),
'r' => Ok('\r'),
't' => Ok('\t'),
'\\' => Ok('\\'),
'\'' => Ok('\''),
_ => Err(c),
}
}
#[inline] fn hex_escape(chars: &mut impl Iterator<Item = char>) -> Result<u8, EscapeError> {
let hi = chars.next().ok_or(EscapeError)?;
let hi = hi.to_digit(16).ok_or(EscapeError)?;
let lo = chars.next().ok_or(EscapeError)?;
let lo = lo.to_digit(16).ok_or(EscapeError)?;
Ok((hi * 16 + lo) as u8)
}
#[inline] fn unicode_escape(chars: &mut impl Iterator<Item = char>) -> Result<u32, EscapeError> {
if chars.next() != Some('{') {
return Err(EscapeError);
}
let mut value: u32 = match chars.next().ok_or(EscapeError)? {
'_' => return Err(EscapeError),
'}' => return Err(EscapeError),
c => c.to_digit(16).ok_or(EscapeError)?,
};
let mut n_digits = 1;
loop {
match chars.next() {
None => return Err(EscapeError),
Some('_') => continue,
Some('}') => {
return if n_digits > 6 {
Err(EscapeError)
} else {
Ok(value)
};
}
Some(c) => {
let digit: u32 = c.to_digit(16).ok_or(EscapeError)?;
n_digits += 1;
if n_digits > 6 {
continue;
}
value = value * 16 + digit;
}
};
}
}
#[inline] fn skip_ascii_whitespace(chars: &mut Chars<'_>) -> Result<(), EscapeError> {
let rest = chars.as_str();
let first_non_space = rest
.bytes()
.position(|b| b != b' ' && b != b'\t' && b != b'\n' && b != b'\r')
.unwrap_or(rest.len());
let (space, rest) = rest.split_at(first_non_space);
if space.contains('\n') {
return Err(EscapeError);
}
*chars = rest.chars();
if let Some(c) = chars.clone().next()
&& c.is_whitespace()
{
return Err(EscapeError);
}
Ok(())
}
#[inline]
fn hex2unit(b: u8) -> Result<char, EscapeError> {
if b.is_ascii() {
Ok(b as char)
} else {
Err(EscapeError)
}
}