use crate::parser::is_whitespace_char;
use std::cell::RefCell;
use std::collections::HashMap;
pub(crate) const QUOTES: [u8; 3] = *b"\"'`";
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Reading {
pub(crate) value: String,
pub(crate) length: usize,
}
pub(crate) struct DelimitedReferences {
length: usize,
runs: [DelimiterRuns; 3],
readings: RefCell<HashMap<usize, Option<Reading>>>,
}
impl DelimitedReferences {
pub(crate) fn new(document: &str) -> Self {
DelimitedReferences {
length: document.len(),
runs: QUOTES.map(|quote| DelimiterRuns::new(document, quote)),
readings: RefCell::new(HashMap::new()),
}
}
pub(crate) fn read(&self, rest: &str) -> Option<Reading> {
self.with_reading(rest, Reading::clone)
}
pub(crate) fn end_at(&self, document: &str, start: usize) -> Option<usize> {
self.with_reading(document.get(start..)?, |reading| start + reading.length)
}
fn with_reading<T>(&self, rest: &str, answer: impl FnOnce(&Reading) -> T) -> Option<T> {
let Some(start) = self.length.checked_sub(rest.len()) else {
return read_reference(rest).as_ref().map(answer);
};
let mut readings = self.readings.borrow_mut();
readings
.entry(start)
.or_insert_with(|| self.read_at(rest, start))
.as_ref()
.map(answer)
}
fn read_at(&self, rest: &str, start: usize) -> Option<Reading> {
let quote = *rest.as_bytes().first()?;
let runs = &self.runs[QUOTES.iter().position(|&known| known == quote)?];
let opening = runs.holding(start);
let count = runs.end(opening) - start;
let mut closing = opening + 1;
while closing < runs.count() {
let length = runs.lengths[closing];
if length < count {
closing = runs.next_longer[closing];
} else if (length / count) % 2 == 1 {
break;
} else {
closing += 1;
}
}
let end = (closing < runs.count()).then(|| runs.end(closing) - start);
reading(rest, quote, count, end)
}
}
pub(crate) fn read_reference(rest: &str) -> Option<Reading> {
let quote = *rest.as_bytes().first()?;
if !QUOTES.contains("e) {
return None;
}
let count = run_length(rest, 0, quote);
let mut position = count;
let mut end = None;
while let Some(found) = rest[position..].find(quote as char) {
let start = position + found;
let length = run_length(rest, start, quote);
position = start + length;
if length >= count && (length / count) % 2 == 1 {
end = Some(position);
break;
}
}
reading(rest, quote, count, end)
}
fn reading(rest: &str, quote: u8, count: usize, end: Option<usize>) -> Option<Reading> {
let empty_reference = count.is_multiple_of(2).then(|| Reading {
value: String::new(),
length: count,
});
let Some(end) = end else {
return empty_reference;
};
let body = &rest[count..end];
let mut value = String::with_capacity(body.len());
let mut position = 0;
while let Some(found) = body[position..].find(quote as char) {
let start = position + found;
let length = run_length(body, start, quote);
let escaped = length / (2 * count) * count;
let closes = if start + length == body.len() {
count
} else {
0
};
value.push_str(&body[position..start]);
value.extend(std::iter::repeat_n(
quote as char,
length - escaped - closes,
));
position = start + length;
}
if empty_reference.is_some() && !is_substantive_body(&value) {
return empty_reference;
}
Some(Reading { value, length: end })
}
fn run_length(text: &str, start: usize, quote: u8) -> usize {
text.as_bytes()[start..]
.iter()
.take_while(|&&byte| byte == quote)
.count()
}
struct DelimiterRuns {
starts: Vec<usize>,
lengths: Vec<usize>,
next_longer: Vec<usize>,
}
impl DelimiterRuns {
fn new(document: &str, quote: u8) -> Self {
let mut starts = Vec::new();
let mut lengths = Vec::new();
let mut position = 0;
while let Some(found) = document[position..].find(quote as char) {
let start = position + found;
let length = run_length(document, start, quote);
starts.push(start);
lengths.push(length);
position = start + length;
}
let count = starts.len();
let mut next_longer = vec![count; count];
let mut longer: Vec<usize> = Vec::new();
for run in (0..count).rev() {
while longer
.last()
.is_some_and(|&next| lengths[next] <= lengths[run])
{
longer.pop();
}
if let Some(&next) = longer.last() {
next_longer[run] = next;
}
longer.push(run);
}
DelimiterRuns {
starts,
lengths,
next_longer,
}
}
fn count(&self) -> usize {
self.starts.len()
}
fn end(&self, run: usize) -> usize {
self.starts[run] + self.lengths[run]
}
fn holding(&self, position: usize) -> usize {
self.starts
.partition_point(|&start| start <= position)
.saturating_sub(1)
}
}
fn is_substantive_body(content: &str) -> bool {
let mut depth: isize = 0;
let mut has_visible = false;
for c in content.chars() {
match c {
'(' => depth += 1,
')' => {
depth -= 1;
if depth < 0 {
return false;
}
}
_ => {}
}
if !is_whitespace_char(c) {
has_visible = true;
}
}
has_visible && depth == 0
}