123use std::str::Chars;45fn decode_unicode(chars: &mut Chars) -> Option<u16> {6 #[expect(7 clippy::cast_possible_truncation,8 reason = "truncated value is a single digit, thus it would never truncate anything"9 )]10 IntoIterator::into_iter([chars.next()?, chars.next()?, chars.next()?, chars.next()?])11 .map(|c| c.to_digit(16).map(|f| f as u16))12 .try_fold(0u16, |acc, v| Some((acc << 4) | (v?)))13}141516pub fn unescape(s: &str) -> Option<String> {17 let mut chars = s.chars();18 let mut out = String::with_capacity(s.len());1920 while let Some(c) = chars.next() {21 if c != '\\' {22 out.push(c);23 continue;24 }25 match chars.next()? {26 c @ ('\\' | '"' | '\'') => out.push(c),27 'b' => out.push('\u{0008}'),28 'f' => out.push('\u{000c}'),29 'n' => out.push('\n'),30 'r' => out.push('\r'),31 't' => out.push('\t'),32 'u' => match decode_unicode(&mut chars)? {33 34 0xDC00..=0xDFFF => return None,35 36 n1 @ 0xD800..=0xDBFF => {37 if chars.next() != Some('\\') {38 return None;39 }40 if chars.next() != Some('u') {41 return None;42 }43 let n2 = decode_unicode(&mut chars)?;44 if !matches!(n2, 0xDC00..=0xDFFF) {45 return None;46 }47 let n = (u32::from(n1 - 0xD800) << 10 | u32::from(n2 - 0xDC00)) + 0x1_0000;48 out.push(char::from_u32(n)?);49 }50 n => out.push(char::from_u32(u32::from(n))?),51 },52 'x' => {53 let c = IntoIterator::into_iter([chars.next()?, chars.next()?])54 .map(|c| c.to_digit(16))55 .try_fold(0u32, |acc, v| Some((acc << 8) | (v?)))?;56 out.push(char::from_u32(c)?);57 }58 _ => return None,59 }60 }61 Some(out)62}