diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-07-02 19:59:52 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-07-02 20:07:43 +0200 |
| commit | ebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch) | |
| tree | 2bbc24ddb4124c4bb14dec0e536129d4de37b056 /macros/src/castable.rs | |
| parent | 3ab19185093d7709f824b95b979060ce125389d8 (diff) | |
Move everything into `crates/` directory
Diffstat (limited to 'macros/src/castable.rs')
| -rw-r--r-- | macros/src/castable.rs | 331 |
1 files changed, 0 insertions, 331 deletions
diff --git a/macros/src/castable.rs b/macros/src/castable.rs deleted file mode 100644 index 05c1b4d1..00000000 --- a/macros/src/castable.rs +++ /dev/null @@ -1,331 +0,0 @@ -use super::*; - -/// Expand the `#[derive(Cast)]` macro. -pub fn derive_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! { - #ty, - self => ::typst::eval::IntoValue::into_value(match self { - #(#variants_to_strs),* - }), - #(#strs_to_variants),* - } - }) -} - -/// An enum variant in a `derive(Cast)`. -struct Variant { - ident: Ident, - string: String, - docs: String, -} - -/// Expand the `cast!` macro. -pub fn cast(stream: TokenStream) -> Result<TokenStream> { - let input: CastInput = syn::parse2(stream)?; - let ty = &input.ty; - let eval = quote! { ::typst::eval }; - - let castable_body = create_castable_body(&input); - let describe_body = create_describe_body(&input); - let into_value_body = create_into_value_body(&input); - let from_value_body = create_from_value_body(&input); - - let reflect = (!input.from_value.is_empty() || input.name.is_some()).then(|| { - quote! { - impl #eval::Reflect for #ty { - fn describe() -> #eval::CastInfo { - #describe_body - } - - fn castable(value: &#eval::Value) -> bool { - #castable_body - } - } - } - }); - - let into_value = (input.into_value.is_some() || input.name.is_some()).then(|| { - quote! { - impl #eval::IntoValue for #ty { - fn into_value(self) -> #eval::Value { - #into_value_body - } - } - } - }); - - let from_value = (!input.from_value.is_empty() || input.name.is_some()).then(|| { - quote! { - impl #eval::FromValue for #ty { - fn from_value(value: #eval::Value) -> ::typst::diag::StrResult<Self> { - #from_value_body - } - } - } - }); - - let ty = input.name.as_ref().map(|name| { - quote! { - impl #eval::Type for #ty { - const TYPE_NAME: &'static str = #name; - } - } - }); - - Ok(quote! { - #reflect - #into_value - #from_value - #ty - }) -} - -/// The input to `cast!`. -struct CastInput { - ty: syn::Type, - name: Option<syn::LitStr>, - into_value: Option<syn::Expr>, - from_value: Punctuated<Cast, Token![,]>, -} - -impl Parse for CastInput { - fn parse(input: ParseStream) -> Result<Self> { - let ty; - let mut name = None; - if input.peek(syn::Token![type]) { - let _: syn::Token![type] = input.parse()?; - ty = input.parse()?; - let _: syn::Token![:] = input.parse()?; - name = Some(input.parse()?); - } else { - ty = input.parse()?; - } - - let _: syn::Token![,] = input.parse()?; - - let mut into_value = None; - if input.peek(syn::Token![self]) { - let _: syn::Token![self] = input.parse()?; - let _: syn::Token![=>] = input.parse()?; - into_value = Some(input.parse()?); - let _: syn::Token![,] = input.parse()?; - } - - let from_value = Punctuated::parse_terminated(input)?; - Ok(Self { ty, name, into_value, from_value }) - } -} - -impl Parse for Cast { - fn parse(input: ParseStream) -> Result<Self> { - let attrs = input.call(syn::Attribute::parse_outer)?; - let pattern = input.parse()?; - let _: syn::Token![=>] = input.parse()?; - let expr = input.parse()?; - Ok(Self { attrs, pattern, expr }) - } -} - -impl Parse for Pattern { - fn parse(input: ParseStream) -> Result<Self> { - if input.peek(syn::LitStr) { - Ok(Pattern::Str(input.parse()?)) - } else { - let pat = syn::Pat::parse_single(input)?; - let _: syn::Token![:] = input.parse()?; - let ty = input.parse()?; - Ok(Pattern::Ty(pat, ty)) - } - } -} - -/// A single cast, e.g. `v: i64 => Self::Int(v)`. -struct Cast { - attrs: Vec<syn::Attribute>, - pattern: Pattern, - expr: syn::Expr, -} - -/// A pattern in a cast, e.g.`"ascender"` or `v: i64`. -enum Pattern { - Str(syn::LitStr), - Ty(syn::Pat, syn::Type), -} - -fn create_castable_body(input: &CastInput) -> TokenStream { - let mut strings = vec![]; - let mut casts = vec![]; - - for cast in &input.from_value { - match &cast.pattern { - Pattern::Str(lit) => { - strings.push(quote! { #lit => return true }); - } - Pattern::Ty(_, ty) => { - casts.push(quote! { - if <#ty as ::typst::eval::Reflect>::castable(value) { - return true; - } - }); - } - } - } - - let dynamic_check = input.name.is_some().then(|| { - quote! { - if let ::typst::eval::Value::Dyn(dynamic) = &value { - if dynamic.is::<Self>() { - return true; - } - } - } - }); - - let str_check = (!strings.is_empty()).then(|| { - quote! { - if let ::typst::eval::Value::Str(string) = &value { - match string.as_str() { - #(#strings,)* - _ => {} - } - } - } - }); - - quote! { - #dynamic_check - #str_check - #(#casts)* - false - } -} - -fn create_describe_body(input: &CastInput) -> TokenStream { - let mut infos = vec![]; - - for cast in &input.from_value { - let docs = documentation(&cast.attrs); - infos.push(match &cast.pattern { - Pattern::Str(lit) => { - quote! { - ::typst::eval::CastInfo::Value( - ::typst::eval::IntoValue::into_value(#lit), - #docs, - ) - } - } - Pattern::Ty(_, ty) => { - quote! { <#ty as ::typst::eval::Reflect>::describe() } - } - }); - } - - if let Some(name) = &input.name { - infos.push(quote! { - ::typst::eval::CastInfo::Type(#name) - }); - } - - quote! { - #(#infos)+* - } -} - -fn create_into_value_body(input: &CastInput) -> TokenStream { - if let Some(expr) = &input.into_value { - quote! { #expr } - } else { - quote! { ::typst::eval::Value::dynamic(self) } - } -} - -fn create_from_value_body(input: &CastInput) -> TokenStream { - let mut string_arms = vec![]; - let mut cast_checks = vec![]; - - for cast in &input.from_value { - let expr = &cast.expr; - match &cast.pattern { - Pattern::Str(lit) => { - string_arms.push(quote! { #lit => return Ok(#expr) }); - } - Pattern::Ty(binding, ty) => { - cast_checks.push(quote! { - if <#ty as ::typst::eval::Reflect>::castable(&value) { - let #binding = <#ty as ::typst::eval::FromValue>::from_value(value)?; - return Ok(#expr); - } - }); - } - } - } - - let dynamic_check = input.name.is_some().then(|| { - quote! { - if let ::typst::eval::Value::Dyn(dynamic) = &value { - if let Some(concrete) = dynamic.downcast::<Self>() { - return Ok(concrete.clone()); - } - } - } - }); - - let str_check = (!string_arms.is_empty()).then(|| { - quote! { - if let ::typst::eval::Value::Str(string) = &value { - match string.as_str() { - #(#string_arms,)* - _ => {} - } - } - } - }); - - quote! { - #dynamic_check - #str_check - #(#cast_checks)* - Err(<Self as ::typst::eval::Reflect>::error(&value)) - } -} |
