use alloc::{string::ToString, vec::Vec};
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{
Expr, Ident, LitInt, LitStr, Path, Token,
parse::{Parse, ParseStream},
punctuated::Punctuated,
token::Paren,
};
use super::ErrorList;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Param {
pub name: Option<Ident>,
pub kind: ParamKind,
}
impl Param {
#[inline]
#[must_use]
pub const fn lone(kind: ParamKind) -> Self {
Self { name: None, kind }
}
#[inline]
#[must_use]
pub const fn identified(name: Ident, kind: ParamKind) -> Self {
Self { name: Some(name), kind }
}
}
impl Param {
pub fn classify<'a>(iter: impl IntoIterator<Item = &'a syn::Attribute>) -> syn::Result<(Vec<Self>, Vec<&'a syn::Attribute>)> {
let attr_iter = iter.into_iter();
let (attr_len, ..) = attr_iter.size_hint();
let mut param_list = Vec::with_capacity(attr_len);
let mut attr_list = Vec::new();
let mut error_list = ErrorList::empty();
for attr in attr_iter {
if let Some(ident) = attr.path().get_ident() {
if ident == "error" {
match attr.parse_args_with(Param::parse) {
Ok(param) => param_list.push(param),
Err(error) => error_list.append(error),
}
} else {
attr_list.push(attr);
}
} else {
attr_list.push(attr);
}
}
error_list.compose((param_list, attr_list))
}
}
impl Parse for Param {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
let ident: Ident = input.parse()?;
let param = if ident == "inline" {
let inline_opts: InlineOptions = if input.peek(Paren) {
let inline_content;
syn::parenthesized!(inline_content in input);
inline_content.parse()?
} else {
InlineOptions::default()
};
Param::identified(ident, ParamKind::Inline(inline_opts))
} else if ident == "import" {
let import_content;
syn::parenthesized!(import_content in input);
let import_root: ImportRoot = import_content.parse()?;
Param::identified(ident, ParamKind::Import(import_root))
} else if ident == "source" {
let source_content;
syn::parenthesized!(source_content in input);
let source_field: FieldRef = source_content.parse()?;
Param::identified(ident, ParamKind::Source(source_field))
} else if ident == "transparent" {
let trans_content;
syn::parenthesized!(trans_content in input);
let source_field: Transparent = trans_content.parse()?;
Param::identified(ident, ParamKind::Transparent(source_field))
} else if ident == "from" {
Param::identified(ident, ParamKind::From)
} else {
return Err(syn::Error::new_spanned(
ident,
"expected `inline`, `import`, `source`, `transparent`, or `from`",
));
};
Ok(param)
} else if lookahead.peek(LitStr) {
Ok(Param::lone(ParamKind::Format(Format::parse(input)?)))
} else {
Err(lookahead.error())
}
}
}
impl Param {
#[inline]
pub fn attribute(target_meta: &syn::Meta) -> syn::Result<Self> {
syn::parse2::<Self>(target_meta.to_token_stream()).map_err(|error| syn::Error::new_spanned(target_meta, error))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ParamKind {
Inline(InlineOptions),
Import(ImportRoot),
Format(Format),
Source(FieldRef),
Transparent(Transparent),
From,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, Copy)]
pub enum InlineOptions {
#[default]
Neutral,
Never,
Always,
}
impl Parse for InlineOptions {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
match ident.to_string().as_str() {
"neutral" => Ok(Self::Neutral),
"never" => Ok(Self::Never),
"always" => Ok(Self::Always),
_ => Err(syn::Error::new_spanned(ident, "expected `neutral`, `never` or `always`")),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ImportRoot(pub Path);
impl Parse for ImportRoot {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
let path: Path = input.parse()?;
Ok(Self(path))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Format {
pub format: LitStr,
pub format_args: Punctuated<Expr, Token![,]>,
}
impl Parse for Format {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
let format: LitStr = input.parse()?;
let format_args = if input.peek(Token![,]) {
let _ = input.parse::<Token![,]>()?;
Punctuated::<Expr, Token![,]>::parse_terminated(input)?
} else {
Punctuated::new()
};
Ok(Self { format, format_args })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FieldRef {
Named(Ident),
Indexed(usize),
}
impl Parse for FieldRef {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
let ident: Ident = input.parse()?;
Ok(Self::Named(ident))
} else if lookahead.peek(LitInt) {
let lit_int: LitInt = input.parse()?;
let index = lit_int
.base10_parse::<usize>()
.map_err(|_| syn::Error::new_spanned(lit_int, "expected valid field index"))?;
Ok(Self::Indexed(index))
} else {
Err(lookahead.error())
}
}
}
impl ToTokens for FieldRef {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
FieldRef::Named(ident) => ident.to_tokens(tokens),
FieldRef::Indexed(index) => LitInt::new(index.to_string().as_str(), Span::call_site()).to_tokens(tokens),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ForwardSource {
#[default]
No,
Yes,
}
impl Parse for ForwardSource {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
if ident == "forward" {
Ok(Self::Yes)
} else {
Err(syn::Error::new_spanned(
ident,
"expected just `source`, no internal further parameters",
))
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Transparent(pub FieldRef);
impl Parse for Transparent {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
let field_ref: FieldRef = input.parse()?;
Ok(Self(field_ref))
}
}