Skip to main content

pasture_derive/
lib.rs

1extern crate proc_macro;
2use std::collections::HashSet;
3
4//use anyhow::{anyhow, bail, Result};
5use layout::{get_struct_member_layout, StructMemberLayout};
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::DeriveInput;
9use syn::{
10    parse_macro_input, Attribute, Data, Error, Field, Fields, GenericArgument, Ident, Lit,
11    NestedMeta, PathArguments, Result, Type, TypePath,
12};
13
14mod layout;
15
16enum PasturePrimitiveType {
17    U8,
18    I8,
19    U16,
20    I16,
21    U32,
22    I32,
23    U64,
24    I64,
25    F32,
26    F64,
27    Vec3u8,
28    Vec3u16,
29    Vec3f32,
30    Vec3f64,
31    Vec3i32,
32    Vec4u8,
33}
34
35impl PasturePrimitiveType {
36    fn min_alignment(&self) -> u64 {
37        match self {
38            PasturePrimitiveType::U8 => 1,
39            PasturePrimitiveType::I8 => 1,
40            PasturePrimitiveType::U16 => 2,
41            PasturePrimitiveType::I16 => 2,
42            PasturePrimitiveType::U32 => 4,
43            PasturePrimitiveType::I32 => 4,
44            PasturePrimitiveType::U64 => 8,
45            PasturePrimitiveType::I64 => 8,
46            PasturePrimitiveType::F32 => 4,
47            PasturePrimitiveType::F64 => 8,
48            PasturePrimitiveType::Vec3u8 => 1,
49            PasturePrimitiveType::Vec3u16 => 2,
50            PasturePrimitiveType::Vec3f32 => 4,
51            PasturePrimitiveType::Vec3f64 => 8,
52            PasturePrimitiveType::Vec3i32 => 4,
53            &PasturePrimitiveType::Vec4u8 => 1,
54        }
55    }
56
57    fn size(&self) -> u64 {
58        match self {
59            PasturePrimitiveType::U8 => 1,
60            PasturePrimitiveType::I8 => 1,
61            PasturePrimitiveType::U16 => 2,
62            PasturePrimitiveType::I16 => 2,
63            PasturePrimitiveType::U32 => 4,
64            PasturePrimitiveType::I32 => 4,
65            PasturePrimitiveType::U64 => 8,
66            PasturePrimitiveType::I64 => 8,
67            PasturePrimitiveType::F32 => 4,
68            PasturePrimitiveType::F64 => 8,
69            PasturePrimitiveType::Vec3u8 => 3,
70            PasturePrimitiveType::Vec3u16 => 6,
71            PasturePrimitiveType::Vec3f32 => 12,
72            PasturePrimitiveType::Vec3f64 => 24,
73            PasturePrimitiveType::Vec3i32 => 12,
74            &PasturePrimitiveType::Vec4u8 => 4,
75        }
76    }
77
78    fn as_token_stream(&self) -> quote::__private::TokenStream {
79        match self {
80            PasturePrimitiveType::U8 => quote! {pasture_core::layout::PointAttributeDataType::U8},
81            PasturePrimitiveType::I8 => quote! {pasture_core::layout::PointAttributeDataType::I8},
82            PasturePrimitiveType::U16 => quote! {pasture_core::layout::PointAttributeDataType::U16},
83            PasturePrimitiveType::I16 => quote! {pasture_core::layout::PointAttributeDataType::I16},
84            PasturePrimitiveType::U32 => quote! {pasture_core::layout::PointAttributeDataType::U32},
85            PasturePrimitiveType::I32 => quote! {pasture_core::layout::PointAttributeDataType::I32},
86            PasturePrimitiveType::U64 => quote! {pasture_core::layout::PointAttributeDataType::U64},
87            PasturePrimitiveType::I64 => quote! {pasture_core::layout::PointAttributeDataType::I64},
88            PasturePrimitiveType::F32 => quote! {pasture_core::layout::PointAttributeDataType::F32},
89            PasturePrimitiveType::F64 => quote! {pasture_core::layout::PointAttributeDataType::F64},
90            PasturePrimitiveType::Vec3u8 => {
91                quote! {pasture_core::layout::PointAttributeDataType::Vec3u8}
92            }
93            PasturePrimitiveType::Vec3u16 => {
94                quote! {pasture_core::layout::PointAttributeDataType::Vec3u16}
95            }
96            PasturePrimitiveType::Vec3f32 => {
97                quote! {pasture_core::layout::PointAttributeDataType::Vec3f32}
98            }
99            PasturePrimitiveType::Vec3f64 => {
100                quote! {pasture_core::layout::PointAttributeDataType::Vec3f64}
101            }
102            PasturePrimitiveType::Vec3i32 => {
103                quote! {pasture_core::layout::PointAttributeDataType::Vec3i32}
104            }
105            PasturePrimitiveType::Vec4u8 => {
106                quote! {pasture_core::layout::PointAttributeDataType::Vec4u8}
107            }
108        }
109    }
110}
111
112fn get_primitive_type_for_ident_type(ident: &Ident) -> Result<PasturePrimitiveType> {
113    let type_name = ident.to_string();
114    match type_name.as_str() {
115        "u8" => Ok(PasturePrimitiveType::U8),
116        "u16" => Ok(PasturePrimitiveType::U16),
117        "u32" => Ok(PasturePrimitiveType::U32),
118        "u64" => Ok(PasturePrimitiveType::U64),
119        "i8" => Ok(PasturePrimitiveType::I8),
120        "i16" => Ok(PasturePrimitiveType::I16),
121        "i32" => Ok(PasturePrimitiveType::I32),
122        "i64" => Ok(PasturePrimitiveType::I64),
123        "f32" => Ok(PasturePrimitiveType::F32),
124        "f64" => Ok(PasturePrimitiveType::F64),
125        _ => Err(Error::new_spanned(
126            ident,
127            format!("Type {} is no valid Pasture primitive type!", type_name),
128        )),
129    }
130}
131
132fn get_primitive_type_for_non_ident_type(type_path: &TypePath) -> Result<PasturePrimitiveType> {
133    // Path should have an ident (Vector3, Vector4, ...), as well as one generic argument
134    let valid_idents: HashSet<_> = ["Vector3", "Vector4"].iter().collect();
135
136    let path_segment = type_path
137        .path
138        .segments
139        .first()
140        .ok_or_else(|| Error::new_spanned(&type_path.path, "Invalid type"))?;
141    if !valid_idents.contains(&path_segment.ident.to_string().as_str()) {
142        return Err(Error::new_spanned(&path_segment.ident, "Invalid type"));
143    }
144
145    let path_arg = match &path_segment.arguments {
146        PathArguments::AngleBracketed(arg) => arg,
147        _ => return Err(Error::new_spanned(&path_segment.arguments, "Invalid type")),
148    };
149
150    let first_generic_arg = path_arg
151        .args
152        .first()
153        .ok_or_else(|| Error::new_spanned(path_arg, "Invalid type arguments"))?;
154
155    let type_arg = match first_generic_arg {
156        GenericArgument::Type(t) => t,
157        _ => return Err(Error::new_spanned(first_generic_arg, "Invalid type")),
158    };
159
160    let type_path = match type_arg {
161        Type::Path(p) => p,
162        _ => return Err(Error::new_spanned(type_arg, "Invalid type")),
163    };
164
165    match type_path.path.get_ident() {
166        Some(ident) => {
167            // Not ALL primitive types are supported as generic arguments for Vector3
168            let type_name = ident.to_string();
169            match path_segment.ident.to_string().as_str() {
170                "Vector3" => match type_name.as_str() {
171                    "u8" => Ok(PasturePrimitiveType::Vec3u8),
172                    "u16" => Ok(PasturePrimitiveType::Vec3u16),
173                    "f32" => Ok(PasturePrimitiveType::Vec3f32),
174                    "f64" => Ok(PasturePrimitiveType::Vec3f64),
175                    "i32" => Ok(PasturePrimitiveType::Vec3i32),
176                    _ => Err(Error::new_spanned(
177                        ident,
178                        format!("Vector3<{}> is no valid Pasture primitive type. Vector3 is supported, but only for generic argument(s) u8, u16, i32, f32 or f64", type_name),
179                    ))
180                },
181                "Vector4" => match type_name.as_str() {
182                    "u8" => Ok(PasturePrimitiveType::Vec4u8),
183                    _ => Err(Error::new_spanned(
184                        ident,
185                        format!("Vector4<{}> is no valid Pasture primitive type. Vector4 is supported, but only for generic argument(s) u8", type_name),
186                    ))
187                },
188                _ => Err(Error::new_spanned(ident, "Invalid type")),
189            }
190        }
191        None => Err(Error::new_spanned(&type_path.path, "Invalid type")),
192    }
193}
194
195fn type_path_to_primitive_type(type_path: &TypePath) -> Result<PasturePrimitiveType> {
196    if type_path.qself.is_some() {
197        return Err(Error::new_spanned(
198            type_path,
199            "Qualified types are illegal in a struct with #[derive(PointType)]",
200        ));
201    }
202
203    let datatype = match type_path.path.get_ident() {
204        Some(ident) => get_primitive_type_for_ident_type(ident),
205        None => get_primitive_type_for_non_ident_type(type_path),
206    }?;
207
208    Ok(datatype)
209    // let gen = quote! {
210    //     pasture_core::layout::PointAttributeDataType::#datatype_name
211    // };
212    // Ok(gen)
213}
214
215fn get_attribute_name_from_field(field: &Field) -> Result<String> {
216    if field.attrs.len() != 1 {
217        return Err(Error::new_spanned(
218            field,
219            "derive(PointType) requires exactly one #[pasture] attribute per member!",
220        ));
221    }
222    let pasture_attribute = &field.attrs[0];
223    let meta = pasture_attribute.parse_meta()?;
224    // TODO Better explanation of the builtin Pasture attributes in this error message!
225    let malformed_field_error_msg = "#[pasture] attribute is malformed. Correct syntax is #[pasture(attribute = \"NAME\")] or #[pasture(BUILTIN_XXX)], where XXX matches any of the builtin attributes in Pasture.";
226
227    // For now, we expect that 'meta' is a Meta::List containing a single entry
228    // The entry should be a NameValue, corresponding to 'attribute = "NAME"', or a Path, corresponding to 'builtin_XXX', where XXX matches any of the basic
229    // builtin attributes in Pasture (such as INTENSITY, POSITION_3D etc.)
230    match &meta {
231        syn::Meta::List(list) => {
232            let first_list_entry = list
233                .nested
234                .first()
235                .ok_or_else(|| Error::new_spanned(list, malformed_field_error_msg))?;
236            let nested_meta = match first_list_entry {
237                NestedMeta::Meta(nested_meta) => nested_meta,
238                _ => return Err(Error::new_spanned(list, malformed_field_error_msg)),
239            };
240
241            match nested_meta {
242                syn::Meta::Path(path) => {
243                    let ident = path
244                        .get_ident()
245                        .ok_or_else(|| Error::new_spanned(path, malformed_field_error_msg))?;
246                    let ident_as_str = ident.to_string();
247                    match ident_as_str.as_str() {
248                        "BUILTIN_POSITION_3D" => Ok("Position3D".into()),
249                        "BUILTIN_INTENSITY" => Ok("Intensity".into()),
250                        "BUILTIN_RETURN_NUMBER" => Ok("ReturnNumber".into()),
251                        "BUILTIN_NUMBER_OF_RETURNS" => Ok("NumberOfReturns".into()),
252                        "BUILTIN_CLASSIFICATION_FLAGS" => Ok("ClassificationFlags".into()),
253                        "BUILTIN_SCANNER_CHANNEL" => Ok("ScannerChannel".into()),
254                        "BUILTIN_SCAN_DIRECTION_FLAG" => Ok("ScanDirectionFlag".into()),
255                        "BUILTIN_EDGE_OF_FLIGHT_LINE" => Ok("EdgeOfFlightLine".into()),
256                        "BUILTIN_CLASSIFICATION" => Ok("Classification".into()),
257                        "BUILTIN_SCAN_ANGLE_RANK" => Ok("ScanAngleRank".into()),
258                        "BUILTIN_SCAN_ANGLE" => Ok("ScanAngle".into()),
259                        "BUILTIN_USER_DATA" => Ok("UserData".into()),
260                        "BUILTIN_POINT_SOURCE_ID" => Ok("PointSourceID".into()),
261                        "BUILTIN_COLOR_RGB" => Ok("ColorRGB".into()),
262                        "BUILTIN_GPS_TIME" => Ok("GpsTime".into()),
263                        "BUILTIN_NIR" => Ok("NIR".into()),
264                        "BUILTIN_WAVE_PACKET_DESCRIPTOR_INDEX" => {
265                            Ok("WavePacketDescriptorIndex".into())
266                        }
267                        "BUILTIN_WAVEFORM_DATA_OFFSET" => Ok("WaveformDataOffset".into()),
268                        "BUILTIN_WAVEFORM_PACKET_SIZE" => Ok("WaveformPacketSize".into()),
269                        "BUILTIN_RETURN_POINT_WAVEFORM_LOCATION" => {
270                            Ok("ReturnPointWaveformLocation".into())
271                        }
272                        "BUILTIN_WAVEFORM_PARAMETERS" => Ok("WaveformParameters".into()),
273                        "BUILTIN_POINT_ID" => Ok("PointID".into()),
274                        "BUILTIN_NORMAL" => Ok("Normal".into()),
275                        // TODO Other attributes
276                        _ => Err(Error::new_spanned(
277                            ident,
278                            format!("Unrecognized attribute name {}", ident_as_str),
279                        )),
280                    }
281                }
282                syn::Meta::NameValue(name_value) => name_value
283                    .path
284                    .get_ident()
285                    .and_then(|path| {
286                        if path != "attribute" {
287                            return None;
288                        }
289
290                        if let Lit::Str(ref attribute_name) = name_value.lit {
291                            Some(attribute_name.value())
292                        } else {
293                            None
294                        }
295                    })
296                    .ok_or_else(|| Error::new_spanned(name_value, malformed_field_error_msg)),
297                bad => Err(Error::new_spanned(bad, malformed_field_error_msg)),
298            }
299        }
300        bad => Err(Error::new_spanned(bad, malformed_field_error_msg)),
301    }
302}
303
304/// Describes a single field within a `PointType` struct. Contains the name of the field, the point attribute
305/// that the field maps to, as well as the primitive type of the field
306struct FieldLayoutDescription {
307    pub attribute_name: String,
308    pub primitive_type: PasturePrimitiveType,
309}
310
311fn get_field_layout_descriptions(fields: &Fields) -> Result<Vec<FieldLayoutDescription>> {
312    fields
313        .iter()
314        .map(|field| match field.ty {
315            Type::Path(ref type_path) => {
316                let primitive_type = type_path_to_primitive_type(type_path)?;
317                let attribute_name = get_attribute_name_from_field(field)?;
318
319                Ok(FieldLayoutDescription {
320                    attribute_name,
321                    primitive_type,
322                })
323            }
324            ref bad => Err(Error::new_spanned(bad, "Invalid type in PointType struct")),
325        })
326        .collect::<Result<Vec<FieldLayoutDescription>>>()
327}
328
329fn field_parameters(data: &Data, ident: &Ident) -> Result<Vec<FieldLayoutDescription>> {
330    // TODO Make sure that structrs are #[repr(C)] - OR figure out the exact layout of the members in the struct. But #[repr(rust)] is allowed
331    // to re-order the fields in the struct, which would (maybe?) break the Layout. Then again, if we correctly determine offsets and sizes of
332    // fields, the order might not be important anymore?! It's really quite tricky to get this right and will need a lot of tests
333    // We can use this maybe: https://doc.rust-lang.org/std/alloc/struct.Layout.html
334    //
335    //let member_layout = get_struct_member_layout(type_attributes, struct_data)?;
336
337    match data {
338        Data::Struct(struct_data) => get_field_layout_descriptions(&struct_data.fields),
339        _ => Err(Error::new_spanned(
340            ident,
341            "#[derive(PointType)] is only valid for structs",
342        )),
343    }
344}
345
346fn calculate_offsets_and_alignment(
347    fields: &[FieldLayoutDescription],
348    data: &Data,
349    ident: &Ident,
350    type_attributes: &[Attribute],
351) -> Result<(Vec<u64>, u64)> {
352    let struct_data = match data {
353        Data::Struct(struct_data) => struct_data,
354        _ => {
355            return Err(Error::new_spanned(
356                ident,
357                "#[derive(PointType)] is only valid for structs",
358            ))
359        }
360    };
361    let struct_layout = get_struct_member_layout(type_attributes, struct_data)?;
362
363    let mut current_offset = 0;
364    let mut max_alignment = 1;
365    let mut offsets = vec![];
366    for field in fields {
367        let min_alignment = match struct_layout {
368            StructMemberLayout::C => field.primitive_type.min_alignment(),
369            StructMemberLayout::Packed(max_alignment) => {
370                std::cmp::min(max_alignment, field.primitive_type.min_alignment())
371            }
372        };
373        max_alignment = std::cmp::max(min_alignment, max_alignment);
374
375        let aligned_offset = ((current_offset + min_alignment - 1) / min_alignment) * min_alignment;
376        offsets.push(aligned_offset);
377        current_offset = aligned_offset + field.primitive_type.size();
378    }
379
380    Ok((offsets, max_alignment))
381}
382
383/// Custom `derive` macro that implements the [`PointType`](pasture_core::layout::PointType) trait for the type that it is applied to.
384///
385/// Any that that wants to implement `PointType` using this `derive` macro must fulfill the following requirements:
386/// - It must be at least one of `#[repr(C)]` and `#[repr(packed)]`
387/// - All its members may only be [Pasture primitive types](pasture_core::layout::PointAttributeDataType)
388/// - Each member must contain an attribute `#[pasture(X)]`, where `X` is either one of the builtin attributes explained below, or `attribute = "name"` for a custom attribute named `name`
389/// - No two members may share the same attribute name
390///
391/// # Builtin attributes
392///
393/// To associate a member of a custom `PointType` with one of the builtin point attributes in Pasture, use the `#[pasture(X)]` attribute, where `X` is one of:
394///
395/// - `BUILTIN_POSITION_3D` corresponding to the [POSITION_3D](pasture_core::layout::attributes::POSITION_3D) attribute
396/// - `BUILTIN_INTENSITY` corresponding to the [INTENSITY](pasture_core::layout::attributes::INTENSITY) attribute
397/// - `BUILTIN_RETURN_NUMBER` corresponding to the [RETURN_NUMBER](pasture_core::layout::attributes::RETURN_NUMBER) attribute
398/// - `BUILTIN_NUMBER_OF_RETURNS` corresponding to the [NUMBER_OF_RETURNS](pasture_core::layout::attributes::NUMBER_OF_RETURNS) attribute
399/// - `BUILTIN_CLASSIFICATION_FLAGS` corresponding to the [CLASSIFICATION_FLAGS](pasture_core::layout::attributes::CLASSIFICATION_FLAGS) attribute
400/// - `BUILTIN_SCANNER_CHANNEL` corresponding to the [SCANNER_CHANNEL](pasture_core::layout::attributes::SCANNER_CHANNEL) attribute
401/// - `BUILTIN_SCAN_DIRECTION_FLAG` corresponding to the [SCAN_DIRECTION_FLAG](pasture_core::layout::attributes::SCAN_DIRECTION_FLAG) attribute
402/// - `BUILTIN_EDGE_OF_FLIGHT_LINE` corresponding to the [EDGE_OF_FLIGHT_LINE](pasture_core::layout::attributes::EDGE_OF_FLIGHT_LINE) attribute
403/// - `BUILTIN_CLASSIFICATION` corresponding to the [CLASSIFICATION](pasture_core::layout::attributes::CLASSIFICATION) attribute
404/// - `BUILTIN_SCAN_ANGLE_RANK` corresponding to the [SCAN_ANGLE_RANK](pasture_core::layout::attributes::SCAN_ANGLE_RANK) attribute
405/// - `BUILTIN_SCAN_ANGLE` corresponding to the [SCAN_ANGLE](pasture_core::layout::attributes::SCAN_ANGLE) attribute
406/// - `BUILTIN_USER_DATA` corresponding to the [USER_DATA](pasture_core::layout::attributes::USER_DATA) attribute
407/// - `BUILTIN_POINT_SOURCE_ID` corresponding to the [POINT_SOURCE_ID](pasture_core::layout::attributes::POINT_SOURCE_ID) attribute
408/// - `BUILTIN_COLOR_RGB` corresponding to the [COLOR_RGB](pasture_core::layout::attributes::COLOR_RGB) attribute
409/// - `BUILTIN_GPS_TIME` corresponding to the [GPS_TIME](pasture_core::layout::attributes::GPS_TIME) attribute
410/// - `BUILTIN_NIR` corresponding to the [NIR](pasture_core::layout::attributes::NIR) attribute
411/// - `BUILTIN_WAVE_PACKET_DESCRIPTOR_INDEX` corresponding to the [WAVE_PACKET_DESCRIPTOR_INDEX](pasture_core::layout::attributes::WAVE_PACKET_DESCRIPTOR_INDEX) attribute
412/// - `BUILTIN_WAVEFORM_DATA_OFFSET` corresponding to the [WAVEFORM_DATA_OFFSET](pasture_core::layout::attributes::WAVEFORM_DATA_OFFSET) attribute
413/// - `BUILTIN_WAVEFORM_PACKET_SIZE` corresponding to the [WAVEFORM_PACKET_SIZE](pasture_core::layout::attributes::WAVEFORM_PACKET_SIZE) attribute
414/// - `BUILTIN_RETURN_POINT_WAVEFORM_LOCATION` corresponding to the [RETURN_POINT_WAVEFORM_LOCATION](pasture_core::layout::attributes::RETURN_POINT_WAVEFORM_LOCATION) attribute
415/// - `BUILTIN_WAVEFORM_PARAMETERS` corresponding to the [WAVEFORM_PARAMETERS](pasture_core::layout::attributes::WAVEFORM_PARAMETERS) attribute
416/// - `BUILTIN_POINT_ID` corresponding to the [POINT_ID](pasture_core::layout::attributes::POINT_ID) attribute
417/// - `BUILTIN_NORMAL` corresponding to the [NORMAL](pasture_core::layout::attributes::NORMAL) attribute
418///
419/// # Custom attributes
420///
421/// To associate a member of a custom `PointType` with a point attribute with custom `name`, use the `#[pasture(attribute = "name")]` attribute
422#[proc_macro_derive(PointType, attributes(pasture))]
423pub fn derive_point_type(item: TokenStream) -> TokenStream {
424    let input = parse_macro_input!(item as DeriveInput);
425
426    // What we want:
427    //   - Ensure that the current type is a struct and not an enum
428    //   - Get all members of the struct
429    //   - Ensure that all members are one of the accepted primitive types that pasture-core defines
430    //   - Ensure that each member has an appropriate attribute on it for the name of the attribute
431    //   - Get the name, size and offset of each member, in order
432    //   - Generate an impl PointType for the struct, where we build the layout using the types, names, sizes and offsets
433
434    if !input.generics.params.is_empty() {
435        return Error::new_spanned(input, "derive(PointType) is not valid for generic types")
436            .to_compile_error()
437            .into();
438    }
439
440    let name = &input.ident;
441
442    let fields = match field_parameters(&input.data, name) {
443        Ok(inner) => inner,
444        Err(why) => {
445            return why.to_compile_error().into();
446        }
447    };
448    let (offsets, type_alignment) =
449        match calculate_offsets_and_alignment(&fields, &input.data, name, input.attrs.as_slice()) {
450            Ok(inner) => inner,
451            Err(why) => {
452                return why.to_compile_error().into();
453            }
454        };
455
456    let attribute_descriptions = fields.iter().zip(offsets.iter()).map(|(field, offset)| {
457        let attribute_name = &field.attribute_name;
458        let primitive_type = &field.primitive_type.as_token_stream();
459        quote! {
460            pasture_core::layout::PointAttributeDefinition::custom(std::borrow::Cow::Borrowed(#attribute_name), #primitive_type).at_offset_in_type(#offset)
461        }
462    });
463
464    let gen = quote! {
465        impl pasture_core::layout::PointType for #name {
466            fn layout() -> pasture_core::layout::PointLayout {
467                pasture_core::layout::PointLayout::from_members_and_alignment(&[
468                    #(#attribute_descriptions ,)*
469                ], #type_alignment)
470            }
471        }
472    };
473
474    gen.into()
475}