use crate::{error::compile_error_at, Result};
use proc_macro2::{Delimiter, Group, Span};
use quote::ToTokens;
use syn::{
parse::{Parse, ParseStream, Parser},
spanned::Spanned,
Attribute, Ident, Lit, Path, Token,
};
pub(crate) fn fold_attrs<A, R>(
attrs: &[Attribute],
filter: fn(&Attribute) -> bool,
parse: impl Fn(Attr) -> Result<R>,
init: A,
f: impl Fn(A, R) -> Result<A>,
) -> Result<A> {
attrs
.iter()
.filter(|&a| filter(a))
.flat_map(|attribute| {
let Some(key) = attribute.path().get_ident().cloned() else {
return vec![Err(compile_error_at("expected an ident", attribute.path().span()))];
};
let args = match attribute.parse_args() {
Ok(v) => Group::new(Delimiter::Parenthesis, v).to_token_stream(),
Err(_) => {
let value = match attribute.meta.require_name_value() {
Ok(v) => v.value.to_token_stream(),
Err(err) => return vec![Err(err.into())],
};
quote::quote! { = #value }
}
};
match (|input: ParseStream<'_>| Attrs::parse_with_key(input, key)).parse2(args) {
Ok(ok) => ok.0.into_iter().map(&parse).collect(),
Err(err) => vec![Err(err.into())],
}
})
.try_fold(init, |acc, r| f(acc, r?))
}
#[derive(Default, Debug)]
struct Attrs(Vec<Attr>);
#[derive(Debug)]
pub(crate) struct Attr {
pub key: Vec<Ident>,
pub value: AttrValue,
}
#[derive(Debug)]
pub(crate) enum AttrValue {
Path(Path),
Lit(Lit),
Array(Vec<AttrValue>, Span),
None(Span),
}
impl Parse for Attrs {
fn parse(input: ParseStream) -> syn::Result<Attrs> {
let key = input.parse::<Ident>()?;
Attrs::parse_with_key(input, key)
}
}
impl Attrs {
fn parse_with_key(input: ParseStream, key: Ident) -> syn::Result<Attrs> {
let attrs = input.step(|cursor| {
if let Some((group, _sp, next_cursor)) = cursor.group(Delimiter::Parenthesis) {
if !next_cursor.eof() {
return Err(syn::Error::new(next_cursor.span(), "unexpected tokens"));
}
let mut attrs =
(|input: ParseStream<'_>| input.parse_terminated(Attrs::parse, Token![,]))
.parse(group.token_stream().into())?
.into_iter()
.reduce(|mut l, r| {
l.0.extend(r.0);
l
})
.unwrap_or_default();
attrs.0.iter_mut().for_each(|attr| attr.key.push(key.clone()));
Ok((Some(attrs), next_cursor))
} else {
Ok((None, *cursor))
}
})?;
if let Some(attrs) = attrs {
return Ok(attrs);
}
let value = match input.peek(Token![=]) {
true => {
input.parse::<Token![=]>()?;
input.parse::<AttrValue>()?
}
false => AttrValue::None(input.span()),
};
Ok(Attrs(vec![Attr { key: vec![key], value }]))
}
}
impl Attr {
pub(crate) fn span(&self) -> Span {
self.key().span().join(self.value.span()).unwrap_or_else(|| self.key().span())
}
fn key(&self) -> &Ident {
self.key.first().unwrap()
}
}
impl AttrValue {
pub fn expect_string(self) -> Result<String> {
self.expect("a string", |this| match this {
AttrValue::Lit(Lit::Str(s)) => Ok(s.value()),
_ => Err(this),
})
}
pub fn expect_none(self, option_name: &str) -> Result<()> {
match self {
AttrValue::None(_) => Ok(()),
_ => Err(compile_error_at(
&format!("The {option_name} option should not have a value, remove it"),
self.span(),
)),
}
}
pub fn expect_array(self) -> Result<Vec<Self>> {
self.expect("an array", |this| match this {
AttrValue::Array(a, _) => Ok(a),
_ => Err(this),
})
}
pub fn expect<T>(self, expected: &str, f: impl FnOnce(Self) -> Result<T, Self>) -> Result<T> {
f(self).map_err(|this| {
compile_error_at(&format!("expected {expected}, found {}", this.descr()), this.span())
})
}
fn descr(&self) -> &'static str {
use Lit::*;
match self {
Self::None(_) => "nothing",
Self::Lit(l) => match l {
Str(_) | ByteStr(_) | CStr(_) => "a string",
Char(_) => "a character",
Byte(_) | Int(_) => "an integer",
Float(_) => "a floating point integer",
Bool(_) => "a boolean",
Verbatim(_) => ":shrug:",
_ => ":mag:",
},
Self::Array(_, _) => "an array",
Self::Path(_) => "a path",
}
}
pub fn span(&self) -> Span {
match self {
Self::Path(p) => p.span(),
Self::Lit(l) => l.span(),
Self::None(sp) => *sp,
Self::Array(_, sp) => *sp,
}
}
}
impl Parse for AttrValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Lit) {
input.parse::<Lit>().map(AttrValue::Lit)
} else if input.peek(syn::token::Bracket) {
let content;
let array_span = syn::bracketed!(content in input).span;
let array = content.parse_terminated(AttrValue::parse, Token![,])?;
Ok(AttrValue::Array(array.into_iter().collect(), array_span.span()))
} else {
Ok(AttrValue::Path(
input
.parse::<Path>()
.map_err(|_| syn::Error::new(input.span(), "Unexpected token"))?,
))
}
}
}