use super::marks::{flags, Mark};
#[derive(Debug, Clone, Copy)]
pub struct MathSpan {
pub opener_pos: u32,
pub opener_end: u32,
pub closer_pos: u32,
pub closer_end: u32,
pub is_display: bool,
}
impl MathSpan {
pub fn content_range(&self) -> (u32, u32) {
(self.opener_end, self.closer_pos)
}
}
pub fn resolve_math_spans(marks: &mut [Mark], text: &[u8]) -> Vec<MathSpan> {
let mut spans = Vec::new();
let len = marks.len();
for i in 0..len {
if marks[i].ch != b'$' || marks[i].is_resolved() || marks[i].flags & flags::IN_CODE != 0 {
continue;
}
let opener_pos = marks[i].pos as usize;
if opener_pos > 0 && text[opener_pos - 1] == b'\\' {
let backslash_escaped = opener_pos > 1 && text[opener_pos - 2] == b'\\';
if !backslash_escaped {
continue;
}
}
let opener_len = marks[i].len();
for j in (i + 1)..len {
if marks[j].ch != b'$' || marks[j].is_resolved() {
continue;
}
if marks[j].len() == opener_len {
let closer_pos = marks[j].pos as usize;
if closer_pos > 0 && text[closer_pos - 1] == b'\\' {
let opener_end = marks[i].end as usize;
let backslash_pos = closer_pos - 1;
if backslash_pos >= opener_end {
} else {
let backslash_escaped =
backslash_pos > 0 && text[backslash_pos - 1] == b'\\';
if !backslash_escaped {
continue;
}
}
}
marks[i].resolve();
marks[j].resolve();
for k in (i + 1)..j {
marks[k].flags |= flags::IN_CODE;
}
spans.push(MathSpan {
opener_pos: marks[i].pos,
opener_end: marks[i].end,
closer_pos: marks[j].pos,
closer_end: marks[j].end,
is_display: opener_len == 2,
});
break;
}
}
}
spans
}
#[cfg(test)]
mod tests {
use super::*;
use crate::inline::marks::{collect_marks, MarkBuffer};
#[test]
fn test_simple_inline_math() {
let text = b"hello $x^2$ world";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
let spans = resolve_math_spans(buffer.marks_mut(), text);
assert_eq!(spans.len(), 1);
assert!(!spans[0].is_display);
let (start, end) = spans[0].content_range();
assert_eq!(&text[start as usize..end as usize], b"x^2");
}
#[test]
fn test_display_math() {
let text = b"hello $$E=mc^2$$ world";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
let spans = resolve_math_spans(buffer.marks_mut(), text);
assert_eq!(spans.len(), 1);
assert!(spans[0].is_display);
let (start, end) = spans[0].content_range();
assert_eq!(&text[start as usize..end as usize], b"E=mc^2");
}
#[test]
fn test_unmatched_dollar() {
let text = b"hello $ world";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
let spans = resolve_math_spans(buffer.marks_mut(), text);
assert_eq!(spans.len(), 0);
}
#[test]
fn test_escaped_dollar() {
let text = b"hello \\$x\\$ world";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
let spans = resolve_math_spans(buffer.marks_mut(), text);
assert_eq!(spans.len(), 0);
}
#[test]
fn test_multiple_math_spans() {
let text = b"$a$ and $b$";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
let spans = resolve_math_spans(buffer.marks_mut(), text);
assert_eq!(spans.len(), 2);
}
}