use proc_macro2::{Ident, Span, TokenStream};
use syn::{Expr, ExprLit, ExprPath, FieldValue, Lit, spanned::Spanned};
use crate::util::{FieldValueKey, print_list};
pub struct Arg<T> {
key: &'static str,
set: Box<dyn FnMut(&FieldValue) -> Result<T, syn::Error>>,
span: Option<Span>,
value: Option<T>,
}
impl Arg<bool> {
pub fn bool(key: &'static str) -> Self {
Arg::new(key, move |fv| match fv.expr {
Expr::Lit(ExprLit {
lit: Lit::Bool(ref l),
..
}) => Ok(l.value),
_ => Err(syn::Error::new(
fv.expr.span(),
format_args!("`{}` requires a boolean value", key),
)),
})
}
}
impl Arg<String> {
pub fn str(key: &'static str) -> Self {
Arg::new(key, move |fv| match fv.expr {
Expr::Lit(ExprLit {
lit: Lit::Str(ref l),
..
}) => Ok(l.value()),
_ => Err(syn::Error::new(
fv.expr.span(),
format_args!("`{}` requires a string value", key),
)),
})
}
}
impl Arg<Ident> {
pub fn ident(key: &'static str) -> Self {
Arg::new(key, move |fv| match fv.expr {
Expr::Path(ExprPath { ref path, .. }) => path.get_ident().cloned().ok_or_else(|| {
syn::Error::new(
fv.expr.span(),
format_args!("`{}` requires an identifier value", key),
)
}),
_ => Err(syn::Error::new(
fv.expr.span(),
format_args!("`{}` requires a string value", key),
)),
})
}
}
impl Arg<TokenStream> {
pub fn token_stream(
key: &'static str,
to_tokens: impl FnMut(&FieldValue) -> Result<TokenStream, syn::Error> + 'static,
) -> Self {
Arg::new(key, to_tokens)
}
}
impl<T> Arg<T> {
pub fn new(
key: &'static str,
mut to_custom: impl FnMut(&FieldValue) -> Result<T, syn::Error> + 'static,
) -> Self {
Arg::value(key, move |fv| {
if fv.attrs.len() > 0 {
return Err(syn::Error::new(
fv.span(),
format!("attributes on the {key} parameter are not supported"),
));
}
to_custom(fv)
})
}
pub fn value(
key: &'static str,
to_custom: impl FnMut(&FieldValue) -> Result<T, syn::Error> + 'static,
) -> Self {
Arg {
key,
set: Box::new(to_custom),
span: None,
value: None,
}
}
pub fn peek(&self) -> Option<&T> {
self.value.as_ref()
}
pub fn take(self) -> Option<T> {
self.value
}
pub fn take_if_std(self) -> Result<Option<T>, syn::Error> {
#[cfg(feature = "std")]
{
Ok(self.take())
}
#[cfg(not(feature = "std"))]
{
if self.value.is_some() {
Err(syn::Error::new(
self.span.unwrap_or_else(Span::call_site),
format!(
"capturing `{}` is only possible when the `std` Cargo feature is enabled",
self.key
),
))
} else {
Ok(None)
}
}
}
}
impl<T: Default> Arg<T> {
pub fn take_or_default(self) -> T {
self.take().unwrap_or_default()
}
}
pub trait ArgDef {
fn key(&self) -> &str;
fn set(&mut self, fv: &FieldValue) -> Result<(), syn::Error>;
}
impl<T> ArgDef for Arg<T> {
fn key(&self) -> &str {
self.key
}
fn set(&mut self, fv: &FieldValue) -> Result<(), syn::Error> {
if self.value.is_some() {
return Err(syn::Error::new(
fv.span(),
format_args!("a value for `{}` has already been specified", self.key),
));
}
self.span = Some(fv.span());
self.value = Some((self.set)(fv)?);
Ok(())
}
}
pub fn set_from_field_values<'a, const N: usize>(
field_values: impl Iterator<Item = &'a FieldValue> + 'a,
mut args: [&mut dyn ArgDef; N],
) -> Result<(), syn::Error> {
'fields: for fv in field_values {
let key_name = fv.key_name()?;
for arg in &mut args {
if arg.key() == key_name {
arg.set(fv)?;
continue 'fields;
}
}
return Err(syn::Error::new(
fv.span(),
format_args!(
"unknown argument `{}`; available arguments are {}",
key_name,
print_list(|| args.iter().map(|arg| arg.key()))
),
));
}
Ok(())
}
#[derive(Default)]
pub struct ValueOrEmptyArg(Option<TokenStream>);
impl ValueOrEmptyArg {
pub fn new(value: TokenStream) -> Self {
ValueOrEmptyArg(Some(value))
}
pub fn to_tokens(&self) -> TokenStream {
self.0.clone().unwrap_or_else(|| quote!(emit::Empty))
}
}
pub type ExtentArg = ValueOrEmptyArg;
pub type PropsArg = ValueOrEmptyArg;
#[derive(Default)]
pub struct MdlArg(Option<TokenStream>);
impl MdlArg {
pub fn new(value: TokenStream) -> Self {
MdlArg(Some(value))
}
pub fn to_tokens(&self) -> TokenStream {
self.0.clone().unwrap_or_else(|| quote!(emit::mdl!()))
}
}
#[derive(Default)]
pub struct WhenArg(Option<TokenStream>);
impl WhenArg {
pub fn new(value: TokenStream) -> Self {
WhenArg(Some(value))
}
pub fn to_tokens(&self) -> Option<TokenStream> {
self.0.clone()
}
}
#[derive(Default)]
pub struct RtArg(Option<TokenStream>);
impl RtArg {
pub fn new(value: TokenStream) -> Self {
RtArg(Some(value))
}
pub fn take(self) -> Option<TokenStream> {
self.0
}
pub fn to_tokens(&self) -> Result<TokenStream, syn::Error> {
let provided = self.0.clone();
#[cfg(feature = "implicit_rt")]
{
Ok(provided.unwrap_or_else(|| quote!(emit::runtime::shared())))
}
#[cfg(not(feature = "implicit_rt"))]
{
use proc_macro2::Span;
provided.ok_or_else(|| syn::Error::new(Span::call_site(), "a runtime must be specified by the `rt` parameter unless the `implicit_rt` feature of `emit` is enabled"))
}
}
}
pub fn ensure_missing(name: &str, value: Option<Span>) -> Result<(), syn::Error> {
if let Some(value) = value {
return Err(syn::Error::new(
value,
format!("the `{name}` control parameter is not supported"),
));
}
Ok(())
}