use proc_macro2::{Span, TokenStream, TokenTree};
use crate::error::{Diag, diag};
pub(crate) struct Cursor {
tokens: Vec<TokenTree>,
pos: usize,
close: Span,
}
impl Cursor {
pub(crate) fn new(stream: TokenStream, close: Option<Span>) -> Self {
let tokens: Vec<TokenTree> = stream.into_iter().collect();
let close = close
.or_else(|| tokens.last().map(TokenTree::span))
.unwrap_or_else(Span::call_site);
Cursor {
tokens,
pos: 0,
close,
}
}
pub(crate) fn peek(&self) -> Option<&TokenTree> {
self.tokens.get(self.pos)
}
pub(crate) fn peek_at(&self, n: usize) -> Option<&TokenTree> {
self.tokens.get(self.pos + n)
}
pub(crate) fn bump(&mut self) {
self.pos += 1;
}
pub(crate) fn at_end(&self) -> bool {
self.pos >= self.tokens.len()
}
pub(crate) fn close(&self) -> Span {
self.close
}
pub(crate) fn peek_punct(&self, c: char) -> bool {
is_punct(self.peek(), c)
}
pub(crate) fn peek_word(&self, word: &str) -> bool {
matches!(self.peek(), Some(TokenTree::Ident(id)) if id == word)
}
pub(crate) fn expected(&self, what: &str) -> Diag {
match self.peek() {
None => diag(self.close, format!("expected {what}")),
Some(TokenTree::Punct(p)) if p.as_char() == '/' => {
diag(p.span(), "division is not supported")
}
Some(token) => diag(
token.span(),
format!("unexpected {}, expected {what}", describe(token)),
),
}
}
}
pub(crate) fn is_punct(token: Option<&TokenTree>, c: char) -> bool {
matches!(token, Some(TokenTree::Punct(p)) if p.as_char() == c)
}
fn describe(token: &TokenTree) -> String {
match token {
TokenTree::Group(g) => {
let open = match g.delimiter() {
proc_macro2::Delimiter::Parenthesis => "`(`",
proc_macro2::Delimiter::Brace => "`{`",
proc_macro2::Delimiter::Bracket => "`[`",
proc_macro2::Delimiter::None => "group",
};
format!("token {open}")
}
other => {
let text: String = other.to_string().chars().take(24).collect();
format!("token `{text}`")
}
}
}