use proc_macro2::{Spacing, TokenTree};
pub(crate) struct Cursor<'a> {
tokens: &'a [TokenTree],
pos: usize,
}
impl<'a> Cursor<'a> {
pub(crate) fn new(tokens: &'a [TokenTree]) -> Self {
Self { tokens, pos: 0 }
}
pub(crate) fn at_end(&self) -> bool {
self.pos >= self.tokens.len()
}
pub(crate) fn peek(&self) -> Option<&'a TokenTree> {
self.tokens.get(self.pos)
}
pub(crate) fn peek_at(&self, offset: usize) -> Option<&'a TokenTree> {
self.tokens.get(self.pos + offset)
}
pub(crate) fn is_punct(&self, ch: char) -> bool {
matches!(self.tokens.get(self.pos), Some(t) if is_punct(t, ch))
}
pub(crate) fn prev_is_punct(&self, ch: char) -> bool {
self.pos > 0
&& matches!(self.tokens.get(self.pos - 1), Some(t) if is_punct(t, ch))
}
pub(crate) fn is_single_colon(&self) -> bool {
is_single_colon(self.tokens, self.pos)
}
pub(crate) fn bump(&mut self) {
self.pos += 1;
}
pub(crate) fn pos(&self) -> usize {
self.pos
}
pub(crate) fn slice_since(&self, start: usize) -> &'a [TokenTree] {
&self.tokens[start..self.pos]
}
pub(crate) fn take_segment(&mut self, stop: &[char]) -> &'a [TokenTree] {
let tokens = self.tokens;
let rest = &tokens[self.pos..];
let end = scan_stop(rest, stop).unwrap_or(rest.len());
self.pos += end;
&rest[..end]
}
pub(crate) fn take_rest(&mut self) -> &'a [TokenTree] {
let tokens = self.tokens;
let rest = &tokens[self.pos..];
self.pos = tokens.len();
rest
}
}
pub(crate) enum ScanMode {
Lossy,
Strict,
}
pub(crate) fn scan_with(
tokens: &[TokenTree], stop: &[char], mode: ScanMode,
) -> Option<usize> {
let mut depth = 0usize;
for (index, token) in tokens.iter().enumerate() {
if is_punct(token, '<') {
depth += 1;
} else if is_punct(token, '>') && !is_arrow(tokens, index) {
match mode {
ScanMode::Lossy => depth = depth.saturating_sub(1),
ScanMode::Strict => {
depth = depth.checked_sub(1)?;
if depth == 0 {
return index.into();
}
}
}
} else if depth == 0
&& matches!(token, TokenTree::Punct(p) if stop.contains(&p.as_char()))
{
let is_arrow_dash = matches!(token, TokenTree::Punct(p)
if p.as_char() == '-' && p.spacing() == Spacing::Joint)
&& matches!(tokens.get(index + 1), Some(next) if is_punct(next, '>'));
if !is_arrow_dash {
return index.into();
}
}
}
None
}
pub(crate) fn scan_stop(tokens: &[TokenTree], stop: &[char]) -> Option<usize> {
scan_with(tokens, stop, ScanMode::Lossy)
}
pub(crate) fn is_punct(token: &TokenTree, punctuation: char) -> bool {
matches!(token, TokenTree::Punct(p) if p.as_char() == punctuation)
}
pub(crate) fn is_arrow(tokens: &[TokenTree], index: usize) -> bool {
index > 0
&& matches!(&tokens[index - 1], TokenTree::Punct(p)
if p.as_char() == '-' && p.spacing() == Spacing::Joint)
}
pub(crate) fn is_single_colon(tokens: &[TokenTree], index: usize) -> bool {
let Some(TokenTree::Punct(p)) = tokens.get(index) else {
return false;
};
p.as_char() == ':'
&& !(index > 0
&& matches!(&tokens[index - 1], TokenTree::Punct(q)
if q.as_char() == ':' && q.spacing() == Spacing::Joint)
|| index + 1 < tokens.len()
&& matches!(&tokens[index + 1], TokenTree::Punct(q)
if q.as_char() == ':' && p.spacing() == Spacing::Joint))
}
pub(crate) fn contains_punct(tokens: &[TokenTree], punctuation: char) -> bool {
tokens.iter().any(|token| is_punct(token, punctuation))
}