mod generic;
pub(crate) use generic::split_at_depth0;
mod parse_atom;
use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use quote::quote;
use crate::apply::{err_ty, err_ty_at};
use crate::ast::*;
use crate::parse::generic::{
is_trait_base, parse_angle_bracket_contents, parse_generic, parse_type_params,
primitive,
};
use crate::parse::parse_atom::{
parse_attribute, parse_function, parse_group, parse_prefix, parse_range,
};
use crate::util::{Cursor, compile_error_str};
pub(crate) fn parse_item(
cursor: &mut Cursor, level: Op, trait_name: Option<&Ident>,
) -> Option<Ty> {
match level {
Op::Semi | Op::Comma => loop {
if let Some(item) = parse_operand(cursor, level, trait_name) {
return item.into();
}
if cursor.is_punct(',') {
cursor.bump();
if cursor.is_punct(',') {
let sp = cursor
.peek()
.map(|t| t.span())
.unwrap_or_else(proc_macro2::Span::call_site);
return err_ty_at(
"batch-impl: missing operand between consecutive commas `,,` (e.g. `A,,B`)",
sp,
)
.into();
}
} else {
return None;
}
},
Op::Dash => parse_binary_chain(cursor, Op::Dash, trait_name, '-', false),
Op::Caret => parse_binary_chain(cursor, Op::Caret, trait_name, '^', true),
Op::Prim => parse_primitive(cursor.take_rest(), trait_name).into(),
}
}
fn parse_binary_chain(
cursor: &mut Cursor, level: Op, trait_name: Option<&Ident>, op_punct: char,
right_assoc: bool,
) -> Option<Ty> {
let hint = if op_punct == '-' { " (e.g. `T-U`)" } else { " (e.g. `T^U`)" };
let mut items = match parse_operand(cursor, level, trait_name) {
Some(op) => vec![op],
None if cursor.at_end() => return None,
None => {
return err_ty_at(
&format!("batch-impl: missing operand before `{}`{}", op_punct, hint),
cursor_span(cursor),
)
.into();
}
};
if is_empty_operand(&items[0]) {
return err_ty_at(
&format!("batch-impl: missing operand before `{}`{}", op_punct, hint),
cursor_span(cursor),
)
.into();
}
while cursor.is_punct(op_punct) {
let op_span = cursor_span(cursor);
cursor.bump();
let Some(op) = parse_operand(cursor, level, trait_name) else {
return err_ty_at(
&format!("batch-impl: missing operand after `{}`{}", op_punct, hint),
op_span,
)
.into();
};
if is_empty_operand(&op) {
return err_ty_at(
&format!("batch-impl: missing operand after `{}`{}", op_punct, hint),
op_span,
)
.into();
}
items.push(op);
}
if right_assoc {
items.into_iter().rev().reduce(|acc, x| x.apply(acc))
} else {
items.into_iter().reduce(|acc, x| acc.apply(x))
}
}
fn cursor_span(cursor: &Cursor) -> proc_macro2::Span {
cursor.peek().map(|t| t.span()).unwrap_or_else(proc_macro2::Span::call_site)
}
fn is_empty_operand(ty: &Ty) -> bool {
matches!(&ty.kind, TyKind::Primitive(p) if p.0.is_empty())
}
fn parse_operand(
cursor: &mut Cursor, level: Op, trait_name: Option<&Ident>,
) -> Option<Ty> {
if cursor.at_end() {
return None;
}
if cursor.is_punct('@') {
let at_span = cursor.span();
cursor.bump(); return match cursor.peek() {
Some(TokenTree::Literal(lit)) => {
match at_ref_name(&lit.to_string()) {
Some(name) => {
cursor.bump(); if cursor.is_punct('.') {
return Some(err_ty_at(
"batch-impl: `@N..M` range references are only \
allowed as a where-predicate subject \
(e.g. `where{@0..=2: Clone}`)",
at_span,
));
}
let ident = Ident::new(&name, at_span);
Some(
Ty::new(at_span, TyPrimitive(quote!(#ident)).into())
.with_span(at_span),
)
}
None => Some(err_ty_at(
"batch-impl: `@` in a type must be followed by a position \
digit (e.g. `@0` or `@0_1`)",
at_span,
)),
}
}
_ => Some(err_ty_at(
"batch-impl: `@` in a type must be a position digit (e.g. `@0` or `@0_1`)",
at_span,
)),
};
}
let segment = cursor.take_segment(level.stop_chars());
parse_item(&mut Cursor::new(segment), level.next()?, trait_name)
}
fn at_ref_name(lit: &str) -> Option<String> {
if let Ok(n) = lit.parse::<usize>() {
return Some(format!("_Param_{}_BatchGen_", n));
}
if let Some((g, i)) = lit.split_once('_')
&& let (Ok(g), Ok(i)) = (g.parse::<usize>(), i.parse::<usize>())
{
return Some(format!("_Param_{}_{}_BatchGen_", g, i));
}
None
}
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 name =
at_ref_name(&lit.to_string()).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)
}
pub(crate) fn parse_primitive(
tokens: &[TokenTree], trait_name: Option<&Ident>,
) -> Ty {
let mut attaches = vec![];
let mut rest = tokens;
loop {
let split = split_trailing_body(rest);
match (split.body, split.is_where) {
(Some(body), false) => {
attaches.push(TyWithCode(None, TyCodeBlock(body)).into());
rest = split.tokens;
}
(Some(w), true) => {
attaches.push(TyWithWhere(None, TyWhere(w)).into());
rest = split.tokens;
}
_ => break,
}
}
let mut ty = if rest.is_empty() {
match attaches.pop() {
Some(inner) => inner,
None => parse_primary(rest, trait_name),
}
} else {
parse_primary(rest, trait_name)
};
while let Some(block) = attaches.pop() {
ty = block.apply(ty);
}
ty
}
struct TrailingBody<'a> {
tokens: &'a [TokenTree],
body: Option<TokenStream>,
is_where: bool,
}
fn split_trailing_body(tokens: &[TokenTree]) -> TrailingBody<'_> {
match tokens.last() {
Some(TokenTree::Group(group)) if group.delimiter() == delimiter![{}] => {
if tokens.len() >= 2
&& let TokenTree::Punct(p) = &tokens[tokens.len() - 2]
&& p.as_char() == '!'
{
return TrailingBody { tokens, body: None, is_where: false };
}
if tokens.len() >= 2
&& let TokenTree::Ident(i) = &tokens[tokens.len() - 2]
&& *i == "where"
{
return TrailingBody {
tokens: &tokens[..tokens.len() - 2],
body: group.stream().into(),
is_where: true,
};
}
TrailingBody {
tokens: &tokens[..tokens.len() - 1],
body: group.stream().into(),
is_where: false,
}
}
_ => TrailingBody { tokens, body: None, is_where: false },
}
}
fn attach_wrapper(
kind: TyKind, rest: &[TokenTree], trait_name: Option<&Ident>,
) -> Ty {
let base = Ty::new(proc_macro2::Span::call_site(), kind);
if rest.is_empty() { base } else { base.apply(parse_primitive(rest, trait_name)) }
}
fn parse_primary(tokens: &[TokenTree], trait_name: Option<&Ident>) -> Ty {
if let Some((attr, rest)) = parse_attribute(tokens) {
return attach_wrapper(
TyWithAttr(TyAttr(attr), None).into(),
rest,
trait_name,
);
}
if let Some(function) = parse_function(tokens, trait_name) {
return function;
}
if let [TokenTree::Ident(name)] = tokens
&& name == "fn"
{
return TyFn(None, None, false).into();
}
if let Some((prefix, rest)) = parse_prefix(tokens) {
if matches!(prefix, TyPrefix::Unsafe) && !rest.is_empty() {
if matches!(rest.first(), Some(TokenTree::Ident(f)) if f == "fn") {
let inner = parse_primitive(rest, trait_name);
return match inner.kind {
TyKind::Fn(mut f) => {
f.2 = true;
Ty::new(inner.span, TyKind::Fn(f))
}
other => Ty::new(inner.span, other),
};
}
return err_ty(
"batch-impl: `unsafe` can only qualify a fn type (e.g. `unsafe fn(u32) -> u32`) \
or act as a bare impl marker (e.g. `unsafe^T`)",
);
}
let inner =
attach_wrapper(TyWithPrefix(prefix, None).into(), rest, trait_name);
return inner;
}
if let Some(range) = parse_range(tokens) {
return range;
}
if let [TokenTree::Literal(literal)] = tokens
&& let Ok(number) = literal.to_string().parse()
{
return TyNum(number).into();
}
if let [TokenTree::Group(group)] = tokens
&& group.delimiter() != delimiter![<>]
{
return parse_group(group, trait_name);
}
if let Some((base, args, rest)) = parse_generic(tokens) {
let args_vec: Vec<_> = args.into_iter().collect();
let params = parse_angle_bracket_contents(&args_vec, trait_name);
let generic = if is_trait_base(&base, trait_name) {
TyTrait(base.iter().cloned().collect(), params).into()
} else {
if !rest.is_empty()
&& !matches!(rest.first(), Some(TokenTree::Group(g)) if g.delimiter() == delimiter![<>])
{
return primitive(tokens);
}
TyGeneric(primitive(&base).into(), params).into()
};
return if rest.is_empty() {
generic
} else {
generic.apply(parse_primitive(&rest, trait_name))
};
}
if let Some((args, rest)) = parse_type_params(tokens) {
let args_vec: Vec<_> = args.into_iter().collect();
let params = parse_angle_bracket_contents(&args_vec, trait_name);
let params = params.into();
return if rest.is_empty() {
params
} else {
params.apply(parse_primitive(&rest, trait_name))
};
}
primitive(tokens)
}