1use alloy_primitives::hex;
4use solar_ast::Span;
5use solar_data_structures::trustme;
6use solar_interface::{BytePos, Session};
7use std::{borrow::Cow, ops::Range, slice, str::Chars};
8
9mod errors;
10pub use errors::EscapeError;
11pub(crate) use errors::emit_unescape_error;
12
13pub use solar_ast::StrKind;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16enum UnescapedUnit {
17 Byte(u8),
18 CodePoint(u32),
19}
20
21impl UnescapedUnit {
22 fn code_point(self) -> u32 {
23 match self {
24 Self::Byte(byte) => byte as u32,
25 Self::CodePoint(code) => code,
26 }
27 }
28
29 fn write(self, dst: &mut [u8]) -> usize {
30 match self {
31 Self::Byte(byte) => {
32 dst[0] = byte;
33 1
34 }
35 Self::CodePoint(code) => super::utf8::encode_utf8_raw(code, dst).len(),
38 }
39 }
40}
41
42pub fn parse_string_literal<'a>(
43 src: &'a str,
44 kind: StrKind,
45 lit_span: Span,
46 sess: &Session,
47) -> (Cow<'a, [u8]>, bool) {
48 let mut has_error = false;
49 let content_start = lit_span.lo() + BytePos(1) + BytePos(kind.prefix().len() as u32);
50 let bytes = try_parse_string_literal(src, kind, |range, error| {
51 has_error = true;
52 let (start, end) = (range.start as u32, range.end as u32);
53 let lo = content_start + BytePos(start);
54 let hi = lo + BytePos(end - start);
55 let span = Span::new(lo, hi);
56 emit_unescape_error(&sess.dcx, src, span, range, error);
57 });
58 (bytes, has_error)
59}
60
61#[instrument(name = "parse_string_literal", level = "trace", skip_all)]
64pub fn try_parse_string_literal<F>(src: &str, kind: StrKind, f: F) -> Cow<'_, [u8]>
65where
66 F: FnMut(Range<usize>, EscapeError),
67{
68 let mut bytes = if needs_unescape(src, kind) {
69 Cow::Owned(parse_literal_unescape(src, kind, f))
70 } else {
71 Cow::Borrowed(src.as_bytes())
72 };
73 if kind == StrKind::Hex {
74 if let Ok(decoded) = hex::decode(&bytes) {
76 bytes = Cow::Owned(decoded);
77 }
78 }
79 bytes
80}
81
82#[cold]
83fn parse_literal_unescape<F>(src: &str, kind: StrKind, f: F) -> Vec<u8>
84where
85 F: FnMut(Range<usize>, EscapeError),
86{
87 let mut bytes = Vec::with_capacity(src.len());
88 parse_literal_unescape_into(src, kind, f, &mut bytes);
89 bytes
90}
91
92fn parse_literal_unescape_into<F>(src: &str, kind: StrKind, mut f: F, dst_buf: &mut Vec<u8>)
93where
94 F: FnMut(Range<usize>, EscapeError),
95{
96 debug_assert!(dst_buf.is_empty());
99 debug_assert!(dst_buf.capacity() >= src.len());
100 let mut dst = unsafe { slice::from_raw_parts_mut(dst_buf.as_mut_ptr(), dst_buf.capacity()) };
101 unescape_literal_unchecked(src, kind, |range, res| match res {
102 Ok(c) => {
103 let written = c.write(dst);
104
105 debug_assert!(dst.len() >= written);
108 let advanced = unsafe { dst.get_unchecked_mut(written..) };
109
110 dst = unsafe { trustme::decouple_lt_mut(advanced) };
112 }
113 Err(e) => f(range, e),
114 });
115 unsafe { dst_buf.set_len(dst_buf.capacity() - dst.len()) };
116}
117
118#[instrument(level = "debug", skip_all)]
122pub fn unescape_literal<F>(src: &str, kind: StrKind, mut callback: F)
123where
124 F: FnMut(Range<usize>, Result<u32, EscapeError>),
125{
126 if needs_unescape(src, kind) {
127 unescape_literal_unchecked(src, kind, |range, res| {
128 callback(range, res.map(UnescapedUnit::code_point));
129 });
130 } else {
131 for (i, ch) in src.char_indices() {
132 callback(i..i + ch.len_utf8(), Ok(ch as u32));
133 }
134 }
135}
136
137fn unescape_literal_unchecked<F>(src: &str, kind: StrKind, callback: F)
141where
142 F: FnMut(Range<usize>, Result<UnescapedUnit, EscapeError>),
143{
144 match kind {
145 StrKind::Str | StrKind::Unicode => {
146 unescape_str(src, matches!(kind, StrKind::Unicode), callback)
147 }
148 StrKind::Hex => unescape_hex_str(src, callback),
149 }
150}
151
152fn needs_unescape(src: &str, kind: StrKind) -> bool {
155 fn needs_unescape_chars(src: &str) -> bool {
156 memchr::memchr3(b'\\', b'\n', b'\r', src.as_bytes()).is_some()
157 }
158
159 match kind {
160 StrKind::Str => needs_unescape_chars(src) || !src.is_ascii(),
161 StrKind::Unicode => needs_unescape_chars(src),
162 StrKind::Hex => !src.len().is_multiple_of(2) || !hex::check_raw(src),
163 }
164}
165
166fn scan_escape(chars: &mut Chars<'_>) -> Result<UnescapedUnit, EscapeError> {
167 let unit = match chars.next().ok_or(EscapeError::LoneSlash)? {
172 '\'' => UnescapedUnit::CodePoint('\'' as u32),
174 '"' => UnescapedUnit::CodePoint('"' as u32),
175
176 '\\' => UnescapedUnit::CodePoint('\\' as u32),
177 'n' => UnescapedUnit::CodePoint('\n' as u32),
178 'r' => UnescapedUnit::CodePoint('\r' as u32),
179 't' => UnescapedUnit::CodePoint('\t' as u32),
180
181 'x' => {
182 let mut value = 0;
184 for _ in 0..2 {
185 let d = chars.next().ok_or(EscapeError::HexEscapeTooShort)?;
186 let d = d.to_digit(16).ok_or(EscapeError::InvalidHexEscape)?;
187 value = value * 16 + d;
188 }
189 UnescapedUnit::Byte(value as u8)
190 }
191
192 'u' => {
193 let mut value = 0;
195 for _ in 0..4 {
196 let d = chars.next().ok_or(EscapeError::UnicodeEscapeTooShort)?;
197 let d = d.to_digit(16).ok_or(EscapeError::InvalidUnicodeEscape)?;
198 value = value * 16 + d;
199 }
200 UnescapedUnit::CodePoint(value)
201 }
202
203 _ => return Err(EscapeError::InvalidEscape),
204 };
205 Ok(unit)
206}
207
208fn unescape_str<F>(src: &str, is_unicode: bool, mut callback: F)
212where
213 F: FnMut(Range<usize>, Result<UnescapedUnit, EscapeError>),
214{
215 let mut chars = src.chars();
216 while let Some(c) = chars.next() {
220 let start = src.len() - chars.as_str().len() - c.len_utf8();
221 let res = match c {
222 '\\' => match chars.clone().next() {
223 Some('\r') if chars.clone().nth(1) == Some('\n') => {
224 skip_ascii_whitespace(&mut chars, start + 2, &mut callback);
226 continue;
227 }
228 Some('\n') => {
229 skip_ascii_whitespace(&mut chars, start + 1, &mut callback);
231 continue;
232 }
233 _ => scan_escape(&mut chars),
234 },
235 '\n' => Err(EscapeError::StrNewline),
236 '\r' => {
237 if chars.clone().next() == Some('\n') {
238 continue;
239 }
240 Err(EscapeError::BareCarriageReturn)
241 }
242 c if !is_unicode && !c.is_ascii() => Err(EscapeError::StrNonAsciiChar),
243 c => Ok(UnescapedUnit::CodePoint(c as u32)),
244 };
245 let end = src.len() - chars.as_str().len();
246 callback(start..end, res);
247 }
248}
249
250fn skip_ascii_whitespace<F>(chars: &mut Chars<'_>, mut start: usize, callback: &mut F)
254where
255 F: FnMut(Range<usize>, Result<UnescapedUnit, EscapeError>),
256{
257 let mut nl = chars.next();
259 if let Some('\r') = nl {
260 nl = chars.next();
261 }
262 debug_assert_eq!(nl, Some('\n'));
263 let mut tail = chars.as_str();
264 start += 1;
265
266 while tail.starts_with(|c: char| c.is_ascii_whitespace()) {
267 let first_non_space =
268 tail.bytes().position(|b| !matches!(b, b' ' | b'\t')).unwrap_or(tail.len());
269 tail = &tail[first_non_space..];
270 start += first_non_space;
271
272 if let Some(tail2) = tail.strip_prefix('\n').or_else(|| tail.strip_prefix("\r\n")) {
273 let skipped = tail.len() - tail2.len();
274 tail = tail2;
275 callback(start..start + skipped, Err(EscapeError::CannotSkipMultipleLines));
276 start += skipped;
277 }
278 }
279 *chars = tail.chars();
280}
281
282fn unescape_hex_str<F>(src: &str, mut callback: F)
286where
287 F: FnMut(Range<usize>, Result<UnescapedUnit, EscapeError>),
288{
289 let mut chars = src.char_indices();
290 if src.starts_with("0x") || src.starts_with("0X") {
291 chars.next();
292 chars.next();
293 callback(0..2, Err(EscapeError::HexPrefix));
294 }
295
296 let count = chars.clone().filter(|(_, c)| c.is_ascii_hexdigit()).count();
297 if !count.is_multiple_of(2) {
298 callback(0..src.len(), Err(EscapeError::HexOddDigits));
299 return;
300 }
301
302 let mut emit_underscore_errors = true;
303 let mut allow_underscore = false;
304 let mut even = true;
305 for (start, c) in chars {
306 let res = match c {
307 '_' => {
308 if emit_underscore_errors && (!allow_underscore || !even) {
309 emit_underscore_errors = false;
311 Err(EscapeError::HexBadUnderscore)
312 } else {
313 allow_underscore = false;
314 continue;
315 }
316 }
317 c if !c.is_ascii_hexdigit() => Err(EscapeError::HexNotHexDigit),
318 c => Ok(UnescapedUnit::CodePoint(c as u32)),
319 };
320
321 if res.is_ok() {
322 even = !even;
323 allow_underscore = true;
324 }
325
326 let end = start + c.len_utf8();
327 callback(start..end, res);
328 }
329
330 if emit_underscore_errors && src.len() > 1 && src.ends_with('_') {
331 callback(src.len() - 1..src.len(), Err(EscapeError::HexBadUnderscore));
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use EscapeError::*;
339
340 type ExErr = (Range<usize>, EscapeError);
341
342 #[track_caller]
343 fn check_parse(kind: StrKind, src: &str, expected: &[u8]) {
344 let out = try_parse_string_literal(src, kind, |_, e| panic!("{e:?}"));
345 assert_eq!(&*out, expected, "{kind:?}: {src:?}");
346 }
347
348 #[track_caller]
349 fn check_unescape(kind: StrKind, src: &str, expected: &str) {
350 let mut out = String::new();
351 unescape_literal(src, kind, |_, c| out.push(char::try_from(c.unwrap()).unwrap()));
352 assert_eq!(out, expected, "{kind:?}: {src:?}");
353 }
354
355 fn check(kind: StrKind, src: &str, expected_str: &str, expected_errs: &[ExErr]) {
356 let panic_str = format!("{kind:?}: {src:?}");
357
358 let mut ok = String::with_capacity(src.len());
359 let mut errs = Vec::with_capacity(expected_errs.len());
360 unescape_literal(src, kind, |range, c| match c {
361 Ok(c) => ok.push(char::try_from(c).unwrap()),
362 Err(e) => errs.push((range, e)),
363 });
364 assert_eq!(errs, expected_errs, "{panic_str}");
365 assert_eq!(ok, expected_str, "{panic_str}");
366
367 let mut errs2 = Vec::with_capacity(errs.len());
368 let out = try_parse_string_literal(src, kind, |range, e| {
369 errs2.push((range, e));
370 });
371 assert_eq!(errs2, errs, "{panic_str}");
372 if kind == StrKind::Hex {
373 assert_eq!(hex::encode(out), expected_str, "{panic_str}");
374 } else {
375 assert_eq!(hex::encode(out), hex::encode(expected_str), "{panic_str}");
376 }
377 }
378
379 #[test]
380 fn unescape_str() {
381 let cases: &[(&str, &str, &[ExErr])] = &[
382 ("", "", &[]),
383 (" ", " ", &[]),
384 ("\t", "\t", &[]),
385 (" \t ", " \t ", &[]),
386 ("foo", "foo", &[]),
387 ("hello world", "hello world", &[]),
388 (r"\", "", &[(0..1, LoneSlash)]),
389 (r"\\", "\\", &[]),
390 (r"\\\", "\\", &[(2..3, LoneSlash)]),
391 (r"\\\\", "\\\\", &[]),
392 (r"\\ ", "\\ ", &[]),
393 (r"\\ \", "\\ ", &[(3..4, LoneSlash)]),
394 (r"\\ \\", "\\ \\", &[]),
395 (r"\x", "", &[(0..2, HexEscapeTooShort)]),
396 (r"\x1", "", &[(0..3, HexEscapeTooShort)]),
397 (r"\xz", "", &[(0..3, InvalidHexEscape)]),
398 (r"\xzf", "f", &[(0..3, InvalidHexEscape)]),
399 (r"\xzz", "z", &[(0..3, InvalidHexEscape)]),
400 (r"\x69", "\x69", &[]),
401 (r"\u", "", &[(0..2, UnicodeEscapeTooShort)]),
402 (r"\u1", "", &[(0..3, UnicodeEscapeTooShort)]),
403 (r"\uz", "", &[(0..3, InvalidUnicodeEscape)]),
404 (r"\uzf", "f", &[(0..3, InvalidUnicodeEscape)]),
405 (r"\u12", "", &[(0..4, UnicodeEscapeTooShort)]),
406 (r"\u123", "", &[(0..5, UnicodeEscapeTooShort)]),
407 (r"\u1234", "\u{1234}", &[]),
408 (r"\u00e8", "è", &[]),
409 (r"\r", "\r", &[]),
410 (r"\t", "\t", &[]),
411 (r"\n", "\n", &[]),
412 (r"\n\n", "\n\n", &[]),
413 (r"\ ", "", &[(0..2, InvalidEscape)]),
414 (r"\?", "", &[(0..2, InvalidEscape)]),
415 ("\r\n", "", &[(1..2, StrNewline)]),
416 ("\n", "", &[(0..1, StrNewline)]),
417 ("\\\n", "", &[]),
418 ("\\\na", "a", &[]),
419 ("\\\n a", "a", &[]),
420 ("a \\\n b", "a b", &[]),
421 ("a\\n\\\n b", "a\nb", &[]),
422 ("a\\t\\\n b", "a\tb", &[]),
423 ("a\\n \\\n b", "a\n b", &[]),
424 ("a\\n \\\n \tb", "a\n b", &[]),
425 ("a\\t \\\n b", "a\t b", &[]),
426 ("\\\n \t a", "a", &[]),
427 (" \\\n \t a", " a", &[]),
428 ("\\\n \t a\n", "a", &[(6..7, StrNewline)]),
429 ("\\\n \t ", "", &[]),
430 (" \\\n \t ", " ", &[]),
431 (" he\\\n \\\nllo \\\n wor\\\nld", " hello world", &[]),
432 ("\\\n\na\\\nb", "ab", &[(2..3, CannotSkipMultipleLines)]),
433 ("\\\n \na\\\nb", "ab", &[(3..4, CannotSkipMultipleLines)]),
434 (
435 "\\\n \n\na\\\nb",
436 "ab",
437 &[(3..4, CannotSkipMultipleLines), (4..5, CannotSkipMultipleLines)],
438 ),
439 (
440 "a\\\n \n \t \nb\\\nc",
441 "abc",
442 &[(4..5, CannotSkipMultipleLines), (8..9, CannotSkipMultipleLines)],
443 ),
444 ];
445 for &(src, expected_str, expected_errs) in cases {
446 check(StrKind::Str, src, expected_str, expected_errs);
447 check(StrKind::Unicode, src, expected_str, expected_errs);
448 }
449
450 check_unescape(StrKind::Str, r"\xE8", "è");
451 check_unescape(StrKind::Unicode, r"\xE8", "è");
452 check_parse(StrKind::Str, r"\xE8", b"\xE8");
453 check_parse(StrKind::Unicode, r"\xE8", b"\xE8");
454 check_parse(StrKind::Str, r"\u00e8", "è".as_bytes());
455 check_parse(StrKind::Unicode, r"\u00e8", "è".as_bytes());
456 check_parse(StrKind::Str, r"\xE8\u00e8", b"\xE8\xc3\xa8");
457 check_parse(StrKind::Unicode, r"\xE8\u00e8", b"\xE8\xc3\xa8");
458 }
459
460 #[test]
461 fn unescape_unicode_str() {
462 let cases: &[(&str, &str, &[ExErr], &[ExErr])] = &[
463 ("è", "è", &[], &[(0..2, StrNonAsciiChar)]),
464 ("😀", "😀", &[], &[(0..4, StrNonAsciiChar)]),
465 ];
466 for &(src, expected_str, e1, e2) in cases {
467 check(StrKind::Unicode, src, expected_str, e1);
468 check(StrKind::Str, src, "", e2);
469 }
470 }
471
472 #[test]
473 fn unescape_hex_str() {
474 let cases: &[(&str, &str, &[ExErr])] = &[
475 ("", "", &[]),
476 ("z", "", &[(0..1, HexNotHexDigit)]),
477 ("\n", "", &[(0..1, HexNotHexDigit)]),
478 (" 11", "11", &[(0..1, HexNotHexDigit), (1..2, HexNotHexDigit)]),
479 ("0x", "", &[(0..2, HexPrefix)]),
480 ("0X", "", &[(0..2, HexPrefix)]),
481 ("0x11", "11", &[(0..2, HexPrefix)]),
482 ("0X11", "11", &[(0..2, HexPrefix)]),
483 ("1", "", &[(0..1, HexOddDigits)]),
484 ("12", "12", &[]),
485 ("123", "", &[(0..3, HexOddDigits)]),
486 ("1234", "1234", &[]),
487 ("_", "", &[(0..1, HexBadUnderscore)]),
488 ("_11", "11", &[(0..1, HexBadUnderscore)]),
489 ("_11_", "11", &[(0..1, HexBadUnderscore)]),
490 ("11_", "11", &[(2..3, HexBadUnderscore)]),
491 ("11_22", "1122", &[]),
492 ("11__", "11", &[(3..4, HexBadUnderscore)]),
493 ("11__22", "1122", &[(3..4, HexBadUnderscore)]),
494 ("1_2", "12", &[(1..2, HexBadUnderscore)]),
495 ];
496 for &(src, expected_str, expected_errs) in cases {
497 check(StrKind::Hex, src, expected_str, expected_errs);
498 }
499 }
500}