summaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
Diffstat (limited to 'macros')
-rw-r--r--macros/Cargo.toml1
-rw-r--r--macros/src/castable.rs62
-rw-r--r--macros/src/lib.rs11
-rw-r--r--macros/src/util.rs4
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.