pub(crate) fn is_string_delim(b: u8) -> bool {
matches!(b, b'"' | b'\'' | b'`')
}
pub(crate) fn raw_string_end(bytes: &[u8], start: usize) -> usize {
let delim = bytes[start];
match bytes[start + 1..].iter().position(|&b| b == delim) {
Some(off) => start + 1 + off + 1,
None => bytes.len(),
}
}
pub(super) fn copy_raw_string(out: &mut String, s: &str, i: usize) -> usize {
let end = raw_string_end(s.as_bytes(), i);
out.push_str(&s[i..end]);
end
}
pub(super) const RAW_STRING_RE_ALT: &str = r#""[^"]*"|'[^']*'|`[^`]*`"#;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn closes_at_first_delimiter_ignoring_backslash() {
let s = r"'Q\' ~ 'v1.0'";
assert_eq!(raw_string_end(s.as_bytes(), 0), 4);
}
#[test]
fn backtick_is_a_delimiter() {
let s = "`v1.0` tail";
assert_eq!(raw_string_end(s.as_bytes(), 0), 6);
}
#[test]
fn unterminated_returns_len() {
let s = r#""never closed"#;
assert_eq!(raw_string_end(s.as_bytes(), 0), s.len());
}
#[test]
fn copy_raw_string_is_verbatim() {
let mut out = String::new();
let s = r"'a\n日本' rest";
let end = copy_raw_string(&mut out, s, 0);
assert_eq!(out, r"'a\n日本'");
assert_eq!(&s[end..], " rest");
}
#[test]
fn regex_fragment_agrees_with_scanner_on_boundaries() {
let re = regex::Regex::new(RAW_STRING_RE_ALT).expect("valid fragment");
let samples = [
r#""plain" x"#,
r"'Q\' tail",
"`v1.0` tail",
r#""a\"b" tail"#,
r"'it\'s'",
];
for s in samples {
let m = re.find(s).expect("fragment must match a leading literal");
assert_eq!(m.start(), 0, "sample: {s}");
assert_eq!(
m.end(),
raw_string_end(s.as_bytes(), 0),
"boundary drift for sample: {s}"
);
}
}
}