use quote::quote;
use syn::punctuated::Punctuated;
use crate::{Error, core::Attr};
#[derive(Clone)]
pub enum Arg {
Flag(syn::Path),
Literal(syn::Path, syn::Lit),
Ident(syn::Path, syn::Ident),
Attr(Attr),
Expr(syn::Path, syn::Expr),
Signature(syn::Path, syn::Signature),
}
impl PartialEq for Arg {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Flag(a), Self::Flag(b)) => a == b,
(Self::Literal(pa, la), Self::Literal(pb, lb)) => pa == pb && la == lb,
(Self::Ident(pa, ia), Self::Ident(pb, ib)) => pa == pb && ia == ib,
(Self::Attr(a), Self::Attr(b)) => a == b,
(Self::Expr(pa, ea), Self::Expr(pb, eb)) => pa == pb && ea == eb,
(Self::Signature(pa, _), Self::Signature(pb, _)) => pa == pb,
_ => false,
}
}
}
impl Eq for Arg {}
impl Arg {
pub fn from_flag(path: syn::Path) -> Self {
Self::Flag(path)
}
pub fn from_lit(path: syn::Path, value: syn::Lit) -> Self {
Self::Literal(path, value)
}
pub fn from_ident(path: syn::Path, value: syn::Ident) -> Self {
Self::Ident(path, value)
}
pub fn from_attr(attr: Attr) -> Self {
Self::Attr(attr)
}
pub fn from_expr(path: syn::Path, expr: syn::Expr) -> Self {
Self::Expr(path, expr)
}
pub fn from_signature(path: syn::Path, sig: syn::Signature) -> Self {
Self::Signature(path, sig)
}
#[allow(unused)]
pub fn is_flag(&self) -> bool {
matches!(self, Self::Flag(_))
}
#[allow(unused)]
pub fn is_lit(&self) -> bool {
matches!(self, Self::Literal(_, _))
}
#[allow(unused)]
pub fn is_ident(&self) -> bool {
matches!(self, Self::Ident(_, _))
}
#[allow(unused)]
pub fn is_attr(&self) -> bool {
matches!(self, Self::Attr(_))
}
#[allow(unused)]
pub fn is_expr(&self) -> bool {
matches!(self, Self::Expr(_, _))
}
#[allow(unused)]
pub fn is_signature(&self) -> bool {
matches!(self, Self::Signature(_, _))
}
pub fn path(&self) -> &syn::Path {
match self {
Self::Flag(path) => path,
Self::Literal(path, _) => path,
Self::Ident(path, _) => path,
Self::Attr(attr) => attr.path(),
Self::Expr(path, _) => path,
Self::Signature(path, _) => path,
}
}
#[allow(unused)]
pub fn as_lit(&self) -> Option<&syn::Lit> {
match self {
Self::Literal(_, v) => Some(v),
_ => None,
}
}
#[allow(unused)]
pub fn as_ident(&self) -> Option<&syn::Ident> {
match self {
Self::Ident(_, v) => Some(v),
_ => None,
}
}
#[allow(unused)]
pub fn as_attr(&self) -> Option<&Attr> {
match self {
Self::Attr(attr) => Some(attr),
_ => None,
}
}
#[allow(unused)]
pub fn to_lit(&self) -> Option<syn::Lit> {
match self {
Self::Literal(_, v) => Some(v.clone()),
_ => None,
}
}
#[allow(unused)]
pub fn to_ident(&self) -> Option<syn::Ident> {
match self {
Self::Ident(_, v) => Some(v.clone()),
_ => None,
}
}
#[allow(unused)]
pub fn to_attr(&self) -> Option<Attr> {
match self {
Self::Attr(attr) => Some(attr.clone()),
_ => None,
}
}
#[allow(unused)]
pub fn as_expr(&self) -> Option<&syn::Expr> {
match self {
Self::Expr(_, expr) => Some(expr),
_ => None,
}
}
#[allow(unused)]
pub fn as_signature(&self) -> Option<&syn::Signature> {
match self {
Self::Signature(_, sig) => Some(sig),
_ => None,
}
}
#[allow(unused)]
pub fn as_value_tokens(&self) -> Option<proc_macro2::TokenStream> {
match self {
Self::Literal(_, lit) => Some(quote::quote!(#lit)),
Self::Ident(_, ident) => Some(quote::quote!(#ident)),
Self::Expr(_, expr) => Some(quote::quote!(#expr)),
_ => None,
}
}
#[allow(unused)]
pub fn error(&self, message: &str) -> proc_macro2::TokenStream {
self.path().error(message).to_compile_error()
}
}
impl quote::ToTokens for Arg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
tokens.extend(match self {
Self::Flag(path) => quote!(#path),
Self::Literal(path, value) => quote!(#path = #value),
Self::Ident(path, ident) => quote!(#path = #ident),
Self::Attr(attr) => quote!(#attr),
Self::Expr(_, expr) => quote!(#expr),
Self::Signature(_, sig) => quote!(#sig),
});
}
}
impl syn::parse::Parse for Arg {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(syn::Token![fn]) {
let sig: syn::Signature = input.parse()?;
let path: syn::Path = syn::parse_quote!(__signature);
return Ok(Arg::from_signature(path, sig));
}
if input.peek(syn::LitStr) {
let lit: syn::Lit = input.parse()?;
let path: syn::Path = syn::parse_quote!(__value);
return Ok(Arg::from_lit(path, lit));
}
let fork = input.fork();
if fork.parse::<syn::Path>().is_ok() {
if fork.peek(syn::Token![=]) {
let path: syn::Path = input.parse()?;
input.parse::<syn::Token![=]>()?;
let expr: syn::Expr = input.parse()?;
return Ok(match expr {
syn::Expr::Lit(expr_lit) => Arg::from_lit(path, expr_lit.lit),
syn::Expr::Path(expr_path) if expr_path.path.get_ident().is_some() => {
Arg::from_ident(path, expr_path.path.get_ident().unwrap().clone())
}
other => Arg::from_expr(path, other),
});
}
if fork.peek(syn::token::Paren) {
let path: syn::Path = input.parse()?;
let list;
let _ = syn::parenthesized!(list in input);
let items = Punctuated::<Arg, syn::Token![,]>::parse_terminated(&list)?;
return Ok(Arg::from_attr(Attr::new(path, items)));
}
if fork.is_empty() || fork.peek(syn::Token![,]) {
let path: syn::Path = input.parse()?;
return Ok(Arg::from_flag(path));
}
}
let expr: syn::Expr = input.parse()?;
let path: syn::Path = syn::parse_quote!(__expr);
Ok(Arg::from_expr(path, expr))
}
}