Skip to main content

pochoir_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::{Data, Fields, Ident, Index};
5
6fn impl_from_value(ast: &syn::DeriveInput) -> TokenStream {
7    let name = &ast.ident;
8
9    if let Data::Struct(data) = &ast.data {
10        let returned = match &data.fields {
11            Fields::Named(fields) => {
12                let fields = fields
13                .named
14                .iter()
15                .map(|f| {
16                    let name = f.ident.as_ref().expect("named structure field must have an identifier");
17                    let ty = &f.ty;
18
19                    quote! { #name: <#ty>::from_value(val.remove(stringify!(#name)).ok_or(pochoir::lang::Error::MissingField(stringify!(#name).to_string()))?)? }
20                }).collect::<Vec<_>>();
21
22                quote! { {#(#fields),*} }
23            }
24            Fields::Unnamed(fields) => {
25                let fields = fields
26                    .unnamed
27                    .iter()
28                    .enumerate()
29                    .map(|(i, f)| {
30                        let name = format!("__field{i}");
31                        let ty = &f.ty;
32
33                        quote! { <#ty>::from_value(val.remove(#name).ok_or(pochoir::lang::Error::MissingField(#name.to_string()))?)? }
34                    }).collect::<Vec<_>>();
35
36                quote! { (#(#fields),*) }
37            }
38            Fields::Unit => {
39                quote! {}
40            }
41        };
42
43        quote! {
44            impl pochoir::lang::FromValue for #name {
45                fn from_value(val: Value) -> ::std::result::Result<Self, Box<dyn ::std::error::Error>> {
46                    if let Value::Object(mut val) = val {
47                        Ok(Self #returned)
48                    } else {
49                        Err(Box::new(pochoir::lang::Error::MismatchedTypes {
50                            expected: "Object".to_string(),
51                            found: val.type_name().to_string(),
52                        }))
53                    }
54                }
55            }
56        }
57        .into()
58    } else if let Data::Enum(data) = &ast.data {
59        let fields = data.variants.iter().map(|v| {
60            let variant_name = &v.ident;
61
62            match &v.fields {
63                Fields::Named(fields) => {
64                    let fields = fields
65                    .named
66                    .iter()
67                    .map(|f| {
68                        let name = f.ident.as_ref().expect("named structure field must have an identifier");
69                        let ty = &f.ty;
70
71                        quote! { #name: <#ty>::from_value(val.remove(stringify!(#name)).ok_or(pochoir::lang::Error::MissingField(stringify!(#name).to_string()))?)? }
72                    }).collect::<Vec<_>>();
73
74                    quote! { stringify!(#variant_name) => Self::#variant_name { #(#fields),* } }
75                }
76                Fields::Unnamed(fields) => {
77                    let fields = fields
78                        .unnamed
79                        .iter()
80                        .enumerate()
81                        .map(|(i, f)| {
82                            let name = format!("__field{i}");
83                            let ty = &f.ty;
84
85                            quote! { <#ty>::from_value(val.remove(#name).ok_or(pochoir::lang::Error::MissingField(#name.to_string()))?)? }
86                        }).collect::<Vec<_>>();
87
88                    quote! { stringify!(#variant_name) => Self::#variant_name ( #(#fields),* ) }
89                }
90                Fields::Unit => {
91                    quote! { stringify!(#variant_name) => Self::#variant_name }
92                }
93            }
94        }).collect::<Vec<_>>();
95
96        quote! {
97            impl pochoir::lang::FromValue for #name {
98                fn from_value(val: Value) -> ::std::result::Result<Self, Box<dyn ::std::error::Error>> {
99                    if let Value::Object(mut val) = val {
100                        Ok(match val.get("__enum_variant")
101                           .ok_or(pochoir::lang::Error::MissingField("__enum_variant".to_string()))?
102                           .as_string().expect("__enum_variant should be a string")
103                        {
104                            #(#fields,)*
105                            v => return Err(Box::new(pochoir::lang::Error::UnknownEnumVariant(v.to_string()))),
106                        })
107                    } else {
108                        Err(Box::new(pochoir::lang::Error::MismatchedTypes {
109                            expected: "Object".to_string(),
110                            found: val.type_name().to_string(),
111                        }))
112                    }
113                }
114            }
115        }
116        .into()
117    } else {
118        panic!("FromValue works only on structures and enumerations")
119    }
120}
121
122fn impl_into_value(ast: &syn::DeriveInput) -> TokenStream {
123    let name = &ast.ident;
124
125    if let Data::Struct(data) = &ast.data {
126        let fields = match &data.fields {
127            Fields::Named(fields) => fields
128                .named
129                .iter()
130                .map(|f| {
131                    let name = f
132                        .ident
133                        .as_ref()
134                        .expect("named structure field must have an identifier");
135                    quote! { stringify!(#name) => self.#name.into_value() }
136                })
137                .collect::<Vec<_>>(),
138            Fields::Unnamed(fields) => (0..fields.unnamed.len())
139                .map(|i| {
140                    let name = Ident::new(&format!("__field{i}"), Span::call_site());
141                    let i = Index::from(i);
142                    quote! { stringify!(#name) => self.#i.into_value() }
143                })
144                .collect::<Vec<_>>(),
145            Fields::Unit => {
146                vec![]
147            }
148        };
149
150        quote! {
151            impl pochoir::lang::IntoValue for #name {
152                fn into_value(self) -> pochoir::lang::Value {
153                    pochoir::lang::object! {
154                        #(#fields),*
155                    }.into_value()
156                }
157            }
158        }
159        .into()
160    } else if let Data::Enum(data) = &ast.data {
161        let fields = data
162            .variants
163            .iter()
164            .map(|v| {
165                let variant_name = &v.ident;
166
167                match &v.fields {
168                    Fields::Named(fields) => {
169                        let fields = fields.named.iter().map(|f| f.ident.as_ref().expect("named structure field must have an identifier")).collect::<Vec<_>>();
170
171                        quote! { Self::#variant_name { #(#fields),* } => pochoir::lang::object! {"__enum_variant" => stringify!(#variant_name), #(stringify!(#fields) => #fields.into_value()),* }.into_value() }
172                    },
173                    Fields::Unnamed(fields) => {
174                        let fields = (0..fields.unnamed.len()).map(|i| Ident::new(&format!("__field{i}"), Span::call_site())).collect::<Vec<_>>();
175
176                        quote! { Self::#variant_name(#(#fields),*) => pochoir::lang::object! {"__enum_variant" => stringify!(#variant_name), #(stringify!(#fields) => #fields.into_value()),* }.into_value() }
177                    },
178                    Fields::Unit => {
179                        quote! { Self::#variant_name => pochoir::lang::object! { "__enum_variant" => stringify!(#variant_name) }.into_value() }
180                    }
181                }
182            });
183
184        quote! {
185            impl pochoir::lang::IntoValue for #name {
186                fn into_value(self) -> pochoir::lang::Value {
187                    match self {
188                        #(#fields),*
189                    }
190                }
191            }
192        }
193        .into()
194    } else {
195        panic!("IntoValue works only on structures and enumerations")
196    }
197}
198
199/// Implements `pochoir_lang::FromValue` for structures and enumerations.
200///
201/// If **one field** uses an `Option`, it can automatically be converted from `Value::Null`
202/// but if **all fields** of the structure are `Option`s, the structure **itself** can be
203/// converted from a `Value::Null` (all the fields will be `None` in this case).
204#[proc_macro_derive(FromValue)]
205pub fn from_value_derive(input: TokenStream) -> TokenStream {
206    let ast = syn::parse(input).unwrap();
207    impl_from_value(&ast)
208}
209
210/// Implements `pochoir_lang::IntoValue` for structures and enumerations.
211#[proc_macro_derive(IntoValue)]
212pub fn into_value_derive(input: TokenStream) -> TokenStream {
213    let ast = syn::parse(input).unwrap();
214    impl_into_value(&ast)
215}