use std::fmt::Debug;
use std::hash::Hash;
use indexmap::{map::Entry, IndexMap};
use proc_macro2::{Ident, Span};
use syn::parse::{Parse, ParseStream};
use syn::{braced, ext::IdentExt, punctuated::Punctuated, Token};
use crate::MacroArg;
pub trait ParsedArgValue<Id: KeywordArgId>: Sized {
fn id(&self) -> Id;
fn parse_with_id(id: Id, id_span: Span, stream: ParseStream) -> syn::Result<Self>;
}
pub trait KeywordArgId: Copy + Eq + Hash + Debug {
fn as_str(&self) -> &'_ str;
fn from_name(name: &str) -> Option<Self>;
}
pub struct KeywordArg<K: MacroKeywordArgs> {
pub name: Ident,
pub id: K::ArgId,
pub value: K::ParsedArg,
}
impl<K: MacroKeywordArgs> Parse for KeywordArg<K> {
fn parse(stream: ParseStream) -> syn::Result<Self> {
let name = stream.call(Ident::parse_any)?;
let name_text = name.to_string();
let id = K::ArgId::from_name(&name_text).ok_or_else(|| {
syn::Error::new(
name.span(),
format!("Unknown argument name: {}", &name_text),
)
})?;
stream.parse::<Token![=>]>()?;
let value = K::ParsedArg::parse_with_id(id, name.span(), stream)?;
Ok(KeywordArg { id, name, value })
}
}
pub struct ParsedKeywordArguments<K: MacroKeywordArgs> {
by_name: IndexMap<K::ArgId, KeywordArg<K>>,
}
impl<K: MacroKeywordArgs> ParsedKeywordArguments<K> {
pub fn get(&self, id: K::ArgId) -> Option<&'_ KeywordArg<K>> {
self.by_name.get(&id)
}
pub fn take(&mut self, id: K::ArgId) -> Option<KeywordArg<K>> {
self.by_name.swap_remove(&id)
}
pub fn require(&mut self, id: K::ArgId) -> syn::Result<KeywordArg<K>> {
self.take(id).ok_or_else(|| {
syn::Error::new(
Span::call_site(),
format!("Missing required argument `{}`", id.as_str()),
)
})
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &'_ KeywordArg<K>> + '_ {
self.by_name.values()
}
}
impl<K: MacroKeywordArgs> Parse for ParsedKeywordArguments<K> {
fn parse(stream: ParseStream) -> syn::Result<Self> {
let punct: Punctuated<KeywordArg<K>, Token![,]> =
stream.call(Punctuated::parse_terminated)?;
let mut by_name = IndexMap::with_capacity(punct.len());
let mut errors = Vec::new();
for arg in punct.into_iter() {
match by_name.entry(arg.id) {
Entry::Occupied(_entry) => {
errors.push(syn::Error::new(
arg.name.span(),
format!("Duplicate values for argument: {}", arg.name),
));
}
Entry::Vacant(entry) => {
entry.insert(arg);
}
}
}
if !errors.is_empty() {
return Err(crate::combine_errors(errors));
}
Ok(ParsedKeywordArguments { by_name })
}
}
impl<K: MacroKeywordArgs> MacroArg for ParsedKeywordArguments<K> {
fn parse_macro_arg(stream: ParseStream) -> syn::Result<Self> {
let content;
braced!(content in stream);
content.parse::<Self>()
}
}
pub trait MacroKeywordArgs: MacroArg + Parse {
type ArgId: KeywordArgId;
type ParsedArg: ParsedArgValue<Self::ArgId>;
fn from_keyword_args(kwargs: ParsedKeywordArguments<Self>) -> Result<Self, syn::Error>;
}