1use core::fmt;
10use std::borrow::Cow;
11
12#[derive(Eq, PartialEq, Debug)]
13pub enum Error {
14 InvalidCharInString(usize, char),
15 InvalidEscape(usize, char),
16 InvalidHexEscape(usize, char),
17 InvalidEscapeValue(usize, u32),
18 UnterminatedString(usize),
19}
20
21pub fn escape(string: &str) -> Result<Cow<'_, str>, Error> {
22 let mut chars = string.chars().enumerate();
23 let mut output = Cow::from(string);
24 while let Some((i, ch)) = chars.next() {
25 match ch {
26 '\\' => {
27 if let Cow::Borrowed(_) = output {
28 output = Cow::Owned(string[..i].into());
29 }
30
31 let val = output.to_mut();
32 match chars.next() {
33 Some((_, '"')) => val.push('"'),
34 Some((_, '\\')) => val.push('\\'),
35 Some((_, 'b')) => val.push('\u{8}'),
36 Some((_, 'f')) => val.push('\u{c}'),
37 Some((_, 'n')) => val.push('\n'),
38 Some((_, 'r')) => val.push('\r'),
39 Some((_, 't')) => val.push('\t'),
40 Some((i, c @ 'u')) | Some((i, c @ 'U')) => {
41 let len = if c == 'u' { 4 } else { 8 };
42 val.push(hex(&mut chars, i, len)?);
43 }
44 Some((i, c)) => return Err(Error::InvalidEscape(i, c)),
45 None => return Err(Error::UnterminatedString(0)),
46 }
47 },
48 ch if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}' && ch != '\u{7f}') => {
49 if let Cow::Owned(ref mut val) = output {
51 val.push(ch);
52 }
53 },
54 _ => return Err(Error::InvalidCharInString(i, ch)),
55 }
56 }
57
58 Ok(output)
59}
60
61fn hex<I>(mut chars: I, i: usize, len: usize) -> Result<char, Error>
62 where I: Iterator<Item = (usize, char)>
63{
64 let mut buf = String::with_capacity(len);
65 for _ in 0..len {
66 match chars.next() {
67 Some((_, ch)) if ch as u32 <= 0x7F && ch.is_digit(16) => buf.push(ch),
68 Some((i, ch)) => return Err(Error::InvalidHexEscape(i, ch)),
69 None => return Err(Error::UnterminatedString(0)),
70 }
71 }
72
73 let val = u32::from_str_radix(&buf, 16).unwrap();
74 match std::char::from_u32(val) {
75 Some(ch) => Ok(ch),
76 None => Err(Error::InvalidEscapeValue(i, val)),
77 }
78}
79
80impl fmt::Display for Error {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 match self {
83 Error::InvalidCharInString(_, ch) => write!(f, "invalid char `{:?}`", ch),
84 Error::InvalidEscape(_, ch) => write!(f, "invalid escape `\\{:?}`", ch),
85 Error::InvalidHexEscape(_, ch) => write!(f, "invalid hex escape `{:?}`", ch),
86 Error::InvalidEscapeValue(_, ch) => write!(f, "invalid escaped value `{:?}`", ch),
87 Error::UnterminatedString(_) => write!(f, "unterminated"),
88 }
89 }
90}