1extern crate proc_macro;
3
4use std::collections::HashMap;
5
6use proc_macro::TokenStream;
7use proc_macro2::{Ident, Literal};
8use quote::{quote, ToTokens};
9use syn::{parenthesized, parse_macro_input, Data, DeriveInput, Fields, LitStr};
10
11fn get_allowed_types() -> HashMap<&'static str, usize> {
12 let mut allowed_datatypes = HashMap::<&'static str, usize>::new();
13 allowed_datatypes.insert("f32", std::mem::size_of::<f32>());
14 allowed_datatypes.insert("f64", std::mem::size_of::<f64>());
15 allowed_datatypes.insert("i32", std::mem::size_of::<i32>());
16 allowed_datatypes.insert("u8", std::mem::size_of::<u8>());
17 allowed_datatypes.insert("u16", std::mem::size_of::<u16>());
18 allowed_datatypes.insert("u32", std::mem::size_of::<u32>());
19 allowed_datatypes.insert("i8", std::mem::size_of::<i8>());
20 allowed_datatypes.insert("i16", std::mem::size_of::<i16>());
21 allowed_datatypes
22}
23
24fn struct_field_rename_array(input: &DeriveInput) -> Vec<String> {
25 let fields = match input.data {
26 syn::Data::Struct(ref data) => match data.fields {
27 syn::Fields::Named(ref fields) => &fields.named,
28 _ => panic!("StructNames can only be derived for structs with named fields"),
29 },
30 _ => panic!("StructNames can only be derived for structs"),
31 };
32
33 let mut field_names = Vec::with_capacity(fields.len());
34 for f in fields.iter() {
35 if f.attrs.is_empty() {
36 field_names.push(f.ident.as_ref().unwrap().to_token_stream().to_string());
37 } else {
38 f.attrs.iter().for_each(|attr| {
39 if attr.path().is_ident("ros") {
40 let res = attr.parse_nested_meta(|meta| {
41 if meta.path.is_ident("rename") {
42 let new_name;
43 parenthesized!(new_name in meta.input);
44 let lit: LitStr = new_name.parse()?;
45 field_names.push(lit.value());
46 Ok(())
47 } else {
48 panic!("expected `name` attribute");
49 }
50 });
51 if let Err(e) = res {
52 panic!("Error parsing attribute: {e}");
53 }
54 }
55 });
56 }
57 }
58
59 field_names
60}
61
62#[proc_macro_derive(PointConvertible, attributes(ros))]
71pub fn ros_point_derive(input: TokenStream) -> TokenStream {
72 let input = parse_macro_input!(input as DeriveInput);
73 let name = input.clone().ident;
74
75 let fields = match input.data {
76 syn::Data::Struct(ref data) => data.fields.clone(),
77 _ => {
78 return syn::Error::new_spanned(input, "Only structs are supported")
79 .to_compile_error()
80 .into()
81 }
82 };
83
84 let allowed_datatypes = get_allowed_types();
85
86 if fields.is_empty() {
87 return syn::Error::new_spanned(input, "No fields found")
88 .to_compile_error()
89 .into();
90 }
91
92 for field in fields.iter() {
93 let ty = field.ty.to_token_stream().to_string();
94 if ty.contains("RGB") || ty.contains("rgb") {
95 return syn::Error::new_spanned(field, "RGB can not be guaranteed to have the correct type or layout. Implement PointConvertible manual or use predefined points instead.")
96 .to_compile_error()
97 .into();
98 }
99 if !allowed_datatypes.contains_key(&ty.as_str()) {
100 return syn::Error::new_spanned(field, "Field type not allowed")
101 .to_compile_error()
102 .into();
103 }
104 }
105
106 let field_len_token: usize = fields.len();
107 let rename_arr = struct_field_rename_array(&input);
108 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
109 let layout = layout_of_type(&name, &input.data);
110
111 let expanded = quote! {
112 unsafe impl #impl_generics ::ros_pointcloud2::PointConvertible<#field_len_token> for #name #ty_generics #where_clause {
113 fn layout() -> ::ros_pointcloud2::LayoutDescription {
114 let mut last_field_end = 0;
115 let mut fields = Vec::new();
116
117 #layout
118
119 let mut rename_idx = 0;
120 let rename_arr = vec![#(#rename_arr),*];
121 let field_info: Vec<::ros_pointcloud2::LayoutField> = fields.into_iter().map(|field| {
122 match field {
123 (0, ty, size) => {
124 rename_idx += 1;
125 ::ros_pointcloud2::LayoutField::new(rename_arr[rename_idx - 1], ty, size)
126 },
127 (1, _, size) => ::ros_pointcloud2::LayoutField::padding(size),
128 _ => unreachable!(),
129 }
130 }).collect();
131
132 ::ros_pointcloud2::LayoutDescription::new(field_info.as_slice())
133 }
134 }
135 };
136
137 let field_names_get = fields
138 .iter()
139 .enumerate()
140 .map(|(idx, f)| {
141 let field_name = f.ident.as_ref().unwrap();
142 quote! { #field_name: point[#idx].get() }
143 })
144 .collect::<Vec<_>>();
145
146 let from_my_point = quote! {
147 impl From<ros_pointcloud2::IPoint<#field_len_token>> for #name {
148 fn from(point: ros_pointcloud2::IPoint<#field_len_token>) -> Self {
149 Self {
150 #(#field_names_get,)*
151 }
152 }
153 }
154 };
155
156 let field_names_into = fields
157 .iter()
158 .map(|f| {
159 let field_name = f.ident.as_ref().unwrap();
160 quote! { point.#field_name.into() }
161 })
162 .collect::<Vec<_>>();
163
164 let from_custom_point = quote! {
165 impl From<#name> for ros_pointcloud2::IPoint<#field_len_token> {
166 fn from(point: #name) -> Self {
167 [ #(#field_names_into,)* ].into()
168 }
169 }
170 };
171
172 TokenStream::from(quote! {
173 #expanded
174 #from_my_point
175 #from_custom_point
176 })
177}
178
179fn layout_of_type(struct_name: &Ident, data: &Data) -> proc_macro2::TokenStream {
180 match data {
181 Data::Struct(data) => match &data.fields {
182 Fields::Named(fields) => {
183 let values = fields.named.iter().map(|field| {
184 let field_name = field.ident.as_ref().unwrap();
185 let field_ty = &field.ty;
186 let field_ty_str = Literal::string(&field_ty.to_token_stream().to_string());
187 let field_ty = &field.ty;
188
189 quote! {
190 let size = ::core::mem::size_of::<#field_ty>();
191 let offset = ::core::mem::offset_of!(#struct_name, #field_name);
192 if offset > last_field_end {
193 fields.push((1, "", offset - last_field_end));
194 }
195 fields.push((0, #field_ty_str, size));
196 last_field_end = offset + size;
197 }
198 });
199
200 quote! {
201 #(#values)*
202
203 let struct_size = ::std::mem::size_of::<#struct_name>();
204 if struct_size > last_field_end {
205 fields.push((1, "", struct_size - last_field_end));
206 }
207 }
208 }
209 Fields::Unnamed(_) => unimplemented!(),
210 Fields::Unit => unimplemented!(),
211 },
212 Data::Enum(_) | Data::Union(_) => unimplemented!("type-layout only supports structs"),
213 }
214}