diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-13 21:39:38 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-13 21:40:06 +0100 |
| commit | 880b1847bd4170ce80be5781c2163ba085cdcaff (patch) | |
| tree | 3fbfdb70cb04c4922f0ec9e3f29f2c63d11d753b /macros | |
| parent | cb3c263c4a67f4d361dbdb5048a1c073bd1fff96 (diff) | |
Derive `Cast` for enums
Diffstat (limited to 'macros')
| -rw-r--r-- | macros/Cargo.toml | 1 | ||||
| -rw-r--r-- | macros/src/castable.rs | 62 | ||||
| -rw-r--r-- | macros/src/lib.rs | 11 | ||||
| -rw-r--r-- | macros/src/util.rs | 4 |
4 files changed, 76 insertions, 2 deletions
diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 8b4280fe..24d537be 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -15,3 +15,4 @@ proc-macro2 = "1" quote = "1" syn = { version = "1", features = ["full", "extra-traits"] } unscanny = "0.1" +heck = "0.4" diff --git a/macros/src/castable.rs b/macros/src/castable.rs index c0d0c1ad..cd05ed2d 100644 --- a/macros/src/castable.rs +++ b/macros/src/castable.rs @@ -1,5 +1,67 @@ use super::*; +/// Expand the `#[derive(Cast)]` macro. +pub fn cast(item: DeriveInput) -> Result<TokenStream> { + let ty = &item.ident; + + let syn::Data::Enum(data) = &item.data else { + bail!(item, "only enums are supported"); + }; + + let mut variants = vec![]; + for variant in &data.variants { + if let Some((_, expr)) = &variant.discriminant { + bail!(expr, "explicit discriminant is not allowed"); + } + + let string = if let Some(attr) = + variant.attrs.iter().find(|attr| attr.path.is_ident("string")) + { + attr.parse_args::<syn::LitStr>()?.value() + } else { + kebab_case(&variant.ident) + }; + + variants.push(Variant { + ident: variant.ident.clone(), + string, + docs: documentation(&variant.attrs), + }); + } + + let strs_to_variants = variants.iter().map(|Variant { ident, string, docs }| { + quote! { + #[doc = #docs] + #string => Self::#ident + } + }); + + let variants_to_strs = variants.iter().map(|Variant { ident, string, .. }| { + quote! { + #ty::#ident => #string + } + }); + + Ok(quote! { + ::typst::eval::cast_from_value! { + #ty, + #(#strs_to_variants),* + } + + ::typst::eval::cast_to_value! { + v: #ty => ::typst::eval::Value::from(match v { + #(#variants_to_strs),* + }) + } + }) +} + +struct Variant { + ident: Ident, + string: String, + docs: String, +} + /// Expand the `cast_from_value!` macro. pub fn cast_from_value(stream: TokenStream) -> Result<TokenStream> { let castable: Castable = syn::parse2(stream)?; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 889eaa7b..fafe8eea 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,7 +15,7 @@ use quote::quote; use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream, Parser}; use syn::punctuated::Punctuated; -use syn::{parse_quote, Ident, Result, Token}; +use syn::{parse_quote, DeriveInput, Ident, Result, Token}; use self::util::*; @@ -35,6 +35,15 @@ pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { .into() } +/// Implement `Cast` for an enum. +#[proc_macro_derive(Cast, attributes(string))] +pub fn cast(item: BoundaryStream) -> BoundaryStream { + let item = syn::parse_macro_input!(item as DeriveInput); + castable::cast(item) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + /// Implement `Cast` and optionally `Type` for a type. #[proc_macro] pub fn cast_from_value(stream: BoundaryStream) -> BoundaryStream { diff --git a/macros/src/util.rs b/macros/src/util.rs index d94ba932..53a8354e 100644 --- a/macros/src/util.rs +++ b/macros/src/util.rs @@ -1,3 +1,5 @@ +use heck::ToKebabCase; + use super::*; /// Return an error at the given item. @@ -55,7 +57,7 @@ pub fn validate_attrs(attrs: &[syn::Attribute]) -> Result<()> { /// Convert an identifier to a kebab-case string. pub fn kebab_case(name: &Ident) -> String { - name.to_string().to_lowercase().replace('_', "-") + name.to_string().to_kebab_case() } /// Extract documentation comments from an attribute list. |
