From 336cea11e8b91f3f0de3990e15d2b20d301081a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Cicho=C5=84?= Date: Wed, 2 Oct 2024 11:09:50 +0200 Subject: [PATCH] specctra, specctra_derive: rewrite logic inspecting macro attributes This allows parsing more complicated attribute syntax, here needed to specify multiple names for a `Vec` field. --- macros/specctra_derive/src/lib.rs | 44 +++++++++++++++------ macros/specctra_derive/src/read.rs | 59 +++++++++++++++------------- macros/specctra_derive/src/write.rs | 60 ++++++++++++++++------------- src/specctra/read.rs | 9 ++++- src/specctra/structure.rs | 2 +- 5 files changed, 109 insertions(+), 65 deletions(-) diff --git a/macros/specctra_derive/src/lib.rs b/macros/specctra_derive/src/lib.rs index a2f4e61..fc2a878 100644 --- a/macros/specctra_derive/src/lib.rs +++ b/macros/specctra_derive/src/lib.rs @@ -1,5 +1,6 @@ use proc_macro::TokenStream; -use syn::{Attribute, DeriveInput, LitStr}; +use syn::{Attribute, DeriveInput, LitStr, Meta, Token}; +use syn::punctuated::Punctuated; mod read; mod write; @@ -16,16 +17,37 @@ pub fn derive_write(input: TokenStream) -> TokenStream { write::impl_write(&input).into() } -fn attr_present(attrs: &Vec, name: &str) -> bool { - attrs - .iter() - .find(|attr| attr.path().is_ident(name)) - .is_some() +enum FieldType { + Anonymous, + AnonymousVec, + NamedVec(Vec), + NotSpecified, } -fn attr_content(attrs: &Vec, name: &str) -> Option { - attrs - .iter() - .find(|attr| attr.path().is_ident(name)) - .and_then(|attr| Some(attr.parse_args::().expect("string literal").value())) +fn parse_attributes(attrs: &Vec) -> FieldType { + for attr in attrs { + match &attr.meta { + Meta::Path(path) => { + if path.is_ident("anon") { + return FieldType::Anonymous; + } else if path.is_ident("anon_vec") { + return FieldType::AnonymousVec; + } + }, + Meta::List(list) if list.path.is_ident("vec") => { + return FieldType::NamedVec(list + .parse_args_with( + Punctuated::::parse_terminated + ) + .expect("#[vec(...)] must contain a list of string literals") + .iter() + .cloned() + .collect() + ); + }, + _ => (), + } + } + + FieldType::NotSpecified } diff --git a/macros/specctra_derive/src/read.rs b/macros/specctra_derive/src/read.rs index c34cc51..3825cbe 100644 --- a/macros/specctra_derive/src/read.rs +++ b/macros/specctra_derive/src/read.rs @@ -4,8 +4,8 @@ use syn::ext::IdentExt; use syn::Type::Path; use syn::{Data, DeriveInput, Field, Fields}; -use crate::attr_content; -use crate::attr_present; +use crate::parse_attributes; +use crate::FieldType; pub fn impl_read(input: &DeriveInput) -> TokenStream { let name = &input.ident; @@ -46,34 +46,41 @@ fn impl_body(data: &Data) -> TokenStream { fn impl_field(field: &Field) -> TokenStream { let name = &field.ident; let name_str = name.as_ref().expect("field name").unraw(); + let field_type = parse_attributes(&field.attrs); - if attr_present(&field.attrs, "anon") { - quote! { - #name: tokenizer.read_value()?, - } - } else if let Some(dsn_name) = attr_content(&field.attrs, "vec") { - quote! { - #name: tokenizer.read_named_array(#dsn_name)?, - } - } else if attr_present(&field.attrs, "anon_vec") { - quote! { - #name: tokenizer.read_array()?, - } - } else { - if let Path(type_path) = &field.ty { - let segments = &type_path.path.segments; - if segments.len() == 1 { - let ident = &segments.first().unwrap().ident; - if ident == "Option" { - return quote! { - #name: tokenizer.read_optional(stringify!(#name_str))?, - }; + match field_type { + FieldType::Anonymous => { + quote! { + #name: tokenizer.read_value()?, + } + }, + FieldType::AnonymousVec => { + quote! { + #name: tokenizer.read_array()?, + } + }, + FieldType::NamedVec(valid_aliases) => { + quote! { + #name: tokenizer.read_array_with_alias(&[#(#valid_aliases),*])?, + } + }, + FieldType::NotSpecified => { + if let Path(type_path) = &field.ty { + let segments = &type_path.path.segments; + if segments.len() == 1 { + let ident = &segments.first().unwrap().ident; + if ident == "Option" { + return quote! { + #name: tokenizer + .read_optional(stringify!(#name_str))?, + }; + } } } - } - quote! { - #name: tokenizer.read_named(stringify!(#name_str))?, + quote! { + #name: tokenizer.read_named(stringify!(#name_str))?, + } } } } diff --git a/macros/specctra_derive/src/write.rs b/macros/specctra_derive/src/write.rs index 22db6ad..ea675e1 100644 --- a/macros/specctra_derive/src/write.rs +++ b/macros/specctra_derive/src/write.rs @@ -4,8 +4,8 @@ use syn::ext::IdentExt; use syn::Type::Path; use syn::{Data, DeriveInput, Field, Fields}; -use crate::attr_content; -use crate::attr_present; +use crate::parse_attributes; +use crate::FieldType; pub fn impl_write(input: &DeriveInput) -> TokenStream { let name = &input.ident; @@ -44,34 +44,42 @@ fn impl_body(data: &Data) -> TokenStream { fn impl_field(field: &Field) -> TokenStream { let name = &field.ident; let name_str = name.as_ref().expect("field name").unraw(); + let field_type = parse_attributes(&field.attrs); - if attr_present(&field.attrs, "anon") { - quote! { - writer.write_value(&self.#name)?; - } - } else if let Some(dsn_name) = attr_content(&field.attrs, "vec") { - quote! { - writer.write_named_array(#dsn_name, &self.#name)?; - } - } else if attr_present(&field.attrs, "anon_vec") { - quote! { - writer.write_array(&self.#name)?; - } - } else { - if let Path(type_path) = &field.ty { - let segments = &type_path.path.segments; - if segments.len() == 1 { - let ident = &segments.first().unwrap().ident; - if ident == "Option" { - return quote! { - writer.write_optional(stringify!(#name_str), &self.#name)?; - }; + match field_type { + FieldType::Anonymous => { + quote! { + writer.write_value(&self.#name)?; + } + }, + FieldType::AnonymousVec => { + quote! { + writer.write_array(&self.#name)?; + } + }, + FieldType::NamedVec(valid_aliases) => { + let canonical_name = &valid_aliases[0]; + quote! { + writer.write_named_array(#canonical_name, &self.#name)?; + } + }, + FieldType::NotSpecified => { + if let Path(type_path) = &field.ty { + let segments = &type_path.path.segments; + if segments.len() == 1 { + let ident = &segments.first().unwrap().ident; + if ident == "Option" { + return quote! { + writer + .write_optional(stringify!(#name_str), &self.#name)?; + }; + } } } - } - quote! { - writer.write_named(stringify!(#name_str), &self.#name)?; + quote! { + writer.write_named(stringify!(#name_str), &self.#name)?; + } } } } diff --git a/src/specctra/read.rs b/src/specctra/read.rs index 130d219..067439e 100644 --- a/src/specctra/read.rs +++ b/src/specctra/read.rs @@ -387,6 +387,13 @@ impl ListTokenizer { pub fn read_named_array>( &mut self, name: &'static str, + ) -> Result, ParseErrorContext> { + self.read_array_with_alias(&[name]) + } + + pub fn read_array_with_alias>( + &mut self, + valid_names: &[&'static str], ) -> Result, ParseErrorContext> { let mut array = Vec::::new(); loop { @@ -395,7 +402,7 @@ impl ListTokenizer { name: ref actual_name, } = input.token { - if actual_name == name { + if valid_names.contains(&actual_name.to_ascii_lowercase().as_ref()) { let value = self.read_value::()?; self.consume_token()?.expect_end()?; array.push(value); diff --git a/src/specctra/structure.rs b/src/specctra/structure.rs index b9e6600..5ce7088 100644 --- a/src/specctra/structure.rs +++ b/src/specctra/structure.rs @@ -159,7 +159,7 @@ pub struct Grid { #[derive(ReadDsn, WriteSes, Debug)] pub struct StructureRule { pub width: Option, - #[vec("clearance")] + #[vec("clearance", "clear")] pub clearances: Vec, }