diff --git a/crates/specctra-core/src/error.rs b/crates/specctra-core/src/error.rs index b7a3a74..670bbd6 100644 --- a/crates/specctra-core/src/error.rs +++ b/crates/specctra-core/src/error.rs @@ -15,6 +15,8 @@ pub enum ParseError { Expected(&'static str), #[error("expected \"({0}\"")] ExpectedStartOfList(&'static str), + #[error("expected one of: {0:?}")] + ExpectedStartOfListOneOf(&'static [&'static str]), #[error("expected \")\"")] ExpectedEndOfList, #[error("expected leaf value")] diff --git a/crates/specctra-core/src/structure.rs b/crates/specctra-core/src/structure.rs index f90ccef..c0c666f 100644 --- a/crates/specctra-core/src/structure.rs +++ b/crates/specctra-core/src/structure.rs @@ -279,8 +279,7 @@ pub struct Padstack { pub attach: Option, } -// TODO: derive for enums if more than this single one is needed -#[derive(Debug, Clone, PartialEq)] +#[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub enum Shape { Circle(Circle), Rect(Rect), @@ -288,33 +287,6 @@ pub enum Shape { Polygon(Polygon), } -impl ReadDsn for Shape { - fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { - let ctx = tokenizer.context(); - let name = tokenizer.consume_token()?.expect_any_start()?; - let value = match name.as_str() { - "circle" => Ok(Shape::Circle(tokenizer.read_value()?)), - "rect" => Ok(Shape::Rect(tokenizer.read_value()?)), - "path" => Ok(Shape::Path(tokenizer.read_value()?)), - "polygon" => Ok(Shape::Polygon(tokenizer.read_value()?)), - _ => Err(ParseError::Expected("a different keyword").add_context(ctx)), - }; - tokenizer.consume_token()?.expect_end()?; - value - } -} - -impl WriteSes for Shape { - fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), std::io::Error> { - match self { - Self::Circle(inner) => writer.write_named("circle", inner), - Self::Rect(inner) => writer.write_named("rect", inner), - Self::Path(inner) => writer.write_named("path", inner), - Self::Polygon(inner) => writer.write_named("polygon", inner), - } - } -} - #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Circle { #[anon] diff --git a/crates/specctra_derive/src/read.rs b/crates/specctra_derive/src/read.rs index e02d11b..6b02213 100644 --- a/crates/specctra_derive/src/read.rs +++ b/crates/specctra_derive/src/read.rs @@ -6,7 +6,7 @@ use proc_macro2::TokenStream; use quote::quote; use syn::ext::IdentExt; use syn::Type::Path; -use syn::{Data, DeriveInput, Field, Fields}; +use syn::{Data, DeriveInput, Field, Fields, Variant}; use crate::parse_attributes; use crate::FieldType; @@ -40,8 +40,19 @@ fn impl_body(data: &Data) -> TokenStream { } _ => unimplemented!(), }, - Data::Enum(_data) => { - todo!(); + Data::Enum(data) => { + let (variantnames, variants): (TokenStream, TokenStream) = + data.variants.iter().map(impl_variant).unzip(); + quote! { + let ctx = tokenizer.context(); + let name = tokenizer.consume_token()?.expect_any_start()?; + let value = Ok(match name.as_str() { + #variants + _ => return Err(ParseError::ExpectedStartOfListOneOf(&[#variantnames]).add_context(ctx)), + }); + tokenizer.consume_token()?.expect_end()?; + value + } } _ => unimplemented!(), } @@ -88,3 +99,35 @@ fn impl_field(field: &Field) -> TokenStream { } } } + +fn impl_variant(variant: &Variant) -> (TokenStream, TokenStream) { + let name = &variant.ident; + let mut name_str = name.unraw().to_string(); + name_str.make_ascii_lowercase(); + + let inner = match &variant.fields { + Fields::Unnamed(fields) => { + let all_parts = + core::iter::repeat(quote! { tokenizer.read_value()?, }).take(fields.unnamed.len()); + quote! { Self::#name(#(#all_parts)*) } + } + Fields::Named(fields) => { + let fields = fields.named.iter().map(impl_field); + + quote! { + Self::#name { + #(#fields)* + } + } + } + Fields::Unit => unimplemented!(), + }; + ( + quote! { + #name_str, + }, + quote! { + #name_str => #inner, + }, + ) +} diff --git a/crates/specctra_derive/src/write.rs b/crates/specctra_derive/src/write.rs index 3bc48ba..ec729ba 100644 --- a/crates/specctra_derive/src/write.rs +++ b/crates/specctra_derive/src/write.rs @@ -2,11 +2,11 @@ // // SPDX-License-Identifier: MIT -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::ext::IdentExt; use syn::Type::Path; -use syn::{Data, DeriveInput, Field, Fields}; +use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Fields, Ident, Variant}; use crate::parse_attributes; use crate::FieldType; @@ -41,6 +41,17 @@ fn impl_body(data: &Data) -> TokenStream { } _ => unimplemented!(), }, + Data::Enum(data) => { + let variants = data.variants.iter().map(impl_variant); + + quote! { + match self { + #(#variants)* + } + + Ok(()) + } + } _ => unimplemented!(), } } @@ -87,3 +98,30 @@ fn impl_field(field: &Field) -> TokenStream { } } } + +fn impl_variant(variant: &Variant) -> TokenStream { + let name = &variant.ident; + let mut name_str = name.unraw().to_string(); + name_str.make_ascii_lowercase(); + + match &variant.fields { + Fields::Unnamed(fields) => { + let names: Vec<_> = (0..fields.unnamed.len()) + .map(|i| Ident::new(&format!("inner__{}", i), Span::mixed_site())) + .collect(); + let mut select = Punctuated::<_, syn::Token![,]>::new(); + for i in &names { + select.push(i.clone()); + } + let fields = names.into_iter().map(|name| { + quote! { writer.write_value(#name)?; } + }); + quote! { Self::#name(#select) => { + writer.write_token(ListToken::Start { name: #name_str.to_string() })?; + #(#fields)* + writer.write_token(ListToken::End)?; + }, } + } + _ => unimplemented!(), + } +}