1#![warn(missing_docs)]
2
3use thiserror::Error as ThisError;
8
9#[cfg(test)] mod test;
10
11pub type Result<T> = ::std::result::Result<T, Error>;
13
14#[allow(missing_docs)]
16#[cfg_attr(test, derive(PartialEq, Eq))]
17#[derive(Debug, ThisError)]
18pub enum Error {
19 #[error("incomplete str, break at {0}")]
20 IncompleteStr(usize),
21 #[error("invalid char, {char:?} break at {pos}")]
22 InvalidChar { char: char, pos: usize },
23 #[error("parse int error, break at {pos}")]
24 ParseIntError { source: ::std::num::ParseIntError, pos: usize },
25}
26use Error::*;
27
28#[derive(Debug)]
30pub struct Unescaper {
31 pub chars: Vec<char>,
33}
34impl Unescaper {
35 pub fn new(s: &str) -> Self {
37 Self { chars: s.chars().rev().collect() }
38 }
39
40 pub fn unescape(&mut self) -> Result<String> {
42 let chars_count = self.chars.len();
43 let offset = |mut e, remaining_count| {
44 let (IncompleteStr(pos) | InvalidChar { pos, .. } | ParseIntError { pos, .. }) = &mut e;
45
46 *pos += chars_count - remaining_count - 1;
47
48 e
49 };
50 let mut unescaped = String::new();
51
52 while let Some(c) = self.chars.pop() {
53 if c != '\\' {
54 unescaped.push(c);
55
56 continue;
57 }
58
59 let c = self.chars.pop().ok_or(IncompleteStr(chars_count - self.chars.len() - 1))?;
60 let c = match c {
61 'b' => '\u{0008}',
62 'f' => '\u{000c}',
63 'n' => '\n',
64 'r' => '\r',
65 't' => '\t',
66 '\'' | '\"' | '\\' | '/' => c,
71 'u' => self.unescape_unicode_internal().map_err(|e| offset(e, self.chars.len()))?,
72 'x' => self.unescape_byte_internal().map_err(|e| offset(e, self.chars.len()))?,
73 _ => self.unescape_octal_internal(c).map_err(|e| offset(e, self.chars.len()))?,
74 };
75
76 unescaped.push(c);
77 }
78
79 Ok(unescaped)
80 }
81
82 fn unescape_unicode_internal(&mut self) -> Result<char> {
84 let c = self.chars.pop().ok_or(Error::IncompleteStr(0))?;
85 let mut unicode = String::new();
86
87 if c == '{' {
89 while let Some(n) = self.chars.pop() {
90 if n == '}' {
91 break;
92 }
93
94 unicode.push(n);
95 }
96 }
97 else {
99 unicode.push(c);
101
102 for i in 0..3 {
103 let c = self.chars.pop().ok_or(IncompleteStr(i))?;
104
105 unicode.push(c);
106 }
107 }
108
109 char::from_u32(
110 u32::from_str_radix(&unicode, 16).map_err(|e| ParseIntError { source: e, pos: 0 })?,
111 )
112 .ok_or(Error::InvalidChar {
113 char: unicode.chars().last().expect("empty unicode will exit earlier; qed"),
114 pos: 0,
115 })
116 }
117
118 fn unescape_byte_internal(&mut self) -> Result<char> {
120 let mut byte = String::new();
121
122 for i in 0..2 {
124 let c = self.chars.pop().ok_or(IncompleteStr(i))?;
125
126 byte.push(c);
127 }
128
129 Ok(u8::from_str_radix(&byte, 16).map_err(|e| ParseIntError { source: e, pos: 0 })? as _)
130 }
131
132 fn unescape_octal_internal(&mut self, c: char) -> Result<char> {
134 let mut octal = String::new();
135 let mut try_push_next = |octal: &mut String| {
136 if let Some(c) =
137 self.chars.last().cloned().filter(|c| c.is_digit(8)).and_then(|_| self.chars.pop())
138 {
139 octal.push(c);
140 }
141 };
142
143 match c {
144 '0' | '1' | '2' | '3' => {
148 octal.push(c);
149
150 (0..2).for_each(|_| try_push_next(&mut octal));
151 },
152 '4' | '5' | '6' | '7' => {
154 octal.push(c);
155
156 try_push_next(&mut octal);
157 },
158 _ => Err(InvalidChar { char: c, pos: 0 })?,
159 }
160
161 Ok(u8::from_str_radix(&octal, 8).map_err(|e| ParseIntError { source: e, pos: 0 })? as _)
162 }
163}
164
165pub fn unescape(s: &str) -> Result<String> {
167 Unescaper::new(s).unescape()
168}