use quote::quote;
use crate::apply::err_ty_at;
use crate::ast::fresh::at_ref_name;
use crate::ast::*;
use crate::parse::parse_primitive;
use crate::util::Cursor;
use proc_macro2::{Ident, TokenTree};
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(TyPrimitive(quote!(#ident)).to_ty().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)
}