use std::cell::RefCell;
use std::rc::Rc;
struct BracketCaches {
naive_nested: Vec<(usize, usize)>,
escape_aware_nested: Vec<(usize, usize)>,
image_flat: Vec<(usize, usize)>,
}
fn lookup(entries: &[(usize, usize)], key: usize) -> Option<usize> {
entries
.iter()
.find(|(open, _)| *open == key)
.map(|(_, close)| *close)
}
thread_local! {
static CACHE: RefCell<Option<Rc<BracketCaches>>> = const { RefCell::new(None) };
}
pub struct BracketCacheGuard {
prev: Option<Rc<BracketCaches>>,
}
impl BracketCacheGuard {
pub fn install(base_offset: usize, text: &str) -> Self {
let prev = if text.contains('[') {
let caches = Rc::new(BracketCaches {
naive_nested: scan_nested(text, base_offset, false),
escape_aware_nested: scan_nested(text, base_offset, true),
image_flat: scan_image_flat(text, base_offset),
});
CACHE.with(|c| c.borrow_mut().replace(caches))
} else {
CACHE.with(|c| c.borrow_mut().take())
};
Self { prev }
}
}
impl Drop for BracketCacheGuard {
fn drop(&mut self) {
CACHE.with(|c| *c.borrow_mut() = self.prev.take());
}
}
fn scan_nested(text: &str, base_offset: usize, respect_escapes: bool) -> Vec<(usize, usize)> {
let mut matches = Vec::new();
let mut stack: Vec<usize> = Vec::new();
let mut escaped = false;
let mut i = 0usize;
for ch in text.chars() {
if respect_escapes {
if escaped {
escaped = false;
i += ch.len_utf8();
continue;
}
if ch == '\\' {
escaped = true;
i += ch.len_utf8();
continue;
}
}
match ch {
'[' => stack.push(base_offset + i),
']' => {
if let Some(open) = stack.pop() {
matches.push((open, base_offset + i));
}
}
_ => {}
}
i += ch.len_utf8();
}
matches
}
fn scan_image_flat(text: &str, base_offset: usize) -> Vec<(usize, usize)> {
let mut matches = Vec::new();
let bytes = text.as_bytes();
let mut next_close: Option<usize> = None;
let mut i = text.len();
while i > 0 {
i -= 1;
if bytes[i] == b']' {
next_close = Some(base_offset + i);
}
if bytes[i] == b'!' && bytes.get(i + 1) == Some(&b'[') {
if let Some(close) = next_close {
matches.push((base_offset + i, close));
}
}
}
matches
}
pub fn cached_naive_nested_match(open_abs_offset: usize) -> Option<Option<usize>> {
CACHE.with(|c| {
c.borrow()
.as_ref()
.map(|m| lookup(&m.naive_nested, open_abs_offset))
})
}
pub fn cached_escape_aware_nested_match(open_abs_offset: usize) -> Option<Option<usize>> {
CACHE.with(|c| {
c.borrow()
.as_ref()
.map(|m| lookup(&m.escape_aware_nested, open_abs_offset))
})
}
pub fn cached_image_flat_match(bang_abs_offset: usize) -> Option<Option<usize>> {
CACHE.with(|c| {
c.borrow()
.as_ref()
.map(|m| lookup(&m.image_flat, bang_abs_offset))
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scan_nested_matches_independent_per_opener_scans() {
let text = "[[[unbalanced]]";
let matches = scan_nested(text, 0, false);
assert_eq!(lookup(&matches, 0), None);
assert_eq!(lookup(&matches, 1), Some(14));
assert_eq!(lookup(&matches, 2), Some(13));
}
#[test]
fn scan_nested_respects_escapes() {
let text = r"[a\]b]";
let escaped = scan_nested(text, 0, true);
assert_eq!(lookup(&escaped, 0), Some(5));
let naive = scan_nested(text, 0, false);
assert_eq!(lookup(&naive, 0), Some(3));
}
#[test]
fn scan_image_flat_ignores_nesting() {
let text = "![a[b]c]";
let matches = scan_image_flat(text, 0);
assert_eq!(lookup(&matches, 0), Some(5));
}
#[test]
fn guard_restores_previous_on_drop() {
assert_eq!(cached_naive_nested_match(0), None);
{
let _outer = BracketCacheGuard::install(0, "[foo]");
assert_eq!(cached_naive_nested_match(0), Some(Some(4)));
{
let _inner = BracketCacheGuard::install(100, "[bar]");
assert_eq!(cached_naive_nested_match(100), Some(Some(104)));
assert_eq!(cached_naive_nested_match(0), Some(None));
}
assert_eq!(cached_naive_nested_match(0), Some(Some(4)));
}
assert_eq!(cached_naive_nested_match(0), None);
}
#[test]
fn guard_skips_installation_when_no_bracket_present() {
assert_eq!(cached_naive_nested_match(0), None);
let _guard = BracketCacheGuard::install(0, "plain prose, no brackets here");
assert_eq!(cached_naive_nested_match(0), None);
}
}