mod blocks;
mod chain;
mod generic;
mod ident_blocks;
mod parse_atom;
mod space;
pub(crate) use chain::parse_item;
pub(crate) use generic::split_at_depth0;
pub(crate) use space::*;
use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use crate::ast::fresh::at_ref_name;
use crate::ast::*;
use crate::util::{Cursor, compile_error_str};
pub(crate) fn resolve_at_refs(tokens: &[TokenTree]) -> Result<Vec<TokenTree>, TokenStream> {
let mut out = Vec::with_capacity(tokens.len());
let mut i = 0;
while i < tokens.len() {
match &tokens[i] {
TokenTree::Punct(p) if p.as_char() == '@' => {
let at_span = p.span();
match tokens.get(i + 1) {
Some(TokenTree::Literal(lit)) => {
let lit_str = lit.to_string();
let range_lit = parse_range_literal(&lit_str);
if let Some((group, start)) = range_lit
&& matches!(tokens.get(i + 2), Some(TokenTree::Punct(p)) if p.as_char() == '.')
&& matches!(tokens.get(i + 3), Some(TokenTree::Punct(p)) if p.as_char() == '.')
{
let mut consumed = 4;
if matches!(tokens.get(i + 4), Some(TokenTree::Punct(p)) if p.as_char() == '=')
{
consumed += 1;
}
let end = match tokens.get(i + consumed) {
Some(TokenTree::Literal(el)) => {
let Some(e) = el.to_string().parse::<usize>().ok() else {
return Err(compile_error_str(
"batch-impl: a `@N..M` range must end with a number (e.g. `@0..=2`)",
at_span,
));
};
consumed += 1;
Some(e)
}
_ => None,
};
let range = crate::ast::fresh::FreshRange { group, start, end };
let name = crate::ast::fresh::range_fresh_name(range);
let ident = Ident::new(&name, at_span);
out.push(TokenTree::Ident(ident));
i += consumed;
continue;
}
let name = at_ref_name(&lit_str).ok_or_else(|| {
compile_error_str(
"batch-impl: `@` in a type must be followed by a \
position digit (e.g. `@0` or `@0_1`)",
at_span,
)
})?;
let ident = Ident::new(&name, at_span);
out.push(TokenTree::Ident(ident));
i += 2;
}
_ => {
return Err(compile_error_str(
"batch-impl: `@` in a type must be a position digit (e.g. `@0` or `@0_1`)",
at_span,
));
}
}
}
TokenTree::Group(g) => {
let inner = g.stream().into_iter().collect::<Vec<_>>();
let mut new_g =
Group::new(g.delimiter(), resolve_at_refs(&inner)?.into_iter().collect());
new_g.set_span(g.span());
out.push(TokenTree::Group(new_g));
i += 1;
}
_ => {
out.push(tokens[i].clone());
i += 1;
}
}
}
Ok(out)
}
fn parse_range_literal(s: &str) -> Option<(Option<usize>, usize)> {
if let Ok(n) = s.parse::<usize>() {
return Some((None, n));
}
let (l, n) = s.split_once('_')?;
Some((Some(l.parse::<usize>().ok()?), n.parse::<usize>().ok()?))
}
pub(crate) fn parse_primitive(tokens: &[TokenTree], trait_name: Option<&Ident>) -> Ty {
let mut cursor = Cursor::new(tokens);
parse_block(&mut cursor, trait_name).unwrap_or_else(|| crate::parse::generic::primitive(tokens))
}
#[cfg(test)]
mod tests {
fn parse_ok(s: &str) {
let ts: proc_macro2::TokenStream = s.parse().unwrap();
let v = crate::preprocess::angle_collect(&ts.into_iter().collect::<Vec<_>>()).unwrap();
let mut c = crate::util::Cursor::new(&v);
let ty = super::parse_item(&mut c, crate::ast::Op::Comma, None);
assert!(ty.is_some(), "parse failed for: {s}");
}
#[test]
fn fn_mut_parses() {
parse_ok("dyn FnMut(u8) -> u8");
}
#[test]
fn fn_once_parses() {
parse_ok("dyn FnOnce(u8) -> u8");
}
#[test]
fn impl_trait_parses() {
parse_ok("impl Fn(u8) -> u8");
parse_ok("impl Iterator + Clone");
}
#[test]
fn for_hrtb_parses() {
parse_ok("for<'a> fn(&'a u8) -> &'a u8");
}
#[test]
fn prefix_puncts_parse() {
parse_ok("?Sized");
parse_ok("! u8");
parse_ok("self u8");
parse_ok("self.Box u8");
}
}