use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{Data, Fields, Ident, Index};
fn impl_from_value(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
if let Data::Struct(data) = &ast.data {
let returned = match &data.fields {
Fields::Named(fields) => {
let fields = fields
.named
.iter()
.map(|f| {
let name = f.ident.as_ref().expect("named structure field must have an identifier");
let ty = &f.ty;
quote! { #name: <#ty>::from_value(val.remove(stringify!(#name)).ok_or(pochoir::lang::Error::MissingField(stringify!(#name).to_string()))?)? }
}).collect::<Vec<_>>();
quote! { {#(#fields),*} }
}
Fields::Unnamed(fields) => {
let fields = fields
.unnamed
.iter()
.enumerate()
.map(|(i, f)| {
let name = format!("__field{i}");
let ty = &f.ty;
quote! { <#ty>::from_value(val.remove(#name).ok_or(pochoir::lang::Error::MissingField(#name.to_string()))?)? }
}).collect::<Vec<_>>();
quote! { (#(#fields),*) }
}
Fields::Unit => {
quote! {}
}
};
quote! {
impl pochoir::lang::FromValue for #name {
fn from_value(val: Value) -> ::std::result::Result<Self, Box<dyn ::std::error::Error>> {
if let Value::Object(mut val) = val {
Ok(Self #returned)
} else {
Err(Box::new(pochoir::lang::Error::MismatchedTypes {
expected: "Object".to_string(),
found: val.type_name().to_string(),
}))
}
}
}
}
.into()
} else if let Data::Enum(data) = &ast.data {
let fields = data.variants.iter().map(|v| {
let variant_name = &v.ident;
match &v.fields {
Fields::Named(fields) => {
let fields = fields
.named
.iter()
.map(|f| {
let name = f.ident.as_ref().expect("named structure field must have an identifier");
let ty = &f.ty;
quote! { #name: <#ty>::from_value(val.remove(stringify!(#name)).ok_or(pochoir::lang::Error::MissingField(stringify!(#name).to_string()))?)? }
}).collect::<Vec<_>>();
quote! { stringify!(#variant_name) => Self::#variant_name { #(#fields),* } }
}
Fields::Unnamed(fields) => {
let fields = fields
.unnamed
.iter()
.enumerate()
.map(|(i, f)| {
let name = format!("__field{i}");
let ty = &f.ty;
quote! { <#ty>::from_value(val.remove(#name).ok_or(pochoir::lang::Error::MissingField(#name.to_string()))?)? }
}).collect::<Vec<_>>();
quote! { stringify!(#variant_name) => Self::#variant_name ( #(#fields),* ) }
}
Fields::Unit => {
quote! { stringify!(#variant_name) => Self::#variant_name }
}
}
}).collect::<Vec<_>>();
quote! {
impl pochoir::lang::FromValue for #name {
fn from_value(val: Value) -> ::std::result::Result<Self, Box<dyn ::std::error::Error>> {
if let Value::Object(mut val) = val {
Ok(match val.get("__enum_variant")
.ok_or(pochoir::lang::Error::MissingField("__enum_variant".to_string()))?
.as_string().expect("__enum_variant should be a string")
{
#(#fields,)*
v => return Err(Box::new(pochoir::lang::Error::UnknownEnumVariant(v.to_string()))),
})
} else {
Err(Box::new(pochoir::lang::Error::MismatchedTypes {
expected: "Object".to_string(),
found: val.type_name().to_string(),
}))
}
}
}
}
.into()
} else {
panic!("FromValue works only on structures and enumerations")
}
}
fn impl_into_value(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
if let Data::Struct(data) = &ast.data {
let fields = match &data.fields {
Fields::Named(fields) => fields
.named
.iter()
.map(|f| {
let name = f
.ident
.as_ref()
.expect("named structure field must have an identifier");
quote! { stringify!(#name) => self.#name.into_value() }
})
.collect::<Vec<_>>(),
Fields::Unnamed(fields) => (0..fields.unnamed.len())
.map(|i| {
let name = Ident::new(&format!("__field{i}"), Span::call_site());
let i = Index::from(i);
quote! { stringify!(#name) => self.#i.into_value() }
})
.collect::<Vec<_>>(),
Fields::Unit => {
vec![]
}
};
quote! {
impl pochoir::lang::IntoValue for #name {
fn into_value(self) -> pochoir::lang::Value {
pochoir::lang::object! {
#(#fields),*
}.into_value()
}
}
}
.into()
} else if let Data::Enum(data) = &ast.data {
let fields = data
.variants
.iter()
.map(|v| {
let variant_name = &v.ident;
match &v.fields {
Fields::Named(fields) => {
let fields = fields.named.iter().map(|f| f.ident.as_ref().expect("named structure field must have an identifier")).collect::<Vec<_>>();
quote! { Self::#variant_name { #(#fields),* } => pochoir::lang::object! {"__enum_variant" => stringify!(#variant_name), #(stringify!(#fields) => #fields.into_value()),* }.into_value() }
},
Fields::Unnamed(fields) => {
let fields = (0..fields.unnamed.len()).map(|i| Ident::new(&format!("__field{i}"), Span::call_site())).collect::<Vec<_>>();
quote! { Self::#variant_name(#(#fields),*) => pochoir::lang::object! {"__enum_variant" => stringify!(#variant_name), #(stringify!(#fields) => #fields.into_value()),* }.into_value() }
},
Fields::Unit => {
quote! { Self::#variant_name => pochoir::lang::object! { "__enum_variant" => stringify!(#variant_name) }.into_value() }
}
}
});
quote! {
impl pochoir::lang::IntoValue for #name {
fn into_value(self) -> pochoir::lang::Value {
match self {
#(#fields),*
}
}
}
}
.into()
} else {
panic!("IntoValue works only on structures and enumerations")
}
}
#[proc_macro_derive(FromValue)]
pub fn from_value_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_from_value(&ast)
}
#[proc_macro_derive(IntoValue)]
pub fn into_value_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_into_value(&ast)
}