diff options
Diffstat (limited to 'macros/src')
| -rw-r--r-- | macros/src/castable.rs | 62 | ||||
| -rw-r--r-- | macros/src/lib.rs | 11 | ||||
| -rw-r--r-- | macros/src/util.rs | 4 |
3 files changed, 75 insertions, 2 deletions
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. |
