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 span(&self) -> proc_macro2::Span {
self.peek().map(|t| t.span()).unwrap_or_else(proc_macro2::Span::call_site)
}
pub(crate) fn peek(&self) -> Option<&'a TokenTree> {
self.tokens.get(self.pos)
}
pub(crate) fn is_punct(&self, ch: char) -> bool {
is_punct_at(self.tokens, self.pos, 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) fn scan_stop(tokens: &[TokenTree], stop: &[char]) -> Option<usize> {
for (index, token) in tokens.iter().enumerate() {
if matches!(token, TokenTree::Punct(p) if stop.contains(&p.as_char())) {
let is_arrow_dash = is_joint_punct_at(tokens, index, '-')
&& matches!(tokens.get(index + 1), Some(next) if is_punct(next, '>'));
if !is_arrow_dash {
return index.into();
}
}
}
None
}
pub(crate) fn is_punct(token: &TokenTree, punctuation: char) -> bool {
matches!(token, TokenTree::Punct(p) if p.as_char() == punctuation)
}
pub(crate) fn is_punct_at(tokens: &[TokenTree], index: usize, ch: char) -> bool {
tokens.get(index).is_some_and(|t| is_punct(t, ch))
}
pub(crate) fn is_joint_punct_at(
tokens: &[TokenTree], index: usize, ch: char,
) -> bool {
matches!(tokens.get(index), Some(TokenTree::Punct(p))
if p.as_char() == ch && p.spacing() == Spacing::Joint)
}
pub(crate) fn bracket_is_passthrough(tokens: &[TokenTree], index: usize) -> bool {
index > 0
&& matches!(&tokens[index - 1], TokenTree::Punct(p)
if p.as_char() == '!' || p.as_char() == '#')
}
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))
}