use crate::apply::{err_ty, err_ty_at};
use crate::ast::*;
use crate::parse::generic::empty;
use crate::parse::{parse_item, parse_primitive};
use crate::util::{Cursor, contains_punct};
use proc_macro2::{Delimiter, Ident, Spacing, TokenStream, TokenTree};
pub(crate) fn parse_attribute(
tokens: &[TokenTree],
) -> Option<(TokenStream, &[TokenTree])> {
match tokens {
[TokenTree::Punct(hash), TokenTree::Group(group), rest @ ..]
if hash.as_char() == '#' && group.delimiter() == delimiter![[]] =>
{
(group.stream(), rest).into()
}
_ => None,
}
}
pub(crate) fn parse_function(
tokens: &[TokenTree], trait_name: Option<&Ident>,
) -> Option<Ty> {
let [TokenTree::Ident(name), TokenTree::Group(args), rest @ ..] = tokens else {
return None;
};
if name != "fn" || args.delimiter() != delimiter![()] {
return None;
}
let fn_span = name.span();
let args_tokens = args.stream().into_iter().collect::<Vec<_>>();
let mut cursor = Cursor::new(&args_tokens);
let mut parameters = vec![];
if cursor.is_punct(',') {
return err_ty_at(
"batch-impl: `fn` parameter list cannot start with `,`",
args.span(),
)
.into();
}
while let Some(parameter) = parse_item(&mut cursor, Op::Comma, trait_name) {
parameters.push(parameter);
}
if !cursor.at_end() {
return err_ty_at(
"batch-impl: unexpected tokens in the `fn` parameter list",
cursor.span(),
)
.into();
}
let return_type = match rest {
[TokenTree::Punct(dash), TokenTree::Punct(arrow), return_tokens @ ..]
if dash.as_char() == '-'
&& dash.spacing() == Spacing::Joint
&& arrow.as_char() == '>'
&& !return_tokens.is_empty() =>
{
parse_primitive(return_tokens, trait_name).into()
}
[] => None,
_ => {
return err_ty_at(
"batch-impl: unexpected tokens after the `fn` parameter list \
(a return type is written `fn(A) -> B` or `fn(A)-B`)",
rest[0].span(),
)
.into();
}
};
TyFn(parameters.into(), return_type, false).to_ty().with_span(fn_span).into()
}
pub(crate) fn parse_prefix(tokens: &[TokenTree]) -> Option<(TyPrefix, &[TokenTree])> {
match tokens {
[TokenTree::Punct(p), TokenTree::Ident(name), rest @ ..]
if p.as_char() == '&' && name == "mut" =>
{
(TyPrefix::RefMut, rest).into()
}
[TokenTree::Punct(p), rest @ ..] if p.as_char() == '&' => {
(TyPrefix::Ref, rest).into()
}
[TokenTree::Punct(p), TokenTree::Ident(name), rest @ ..]
if p.as_char() == '*' && name == "const" =>
{
(TyPrefix::PtrConst, rest).into()
}
[TokenTree::Punct(p), TokenTree::Ident(name), rest @ ..]
if p.as_char() == '*' && name == "mut" =>
{
(TyPrefix::PtrMut, rest).into()
}
[TokenTree::Ident(name), rest @ ..] if name == "self" => {
(TyPrefix::SelfType, rest).into()
}
[TokenTree::Ident(name), rest @ ..] if name == "unsafe" => {
(TyPrefix::Unsafe, rest).into()
}
_ => None,
}
}
pub(crate) fn parse_range(tokens: &[TokenTree]) -> Option<Ty> {
let [
TokenTree::Literal(start),
TokenTree::Punct(first_dot),
TokenTree::Punct(second_dot),
rest @ ..,
] = tokens
else {
return None;
};
if first_dot.as_char() != '.'
|| second_dot.as_char() != '.'
|| first_dot.spacing() != Spacing::Joint
{
return None;
}
let span = tokens[0].span();
let start = match start.to_string().parse::<usize>() {
Ok(n) => n,
Err(_) => {
return Some(err_ty_at(
"batch-impl: range start must be an integer",
span,
));
}
};
let (inclusive, end_lit) = match rest {
[TokenTree::Literal(end)] => (false, end),
[TokenTree::Punct(eq), TokenTree::Literal(end)]
if eq.as_char() == '=' && second_dot.spacing() == Spacing::Joint =>
{
(true, end)
}
_ => return None,
};
let end = match end_lit.to_string().parse::<usize>() {
Ok(n) => n,
Err(_) => {
return Some(err_ty_at(
"batch-impl: range end must be an integer",
end_lit.span(),
));
}
};
TyRange { start, end, inclusive }.to_ty().with_span(span).into()
}
fn lone_splat(contents: &[TokenTree]) -> bool {
matches!(
contents,
[TokenTree::Punct(p), TokenTree::Group(g)]
if p.as_char() == '*'
&& matches!(
g.delimiter(),
Delimiter::Parenthesis | Delimiter::Bracket
)
)
}
pub(crate) fn parse_group(
group: &proc_macro2::Group, trait_name: Option<&Ident>,
) -> Ty {
let contents = group.stream().into_iter().collect::<Vec<_>>();
match group.delimiter() {
delimiter![()] => {
if contents.is_empty()
|| contains_punct(&contents, ',')
|| lone_splat(&contents)
{
TyTuple(parse_list(&contents, Op::Comma, trait_name))
.to_ty()
.with_span(group.span())
} else if matches!(contents.as_slice(), [TokenTree::Group(g)]
if g.delimiter() == delimiter![<>])
{
err_ty_at(
"batch-impl: a generic declaration `<...>` inside `(...)` needs \
the trailing-comma tuple form `(<T: Bound>,)^N`",
contents[0].span(),
)
} else {
let inner =
parse_item(&mut Cursor::new(&contents), Op::Dash, trait_name)
.unwrap_or_else(empty);
TyGroup(Box::new(inner)).to_ty().with_span(group.span())
}
}
delimiter![[]] => parse_array_group(&contents, group.span(), trait_name),
delimiter![{}] => TyWithCode(None, TyCodeBlock(group.stream()))
.to_ty()
.with_span(group.span()),
_ => err_ty_at(
"batch-impl: unexpected transparent group in a type position (angle-collect should have flattened it)",
group.span(),
),
}
}
fn parse_array_group(
contents: &[TokenTree], span: proc_macro2::Span, trait_name: Option<&Ident>,
) -> Ty {
if contains_punct(contents, ',') || lone_splat(contents) {
let flat = parse_list(contents, Op::Comma, trait_name);
TyArray(flat).to_ty().with_span(span)
} else if contents.is_empty() {
TyPrimitiveArray(None, None).to_ty().with_span(span)
} else {
let mut cursor = Cursor::new(contents);
let element =
parse_item(&mut cursor, Op::Semi, trait_name).unwrap_or_else(empty);
if cursor.is_punct(';') {
cursor.bump();
let length_tokens = cursor.take_rest();
if length_tokens.is_empty()
|| length_tokens.iter().any(|t| {
matches!(t, TokenTree::Punct(p) if p.as_char() == ';' || p.as_char() == ',')
})
{
return err_ty_at(
"batch-impl: array length `[T; N]` missing or malformed (write `[u8; 3]`)",
span,
);
}
let length = length_tokens.iter().cloned().collect::<TokenStream>();
TyPrimitiveArray(element.into(), length.into()).to_ty().with_span(span)
} else {
TyPrimitiveArray(element.into(), None).to_ty().with_span(span)
}
}
}
pub(crate) fn parse_list(
tokens: &[TokenTree], level: Op, trait_name: Option<&Ident>,
) -> Vec<Ty> {
let mut cursor = Cursor::new(tokens);
let mut items = vec![];
if cursor.is_punct(',') {
items.push(err_ty("batch-impl: a list cannot start with `,`"));
}
items.extend(std::iter::from_fn(|| {
parse_item(&mut cursor, level, trait_name)
}));
items
}