Skip to content

Commit c60f9a0

Browse files
Merge #23
23: Parse string literals to find their length r=matklad a=ecstatic-morse Fixes #20. A more complete alternative to #21. Co-authored-by: Dylan MacKenzie <[email protected]>
2 parents 4395cd1 + 52dcaad commit c60f9a0

File tree

1 file changed

+121
-3
lines changed

1 file changed

+121
-3
lines changed

src/lib.rs

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,13 +274,87 @@ impl Expect {
274274
line_start += line.len();
275275
}
276276
let (literal_start, line_indent) = target_line.unwrap();
277-
let literal_length =
278-
file[literal_start..].find("]]").expect("Couldn't find matching `]]` for `expect![[`.");
279-
let literal_range = literal_start..literal_start + literal_length;
277+
278+
let lit_to_eof = &file[literal_start..];
279+
let lit_to_eof_trimmed = lit_to_eof.trim_start();
280+
281+
let literal_start = literal_start + (lit_to_eof.len() - lit_to_eof_trimmed.len());
282+
283+
let literal_len = locate_end(lit_to_eof_trimmed)
284+
.expect("Couldn't find matching `]]` for `expect![[`.");
285+
let literal_range = literal_start..literal_start + literal_len;
280286
Location { line_indent, literal_range }
281287
}
282288
}
283289

290+
fn locate_end(lit_to_eof: &str) -> Option<usize> {
291+
assert!(lit_to_eof.chars().next().map_or(true, |c| !c.is_whitespace()));
292+
293+
if lit_to_eof.starts_with("]]") {
294+
// expect![[ ]]
295+
Some(0)
296+
} else {
297+
// expect![["foo"]]
298+
find_str_lit_len(lit_to_eof)
299+
}
300+
}
301+
302+
/// Parses a string literal, returning the byte index of its last character
303+
/// (either a quote or a hash).
304+
fn find_str_lit_len(str_lit_to_eof: &str) -> Option<usize> {
305+
use StrLitKind::*;
306+
#[derive(Clone, Copy)]
307+
enum StrLitKind {
308+
Normal,
309+
Raw(usize),
310+
}
311+
312+
fn try_find_n_hashes(
313+
s: &mut impl Iterator<Item = char>,
314+
desired_hashes: usize,
315+
) -> Option<(usize, Option<char>)> {
316+
let mut n = 0;
317+
loop {
318+
match s.next()? {
319+
'#' => n += 1,
320+
c => return Some((n, Some(c))),
321+
}
322+
323+
if n == desired_hashes {
324+
return Some((n, None));
325+
}
326+
}
327+
}
328+
329+
let mut s = str_lit_to_eof.chars();
330+
let kind = match s.next()? {
331+
'"' => Normal,
332+
'r' => {
333+
let (n, c) = try_find_n_hashes(&mut s, usize::MAX)?;
334+
if c != Some('"') { return None; }
335+
Raw(n)
336+
}
337+
_ => return None,
338+
};
339+
340+
let mut oldc = None;
341+
loop {
342+
let c = oldc.take().or_else(|| s.next())?;
343+
match (c, kind) {
344+
('\\', Normal) => { let _escaped = s.next()?; }
345+
('"', Normal | Raw(0)) => break,
346+
('"', Raw(n)) => {
347+
let (seen, c) = try_find_n_hashes(&mut s, n)?;
348+
if seen == n { break; }
349+
oldc = c;
350+
}
351+
_ => {}
352+
}
353+
}
354+
355+
Some(str_lit_to_eof.len() - s.as_str().len())
356+
}
357+
284358
impl ExpectFile {
285359
/// Checks if file contents is equal to `actual`.
286360
pub fn assert_eq(&self, actual: &str) {
@@ -661,4 +735,48 @@ line1
661735
"#]],
662736
);
663737
}
738+
739+
#[test]
740+
fn test_locate() {
741+
macro_rules! check_locate {
742+
($( [[$s:literal]] ),* $(,)?) => {$({
743+
let lit = stringify!($s);
744+
let with_trailer = format!("{} \t]]\n", lit);
745+
assert_eq!(locate_end(&with_trailer), Some(lit.len()));
746+
})*};
747+
}
748+
749+
// Check that we handle string literals containing "]]" correctly.
750+
check_locate!(
751+
[[r#"{ arr: [[1, 2], [3, 4]], other: "foo" } "#]],
752+
[["]]"]],
753+
[["\"]]"]],
754+
[[r#""]]"#]],
755+
);
756+
757+
// Check `expect![[ ]]` as well.
758+
assert_eq!(locate_end("]]"), Some(0));
759+
}
760+
761+
#[test]
762+
fn test_find_str_lit_len() {
763+
macro_rules! check_str_lit_len {
764+
($( $s:literal ),* $(,)?) => {$({
765+
let lit = stringify!($s);
766+
assert_eq!(find_str_lit_len(lit), Some(lit.len()));
767+
})*}
768+
}
769+
770+
check_str_lit_len![
771+
r##"foa\""#"##,
772+
r##"
773+
774+
asdf][]]""""#
775+
"##,
776+
"",
777+
"\"",
778+
"\"\"",
779+
"#\"#\"#",
780+
];
781+
}
664782
}

0 commit comments

Comments
 (0)