com_shim_macro/
lib.rs

1#![deny(unsafe_code)]
2#![warn(clippy::pedantic)]
3#![warn(missing_docs)]
4#![doc = include_str!("../README.md")]
5
6use heck::ToSnakeCase;
7use proc_macro::TokenStream;
8use quote::{ToTokens, TokenStreamExt, quote};
9use syn::{
10    braced, ext::IdentExt, parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, Attribute, Ident, Token
11};
12
13struct Class {
14    attributes: Vec<Attribute>,
15    ident: Ident,
16    inherited: Vec<Ident>,
17    functions_and_variables: Punctuated<FunctionOrVariable, Token![,]>,
18}
19
20impl Parse for Class {
21    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
22        let attributes = Attribute::parse_outer(input)?;
23        let _: Token![struct] = input.parse()?;
24        let ident: Ident = input.parse()?;
25        let mut inherited: Vec<Ident> = vec![];
26        if input.peek(Token![:]) {
27            // Parse inheritance
28            let _: Token![:] = input.parse()?;
29            loop {
30                inherited.push(input.parse()?);
31                if input.peek(Token![+]) {
32                    let _: Token![+] = input.parse()?;
33                } else {
34                    break;
35                }
36            }
37        }
38        let content;
39        braced!(content in input);
40        let functions_and_variables =
41            content.parse_terminated(FunctionOrVariable::parse, Token![,])?;
42
43        Ok(Self {
44            attributes,
45            ident,
46            inherited,
47            functions_and_variables,
48        })
49    }
50}
51
52enum FunctionOrVariable {
53    Function(Function),
54    Variable(Variable),
55}
56
57impl ToTokens for FunctionOrVariable {
58    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
59        match self {
60            Self::Function(f) => f.to_tokens(tokens),
61            Self::Variable(v) => v.to_tokens(tokens),
62        }
63    }
64}
65
66impl Parse for FunctionOrVariable {
67    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
68        let attributes = Attribute::parse_outer(input)?;
69        if input.peek(Token![fn]) {
70            // Parse function next
71            let _: Token![fn] = input.parse()?;
72            let ident: Ident = input.parse()?;
73            let parameters_raw;
74            parenthesized!(parameters_raw in input);
75            let parameters = parameters_raw.parse_terminated(Ident::parse, Token![,])?;
76            let returns = if input.peek(Token![->]) {
77                let _: Token![->] = input.parse()?;
78                Some(input.parse::<Ident>()?)
79            } else {
80                None
81            };
82            Ok(FunctionOrVariable::Function(Function {
83                attributes,
84                ident,
85                parameters,
86                returns,
87            }))
88        } else if input.peek(Token![mut]) {
89            // Parse read/write variable
90            let _: Token![mut] = input.parse()?;
91            let ident: Ident = input.parse()?;
92            let _: Token![:] = input.parse()?;
93            let type_: Ident = input.parse()?;
94            Ok(FunctionOrVariable::Variable(Variable {
95                attributes,
96                mutable: true,
97                ident,
98                type_,
99            }))
100        } else {
101            // Parse read-only variable
102            let ident: Ident = input.parse()?;
103            let _: Token![:] = input.parse()?;
104            let type_: Ident = input.parse()?;
105            Ok(FunctionOrVariable::Variable(Variable {
106                attributes,
107                mutable: false,
108                ident,
109                type_,
110            }))
111        }
112    }
113}
114
115struct Variable {
116    attributes: Vec<Attribute>,
117    mutable: bool,
118    ident: Ident,
119    type_: Ident,
120}
121
122impl ToTokens for Variable {
123    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
124        let Variable {
125            attributes,
126            mutable,
127            ident,
128            type_,
129        } = self;
130        let ident_str = ident.to_string();
131        let ident_unraw_str = ident.unraw().to_string();
132
133        let read_ident = Ident::new(&ident_str.to_snake_case(), ident.span());
134        tokens.append_all(quote! {
135            #(#attributes)*
136            fn #read_ident(&self) -> ::com_shim::Result<#type_> {
137                use ::com_shim::{IDispatchExt, VariantTypeExt};
138                ::std::result::Result::Ok(self.get_idispatch().get(#ident_unraw_str)?.variant_into()?)
139            }
140        });
141
142        if *mutable {
143            let write_ident =
144                Ident::new(&format!("set_{}", ident_str.to_snake_case()), ident.span());
145            tokens.append_all(quote! {
146                #(#attributes)*
147                fn #write_ident(&self, value: #type_) -> ::com_shim::Result<()> {
148                    use ::com_shim::{IDispatchExt, VariantTypeExt};
149                    let _ = self.get_idispatch().set(#ident_unraw_str, ::com_shim::VARIANT::variant_from(value))?;
150                    ::std::result::Result::Ok(())
151                }
152            });
153        }
154    }
155}
156
157struct Function {
158    attributes: Vec<Attribute>,
159    ident: Ident,
160    parameters: Punctuated<Ident, Token![,]>,
161    returns: Option<Ident>,
162}
163
164impl ToTokens for Function {
165    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
166        let Function {
167            attributes,
168            ident,
169            parameters,
170            returns,
171        } = self;
172        let ident_str = ident.to_string();
173        let ident_unraw_str = ident.unraw().to_string();
174        let fn_ident = Ident::new(&ident_str.to_snake_case(), ident.span());
175        let fn_parameters = parameters.iter().enumerate().map(|(idx, p)| {
176            let ident = Ident::new(&format!("p{idx}"), p.span());
177            quote!(#ident: #p)
178        });
179        let parameters = parameters.iter().enumerate().map(|(idx, p)| {
180            let ident = Ident::new(&format!("p{idx}"), p.span());
181            quote!(::com_shim::VARIANT::variant_from(#ident))
182        });
183        let (returns_type, return_statement) = if let Some(returns) = returns {
184            (quote!(#returns), quote!(r.variant_into()?))
185        } else {
186            (quote!(()), quote!(()))
187        };
188        tokens.append_all(quote! {
189            #(#attributes)*
190            fn #fn_ident(&self, #(#fn_parameters),*) -> ::com_shim::Result<#returns_type> {
191                use ::com_shim::{IDispatchExt, VariantTypeExt};
192                let r = self.get_idispatch().call(#ident_unraw_str, vec![
193                    #(#parameters),*
194                ])?;
195                ::std::result::Result::Ok(#return_statement)
196            }
197        });
198    }
199}
200
201/// Generate a COM-compatible class structure.
202#[proc_macro]
203pub fn com_shim(stream: TokenStream) -> TokenStream {
204    let Class {
205        attributes,
206        ident,
207        inherited,
208        functions_and_variables,
209    } = parse_macro_input!(stream as Class);
210
211    let functions_and_variables = functions_and_variables.into_iter();
212    let self_impl = Ident::new(&format!("{ident}Ext"), ident.span());
213    let inherited_casts = inherited.iter().map(|i| quote! {
214        impl ::com_shim::IsA<#i> for #ident {
215            fn upcast(&self) -> #i {
216                #i::from(self.inner.clone())
217            }
218        }
219    });
220    let inherited_impls = inherited
221        .iter()
222        .map(|i| Ident::new(&format!("{i}Ext"), i.span()));
223    quote! {
224        #(#attributes)*
225        pub struct #ident {
226            inner: ::com_shim::IDispatch,
227        }
228
229        impl ::com_shim::HasIDispatch for #ident {
230            fn get_idispatch(&self) -> &::com_shim::IDispatch {
231                &self.inner
232            }
233        }
234
235        pub trait #self_impl<T: ::com_shim::HasIDispatch = Self>: ::com_shim::HasIDispatch<T> {
236            #(#functions_and_variables)*
237        }
238
239        impl #self_impl for #ident {}
240
241        #(impl #inherited_impls for #ident {})*
242
243        #(#inherited_casts)*
244
245        impl ::std::convert::From<::com_shim::IDispatch> for #ident {
246            fn from(value: ::com_shim::IDispatch) -> Self {
247                Self { inner: value }
248            }
249        }
250
251        impl ::com_shim::VariantTypeExt<'_, #ident> for ::com_shim::VARIANT {
252            fn variant_from(value: #ident) -> ::com_shim::VARIANT {
253                let idisp = &value.inner;
254                ::com_shim::VARIANT::variant_from(idisp)
255            }
256
257            fn variant_into(&'_ self) -> ::com_shim::Result<#ident> {
258                let idisp = <::com_shim::VARIANT as ::com_shim::VariantTypeExt<'_, &::com_shim::IDispatch>>
259                    ::variant_into(self)?.clone();
260                ::std::result::Result::Ok(#ident::from(idisp))
261            }
262        }
263    }.into()
264}