Skip to content

Commit

Permalink
backport
Browse files Browse the repository at this point in the history
  • Loading branch information
ciscorn committed Jan 3, 2024
1 parent b8e2f3d commit f9f2408
Show file tree
Hide file tree
Showing 20 changed files with 536 additions and 117 deletions.
83 changes: 59 additions & 24 deletions nusamai-citygml/macros/src/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Error, LitByteStr, LitStr};

use crate::ElementType;
use crate::StereoType;

const CITYGML_ATTR_IDENT: &str = "citygml";

Expand Down Expand Up @@ -38,7 +38,8 @@ fn generate_citygml_impl_for_struct(
let mut id_value = quote!(None);
let struct_ident = &derive_input.ident;
let mut typename = String::from(stringify!(derive_input.ident));
let mut ty = ElementType::Feature;
let mut ty = StereoType::Feature;
let mut allow_extra = true; // FIXME

for attr in &derive_input.attrs {
if !attr.path().is_ident(CITYGML_ATTR_IDENT) {
Expand All @@ -48,23 +49,30 @@ fn generate_citygml_impl_for_struct(
if meta.path.is_ident("name") {
let name: LitStr = meta.value()?.parse()?;
typename = name.value();
}
if meta.path.is_ident("type") {
Ok(())
} else if meta.path.is_ident("type") {
let ty_ident: Ident = meta.value()?.parse()?;
ty = match ty_ident.to_string().as_str() {
"feature" => ElementType::Feature,
"data" => ElementType::Data,
"feature" => StereoType::Feature,
"data" => StereoType::Data,
_ => {
return Err(meta.error("feature or data expected"));
}
};
Ok(())
} else if meta.path.is_ident("allow_extra") {
allow_extra = true;
Ok(())
} else {
Ok(())

Check warning on line 67 in nusamai-citygml/macros/src/derive.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/macros/src/derive.rs#L67

Added line #L67 was not covered by tests
}
Ok(())
})?;
}

// Scan struct fields
for field in &struct_data.fields {
let mut into_obj_generated = false;

let Some(field_ident) = &field.ident else {
continue;
};
Expand All @@ -87,6 +95,10 @@ fn generate_citygml_impl_for_struct(
else if meta.path.is_ident("path") {
let path: LitByteStr = meta.value()?.parse()?;

if path.value().iter().filter(|c| c == &&b'/').count() > 1 {
return Err(meta.error("path must not contain more than one '/'"));

Check warning on line 99 in nusamai-citygml/macros/src/derive.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/macros/src/derive.rs#L99

Added line #L99 was not covered by tests
}

if path.value().starts_with(b"@") {
// XML attributes (e.g. @gml:id)
attribute_arms.push(quote! {
Expand All @@ -112,25 +124,39 @@ fn generate_citygml_impl_for_struct(
}
} else {
// XML child elements (e.g. bldg:measuredHeight)

// if the path contains '/', add the first path as a 'noop' arm.
if let Some(pos) = path.value().iter().position(|&x| x == b'/') {
let prefix = LitByteStr::new(&path.value()[..pos], path.span());
chlid_arms.push(
quote! {
#prefix => Ok(()),
}
);
};

chlid_arms.push(
quote! {
#path => <#field_ty as CityGMLElement>::parse(&mut self.#field_ident, st),
}
);

// Use the first path component as the attribute name
// e.g. "bldg:interiorRoom/bldg:Room" -> "bldg:interiorRoom"
let path_value = path.value();
let pos_slash = path_value.iter().position(|&x| x == b'/').unwrap_or(path_value.len());
let name = std::str::from_utf8(&path_value[..pos_slash]).unwrap();
if !into_obj_generated {
// Use the first path component as the attribute name
// e.g. "bldg:interiorRoom/bldg:Room" -> "bldg:interiorRoom"
let path_value = path.value();
let pos_slash = path_value.iter().position(|&x| x == b'/').unwrap_or(path_value.len());
let name = std::str::from_utf8(&path_value[..pos_slash]).unwrap();

into_object_stmts.push(
quote! {
if let Some(v) = self.#field_ident.into_object() {
attributes.insert(#name.into(), v);
into_object_stmts.push(
quote! {
if let Some(v) = self.#field_ident.into_object() {
attributes.insert(#name.into(), v);
}
}
}
)
);
into_obj_generated = true;
}
}
Ok(())
}
Expand Down Expand Up @@ -178,7 +204,7 @@ fn generate_citygml_impl_for_struct(

Ok(())
} else {
Err(meta.error("unrecognized attribute"))
Err(meta.error("unrecognized argument"))

Check warning on line 207 in nusamai-citygml/macros/src/derive.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/macros/src/derive.rs#L207

Added line #L207 was not covered by tests
}
})?;
}
Expand All @@ -196,7 +222,7 @@ fn generate_citygml_impl_for_struct(
});

let into_object_impl = match ty {
ElementType::Feature => {
StereoType::Feature => {
quote! {
Some(::nusamai_citygml::object::Value::Feature(
::nusamai_citygml::object::Feature {
Expand All @@ -212,7 +238,7 @@ fn generate_citygml_impl_for_struct(
))
}
}
ElementType::Data => {
StereoType::Data => {
quote! {
Some(::nusamai_citygml::object::Value::Data(
::nusamai_citygml::object::Data {
Expand All @@ -230,11 +256,20 @@ fn generate_citygml_impl_for_struct(
};

let element_type = match ty {
ElementType::Feature => quote! { ::nusamai_citygml::ElementType::FeatureType },
ElementType::Data => quote! { ::nusamai_citygml::ElementType::DataType },
StereoType::Feature => quote! { ::nusamai_citygml::ElementType::FeatureType },
StereoType::Data => quote! { ::nusamai_citygml::ElementType::DataType },
_ => unreachable!(),
};

let extra_arm = match allow_extra {
true => quote! { Ok(()) },
false => quote! {
Err(::nusamai_citygml::ParseError::SchemaViolation(
format!("unexpected element: {}", String::from_utf8_lossy(st.current_path())),
))
},

Check warning on line 270 in nusamai-citygml/macros/src/derive.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/macros/src/derive.rs#L266-L270

Added lines #L266 - L270 were not covered by tests
};

Ok(quote! {
impl #impl_generics ::nusamai_citygml::CityGMLElement for #struct_ident #ty_generics #where_clause {
const ELEMENT_TYPE: ::nusamai_citygml::ElementType = #element_type;
Expand All @@ -245,7 +280,7 @@ fn generate_citygml_impl_for_struct(
st.parse_children(|st| {
match st.current_path() {
#(#chlid_arms)*
_ => Ok(()),
_ => #extra_arm,
}
})
}
Expand Down
10 changes: 6 additions & 4 deletions nusamai-citygml/macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Parsing utilities for CityGML 2.0 (and possibly 3.0)
extern crate proc_macro;

mod derive;
Expand All @@ -12,21 +14,21 @@ pub fn derive_citygml_element(token: TokenStream) -> TokenStream {

#[proc_macro_attribute]
pub fn citygml_feature(args: TokenStream, input: TokenStream) -> TokenStream {
type_attrs::citygml_type(ElementType::Feature, args, input)
type_attrs::citygml_type(StereoType::Feature, args, input)
}

#[proc_macro_attribute]
pub fn citygml_data(args: TokenStream, input: TokenStream) -> TokenStream {
type_attrs::citygml_type(ElementType::Data, args, input)
type_attrs::citygml_type(StereoType::Data, args, input)
}

#[proc_macro_attribute]
pub fn citygml_property(args: TokenStream, input: TokenStream) -> TokenStream {
type_attrs::citygml_type(ElementType::Property, args, input)
type_attrs::citygml_type(StereoType::Property, args, input)
}

#[derive(Clone, Copy)]
pub(crate) enum ElementType {
pub(crate) enum StereoType {
Feature,
Data,
Property,
Expand Down
32 changes: 18 additions & 14 deletions nusamai-citygml/macros/src/type_attrs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::ElementType;
use crate::StereoType;
use proc_macro2::TokenStream;
use quote::quote;
use syn::meta::ParseNestedMeta;
Expand All @@ -17,7 +17,11 @@ impl FeatureArgs {
if meta.path.is_ident("name") {
let s: LitStr = meta.value()?.parse()?;
self.prefix = Some(LitByteStr::new(
s.value().split_once(':').unwrap().0.as_bytes(),
s.value()
.split_once(':')
.ok_or_else(|| meta.error("ns prefix is missing"))?
.0
.as_bytes(),
s.span(),
));
self.name = Some(s);
Expand All @@ -29,7 +33,7 @@ impl FeatureArgs {
}

pub(crate) fn citygml_type(
ty: ElementType,
ty: StereoType,
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
Expand Down Expand Up @@ -57,7 +61,7 @@ fn add_named_field(fields: &mut syn::FieldsNamed, body: TokenStream) {
.push(syn::Field::parse_named.parse2(body).unwrap())
}

fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Result<(), Error> {
fn modify(ty: &StereoType, args: &FeatureArgs, input: &mut DeriveInput) -> Result<(), Error> {
match &args.name {
Some(name) => {
input.attrs.push(syn::parse_quote! {
Expand All @@ -68,13 +72,13 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu
};

input.attrs.push(match &ty {
ElementType::Feature => {
StereoType::Feature => {
syn::parse_quote! { #[citygml(type = feature)] }
}
ElementType::Data => {
StereoType::Data => {
syn::parse_quote! { #[citygml(type = data)] }
}
ElementType::Property => {
StereoType::Property => {
syn::parse_quote! { #[citygml(type = property)] }
}
});
Expand All @@ -84,12 +88,12 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu
// for #[citygml_feature] and #[citygml_data]

match ty {
ElementType::Feature | ElementType::Data => {}
StereoType::Feature | StereoType::Data => {}
_ => return Err(Error::new_spanned(input, "target must be struct")),
}

if let syn::Fields::Named(ref mut fields) = data.fields {
if let ElementType::Feature = ty {
if let StereoType::Feature = ty {
// for #[citygml_feature]

let prefix = args.prefix.as_ref().unwrap();
Expand Down Expand Up @@ -132,36 +136,36 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:creationDate")]
#[citygml(path = b"core:creationDate")]
pub creation_date: Option<nusamai_citygml::Date> // TODO: DateTime (CityGML 3.0)
},
);
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:terminationDate")]
#[citygml(path = b"core:terminationDate")]
pub termination_date: Option<nusamai_citygml::Date> // TODO: DateTime (CityGML 3.0)
},
);
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:validFrom")]
#[citygml(path = b"core:validFrom")]
pub valid_from: Option<nusamai_citygml::Date> // TODO: DateTime (CityGML 3.0)
},
);
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:validTo")]
#[citygml(path = b"core:validTo")]
pub valid_to: Option<nusamai_citygml::Date> // TODO: DateTime (CityGML 3.0)
},
);
}
}
}
Data::Enum(_data) => match ty {
ElementType::Property => {
StereoType::Property => {
// for #[citygml_property]
_data.variants.push(parse_quote! {
#[default]
Expand Down
19 changes: 19 additions & 0 deletions nusamai-citygml/src/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::parser::ParseError;

pub trait CityGMLAttribute: Sized {
fn parse_attr_value(value: &str) -> Result<Self, ParseError>;
}

impl CityGMLAttribute for String {
#[inline]
fn parse_attr_value(value: &str) -> Result<Self, ParseError> {
Ok(value.to_string())
}
}

impl<T: CityGMLAttribute> CityGMLAttribute for Option<T> {
#[inline]
fn parse_attr_value(value: &str) -> Result<Self, ParseError> {
Ok(Some(<T as CityGMLAttribute>::parse_attr_value(value)?))
}
}
4 changes: 2 additions & 2 deletions nusamai-citygml/src/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub enum GeometryParseType {
}

#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum GeometryType {
#[default]
Unknown,
Expand All @@ -24,7 +24,7 @@ pub enum GeometryType {
}

#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub struct GeometryRefEntry {
#[serde(rename = "type")]
pub ty: GeometryType,
Expand Down
2 changes: 2 additions & 0 deletions nusamai-citygml/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod attribute;
pub mod codelist;
pub mod geometry;
pub mod namespace;
pub mod object;
pub mod parser;
pub mod values;

pub use attribute::*;
pub use geometry::*;
pub use macros::*;
pub use namespace::*;
Expand Down
Loading

0 comments on commit f9f2408

Please sign in to comment.