use super::TokenizeError;
pub(super) fn scan_quoted(bytes: &[u8], start: usize) -> Result<(String, usize), TokenizeError> {
let mut i = start + 1;
let mut s = String::new();
loop {
if i >= bytes.len() {
return Err(TokenizeError::UnterminatedString(start));
}
let c = bytes[i];
if c == b'"' {
return Ok((s, i + 1));
}
if c == b'\\' {
if i + 1 >= bytes.len() {
return Err(TokenizeError::BadEscape(i));
}
let esc = bytes[i + 1];
match esc {
b'"' => s.push('"'),
b'\\' => s.push('\\'),
_ => return Err(TokenizeError::BadEscape(i)),
}
i += 2;
continue;
}
let ch_len = utf8_char_len(c);
let ch_end = i + ch_len;
if ch_end > bytes.len() {
return Err(TokenizeError::UnterminatedString(start));
}
s.push_str(std::str::from_utf8(&bytes[i..ch_end]).unwrap_or(""));
i = ch_end;
}
}
pub(super) fn is_multiline_start(bytes: &[u8], start: usize) -> bool {
bytes[start..].starts_with(b"text:")
&& (bytes[start..].starts_with(b"text:\r\n")
|| bytes[start..].starts_with(b"text:\n")
|| bytes[start..].starts_with(b"text: \r\n")
|| bytes[start..].starts_with(b"text: \n"))
}
pub(super) fn scan_multiline(bytes: &[u8], start: usize) -> Result<(String, usize), TokenizeError> {
let mut i = start + "text:".len();
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t' || bytes[i] == b'\r') {
i += 1;
}
if i >= bytes.len() || bytes[i] != b'\n' {
return Err(TokenizeError::UnterminatedMultiline(start));
}
i += 1; let mut s = String::new();
loop {
if i >= bytes.len() {
return Err(TokenizeError::UnterminatedMultiline(start));
}
if bytes[i] == b'.'
&& (bytes[i..].starts_with(b".\r\n") || bytes[i..].starts_with(b".\n"))
{
let advance = if bytes[i..].starts_with(b".\r\n") { 3 } else { 2 };
return Ok((s, i + advance));
}
if bytes[i] == b'.' && i + 1 < bytes.len() && bytes[i + 1] == b'.' {
s.push('.');
i += 2;
continue;
}
s.push(bytes[i] as char);
i += 1;
}
}
fn utf8_char_len(first_byte: u8) -> usize {
if first_byte < 0xC0 {
1 } else if first_byte < 0xE0 {
2
} else if first_byte < 0xF0 {
3
} else {
4
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quoted_simple() {
let (s, n) = scan_quoted(br#""hello""#, 0).unwrap();
assert_eq!(s, "hello");
assert_eq!(n, 7);
}
#[test]
fn quoted_escaped_quote() {
let (s, n) = scan_quoted(br#""he said \"hi\"""#, 0).unwrap();
assert_eq!(s, r#"he said "hi""#);
assert_eq!(n, 16);
}
#[test]
fn quoted_unterminated() {
let err = scan_quoted(b"\"oops", 0).unwrap_err();
assert_eq!(err, TokenizeError::UnterminatedString(0));
}
#[test]
fn multiline_simple() {
let src = b"text:\nhello world\n.\n";
let (s, n) = scan_multiline(src, 0).unwrap();
assert_eq!(s, "hello world\n");
assert_eq!(n, src.len());
}
#[test]
fn multiline_dot_stuffed() {
let src = b"text:\n..stuffed\n.\n";
let (s, _) = scan_multiline(src, 0).unwrap();
assert_eq!(s, ".stuffed\n");
}
#[test]
fn multiline_unterminated() {
let src = b"text:\nnever closed";
let err = scan_multiline(src, 0).unwrap_err();
assert_eq!(err, TokenizeError::UnterminatedMultiline(0));
}
}