use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
use crate::{Error, Result};
pub(crate) struct Cursor {
tokens: Vec<TokenTree>,
pos: usize,
end: Span,
}
impl Cursor {
pub(crate) fn new(stream: TokenStream, end: Span) -> Self {
Cursor {
tokens: stream.into_iter().collect(),
pos: 0,
end,
}
}
pub(crate) fn inside(group: &Group) -> Self {
Cursor::new(group.stream(), group.span())
}
pub(crate) fn is_empty(&self) -> bool {
self.pos >= self.tokens.len()
}
pub(crate) fn peek(&self) -> Option<&TokenTree> {
self.tokens.get(self.pos)
}
pub(crate) fn next(&mut self) -> Option<TokenTree> {
let tt = self.tokens.get(self.pos).cloned();
if tt.is_some() {
self.pos += 1;
}
tt
}
pub(crate) fn span(&self) -> Span {
self.peek().map_or(self.end, TokenTree::span)
}
pub(crate) fn peek_ident(&self, name: &str) -> bool {
matches!(self.peek(), Some(TokenTree::Ident(i)) if i.to_string() == name)
}
pub(crate) fn peek_group(&self, delimiter: Delimiter) -> bool {
matches!(self.peek(), Some(TokenTree::Group(g)) if g.delimiter() == delimiter)
}
pub(crate) fn eat_punct(&mut self, ch: char) -> Option<Punct> {
match self.peek() {
Some(TokenTree::Punct(p)) if p.as_char() == ch => {
let p = p.clone();
self.pos += 1;
Some(p)
}
_ => None,
}
}
pub(crate) fn eat_ident(&mut self, name: &str) -> Option<Ident> {
match self.peek() {
Some(TokenTree::Ident(i)) if i.to_string() == name => {
let i = i.clone();
self.pos += 1;
Some(i)
}
_ => None,
}
}
pub(crate) fn eat_group(&mut self, delimiter: Delimiter) -> Option<Group> {
match self.peek() {
Some(TokenTree::Group(g)) if g.delimiter() == delimiter => {
let g = g.clone();
self.pos += 1;
Some(g)
}
_ => None,
}
}
pub(crate) fn expect_punct(&mut self, ch: char, expected: &str) -> Result<Punct> {
self.eat_punct(ch)
.ok_or_else(|| Error::new(self.span(), format!("expected {expected}")))
}
pub(crate) fn expect_ident(&mut self, expected: &str) -> Result<Ident> {
match self.next() {
Some(TokenTree::Ident(i)) => Ok(i),
Some(other) => Err(Error::new(other.span(), format!("expected {expected}"))),
None => Err(Error::new(self.end, format!("expected {expected}"))),
}
}
pub(crate) fn expect_group(&mut self, delimiter: Delimiter, expected: &str) -> Result<Group> {
self.eat_group(delimiter)
.ok_or_else(|| Error::new(self.span(), format!("expected {expected}")))
}
pub(crate) fn until_comma(&mut self) -> Vec<TokenTree> {
let mut out = Vec::new();
let mut angles = Angles::default();
while let Some(tt) = self.peek() {
if angles.at_top() && matches!(tt, TokenTree::Punct(p) if p.as_char() == ',') {
self.pos += 1;
break;
}
angles.step(tt);
out.push(tt.clone());
self.pos += 1;
}
out
}
pub(crate) fn until_close_angle(&mut self) -> Result<Vec<TokenTree>> {
let mut out = Vec::new();
let mut angles = Angles::default();
while let Some(tt) = self.peek() {
if angles.at_top() && matches!(tt, TokenTree::Punct(p) if p.as_char() == '>') {
self.pos += 1;
return Ok(out);
}
angles.step(tt);
out.push(tt.clone());
self.pos += 1;
}
Err(Error::new(self.end, "expected `>` to close the generics"))
}
}
#[derive(Default)]
pub(crate) struct Angles {
depth: u32,
after_joint_minus: bool,
}
impl Angles {
pub(crate) fn at_top(&self) -> bool {
self.depth == 0
}
pub(crate) fn step(&mut self, tt: &TokenTree) {
let arrow_tail = self.after_joint_minus;
self.after_joint_minus = false;
if let TokenTree::Punct(p) = tt {
match p.as_char() {
'<' => self.depth += 1,
'>' if !arrow_tail => self.depth = self.depth.saturating_sub(1),
'-' if p.spacing() == Spacing::Joint => self.after_joint_minus = true,
_ => {}
}
}
}
}
pub(crate) fn split_commas(tokens: Vec<TokenTree>) -> Vec<Vec<TokenTree>> {
let mut pieces = Vec::new();
let mut current = Vec::new();
let mut angles = Angles::default();
for tt in tokens {
if angles.at_top() && matches!(&tt, TokenTree::Punct(p) if p.as_char() == ',') {
pieces.push(std::mem::take(&mut current));
continue;
}
angles.step(&tt);
current.push(tt);
}
if !current.is_empty() {
pieces.push(current);
}
pieces
}
pub(crate) fn find_punct(tokens: &[TokenTree], ch: char) -> Option<usize> {
let mut angles = Angles::default();
for (i, tt) in tokens.iter().enumerate() {
if angles.at_top() && matches!(tt, TokenTree::Punct(p) if p.as_char() == ch) {
return Some(i);
}
angles.step(tt);
}
None
}