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 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 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 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 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#[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}