use std::{
collections::VecDeque,
ops::Range,
borrow::Cow
};
#[derive(PartialEq, Eq, Debug, Clone, Hash, Default)]
pub struct MacroRange<'source> {
pub range: Range<usize>,
pub name: &'source str,
pub arguments: Vec<&'source str>
}
#[must_use]
pub fn find_pair(source: &str) -> Option<MacroRange<'_>> {
let range = find_innermost_brackets(source)?;
let inside = &source[range.start + 1 .. range.end - 1];
let (name, arguments) = split_arguments(inside);
Some(MacroRange { range, name, arguments })
}
#[allow(clippy::range_plus_one)]
fn find_innermost_brackets(string: &str) -> Option<Range<usize>> {
let mut last_escaped = false;
let mut start = None;
for (idx, chr) in string.char_indices() {
if last_escaped {
last_escaped = false;
continue;
}
last_escaped = chr == '\\';
match chr {
'[' =>
start = Some(idx),
']' if start.is_some() => {
let Some(start) = start else { unreachable!("must always be Some") }; return Some(start .. idx + 1);
},
_ => {}
}
}
None
}
fn split_arguments(inside: &str) -> (&str, Vec<&str>) {
let mut argument_spans = VecDeque::new();
let mut last_escaped = false;
let mut old_start = 0usize;
for (idx, char) in inside.char_indices() {
if last_escaped {
last_escaped = false;
continue;
}
last_escaped = char == '\\';
if char == '/' {
argument_spans.push_back(old_start .. idx);
old_start = idx + 1;
}
}
argument_spans.push_back(old_start .. inside.len());
let name = argument_spans.pop_front().expect("we just pushed something");
(&inside[name], argument_spans.into_iter().map(|range| &inside[range]).collect())
}
pub(crate) fn unescape(original: &str) -> Cow<str> {
let mut found_escape = false;
let mut last_escape = false;
let mut string = String::new();
for (idx, char) in original.char_indices() {
if !last_escape && char == '\\' {
if !found_escape {
string += &original[..idx];
}
found_escape = true;
last_escape = true;
continue;
}
if !found_escape { continue }
last_escape = false;
string.push(char);
}
if found_escape {
Cow::Owned(string)
} else {
Cow::Borrowed(original)
}
}
#[cfg(test)]
mod test {
use crate::parsing::*;
#[test]
fn bracket_test() {
assert_eq!(find_innermost_brackets(r"[a[b[c[d]c][e]b]a]"), Some(6 .. 9));
assert_eq!(find_innermost_brackets(r"\[[]\]"), Some(2 .. 4));
assert_eq!(find_innermost_brackets(r"[[\][]"), Some(4 .. 6));
assert_eq!(find_innermost_brackets(r"only open [[[ \]"), None);
assert_eq!(find_innermost_brackets(r"[ no close \]\]"), None);
}
}