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/element.rs | |
| parent | 3ab19185093d7709f824b95b979060ce125389d8 (diff) | |
Move everything into `crates/` directory
Diffstat (limited to 'macros/src/element.rs')
| -rw-r--r-- | macros/src/element.rs | 554 |
1 files changed, 0 insertions, 554 deletions
diff --git a/macros/src/element.rs b/macros/src/element.rs deleted file mode 100644 index 6ce91fcb..00000000 --- a/macros/src/element.rs +++ /dev/null @@ -1,554 +0,0 @@ -use super::*; - -/// Expand the `#[element]` macro. -pub fn element(stream: TokenStream, body: &syn::ItemStruct) -> Result<TokenStream> { - let element = prepare(stream, body)?; - Ok(create(&element)) -} - -struct Elem { - name: String, - display: String, - category: String, - keywords: Option<String>, - docs: String, - vis: syn::Visibility, - ident: Ident, - capable: Vec<Ident>, - fields: Vec<Field>, - scope: Option<BlockWithReturn>, -} - -struct Field { - name: String, - docs: String, - internal: bool, - external: bool, - positional: bool, - required: bool, - variadic: bool, - synthesized: bool, - fold: bool, - resolve: bool, - parse: Option<BlockWithReturn>, - default: syn::Expr, - vis: syn::Visibility, - ident: Ident, - ident_in: Ident, - with_ident: Ident, - push_ident: Ident, - set_ident: Ident, - ty: syn::Type, - output: syn::Type, -} - -impl Field { - fn inherent(&self) -> bool { - self.required || self.variadic - } - - fn settable(&self) -> bool { - !self.inherent() - } -} - -/// Preprocess the element's definition. -fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> { - let syn::Fields::Named(named) = &body.fields else { - bail!(body, "expected named fields"); - }; - - let mut fields = vec![]; - for field in &named.named { - let Some(ident) = field.ident.clone() else { - bail!(field, "expected named field"); - }; - - let mut attrs = field.attrs.clone(); - let variadic = has_attr(&mut attrs, "variadic"); - let required = has_attr(&mut attrs, "required") || variadic; - let positional = has_attr(&mut attrs, "positional") || required; - - if ident == "label" { - bail!(ident, "invalid field name"); - } - - let mut field = Field { - name: kebab_case(&ident), - docs: documentation(&attrs), - internal: has_attr(&mut attrs, "internal"), - external: has_attr(&mut attrs, "external"), - positional, - required, - variadic, - synthesized: has_attr(&mut attrs, "synthesized"), - fold: has_attr(&mut attrs, "fold"), - resolve: has_attr(&mut attrs, "resolve"), - parse: parse_attr(&mut attrs, "parse")?.flatten(), - default: parse_attr(&mut attrs, "default")? - .flatten() - .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }), - vis: field.vis.clone(), - ident: ident.clone(), - ident_in: Ident::new(&format!("{}_in", ident), ident.span()), - with_ident: Ident::new(&format!("with_{}", ident), ident.span()), - push_ident: Ident::new(&format!("push_{}", ident), ident.span()), - set_ident: Ident::new(&format!("set_{}", ident), ident.span()), - ty: field.ty.clone(), - output: field.ty.clone(), - }; - - if field.required && (field.fold || field.resolve) { - bail!(ident, "required fields cannot be folded or resolved"); - } - - if field.required && !field.positional { - bail!(ident, "only positional fields can be required"); - } - - if field.resolve { - let output = &field.output; - field.output = parse_quote! { <#output as ::typst::model::Resolve>::Output }; - } - if field.fold { - let output = &field.output; - field.output = parse_quote! { <#output as ::typst::model::Fold>::Output }; - } - - validate_attrs(&attrs)?; - fields.push(field); - } - - let capable = Punctuated::<Ident, Token![,]>::parse_terminated - .parse2(stream)? - .into_iter() - .collect(); - - let mut attrs = body.attrs.clone(); - let docs = documentation(&attrs); - let mut lines = docs.split('\n').collect(); - let keywords = meta_line(&mut lines, "Keywords").ok().map(Into::into); - let category = meta_line(&mut lines, "Category")?.into(); - let display = meta_line(&mut lines, "Display")?.into(); - let docs = lines.join("\n").trim().into(); - - let element = Elem { - name: body.ident.to_string().trim_end_matches("Elem").to_lowercase(), - display, - category, - keywords, - docs, - vis: body.vis.clone(), - ident: body.ident.clone(), - capable, - fields, - scope: parse_attr(&mut attrs, "scope")?.flatten(), - }; - - validate_attrs(&attrs)?; - Ok(element) -} - -/// Produce the element's definition. -fn create(element: &Elem) -> TokenStream { - let Elem { vis, ident, docs, .. } = element; - let all = element.fields.iter().filter(|field| !field.external); - let settable = all.clone().filter(|field| !field.synthesized && field.settable()); - - // Inherent methods and functions. - let new = create_new_func(element); - let field_methods = all.clone().map(create_field_method); - let field_in_methods = settable.clone().map(create_field_in_method); - let with_field_methods = all.clone().map(create_with_field_method); - let push_field_methods = all.map(create_push_field_method); - let field_style_methods = settable.map(create_set_field_method); - - // Trait implementations. - let element_impl = create_pack_impl(element); - let construct_impl = element - .capable - .iter() - .all(|capability| capability != "Construct") - .then(|| create_construct_impl(element)); - let set_impl = create_set_impl(element); - let locatable_impl = element - .capable - .iter() - .any(|capability| capability == "Locatable") - .then(|| quote! { impl ::typst::model::Locatable for #ident {} }); - - quote! { - #[doc = #docs] - #[derive(Debug, Clone, Hash)] - #[repr(transparent)] - #vis struct #ident(pub ::typst::model::Content); - - impl #ident { - #new - #(#field_methods)* - #(#field_in_methods)* - #(#with_field_methods)* - #(#push_field_methods)* - #(#field_style_methods)* - - /// The element's span. - pub fn span(&self) -> ::typst::syntax::Span { - self.0.span() - } - - /// Set the element's span. - pub fn spanned(self, span: ::typst::syntax::Span) -> Self { - Self(self.0.spanned(span)) - } - } - - #element_impl - #construct_impl - #set_impl - #locatable_impl - - impl ::typst::eval::IntoValue for #ident { - fn into_value(self) -> ::typst::eval::Value { - ::typst::eval::Value::Content(self.0) - } - } - } -} - -/// Create the `new` function for the element. -fn create_new_func(element: &Elem) -> TokenStream { - let relevant = element - .fields - .iter() - .filter(|field| !field.external && !field.synthesized && field.inherent()); - let params = relevant.clone().map(|Field { ident, ty, .. }| { - quote! { #ident: #ty } - }); - let builder_calls = relevant.map(|Field { ident, with_ident, .. }| { - quote! { .#with_ident(#ident) } - }); - quote! { - /// Create a new element. - pub fn new(#(#params),*) -> Self { - Self(::typst::model::Content::new( - <Self as ::typst::model::Element>::func() - )) - #(#builder_calls)* - } - } -} - -/// Create an accessor methods for a field. -fn create_field_method(field: &Field) -> TokenStream { - let Field { vis, docs, ident, name, output, .. } = field; - if field.inherent() || field.synthesized { - quote! { - #[doc = #docs] - #[track_caller] - #vis fn #ident(&self) -> #output { - self.0.expect_field(#name) - } - } - } else { - let access = create_style_chain_access(field, quote! { self.0.field(#name) }); - quote! { - #[doc = #docs] - #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { - #access - } - } - } -} - -/// Create a style chain access method for a field. -fn create_field_in_method(field: &Field) -> TokenStream { - let Field { vis, ident_in, name, output, .. } = field; - let doc = format!("Access the `{}` field in the given style chain.", name); - let access = create_style_chain_access(field, quote! { None }); - quote! { - #[doc = #doc] - #vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output { - #access - } - } -} - -/// Create a style chain access method for a field. -fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStream { - let Field { name, ty, default, .. } = field; - let getter = match (field.fold, field.resolve) { - (false, false) => quote! { get }, - (false, true) => quote! { get_resolve }, - (true, false) => quote! { get_fold }, - (true, true) => quote! { get_resolve_fold }, - }; - - quote! { - styles.#getter::<#ty>( - <Self as ::typst::model::Element>::func(), - #name, - #inherent, - || #default, - ) - } -} - -/// Create a builder pattern method for a field. -fn create_with_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, with_ident, name, ty, .. } = field; - let doc = format!("Set the [`{}`](Self::{}) field.", name, ident); - quote! { - #[doc = #doc] - #vis fn #with_ident(mut self, #ident: #ty) -> Self { - Self(self.0.with_field(#name, #ident)) - } - } -} - -/// Create a set-style method for a field. -fn create_push_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, push_ident, name, ty, .. } = field; - let doc = format!("Push the [`{}`](Self::{}) field.", name, ident); - quote! { - #[doc = #doc] - #vis fn #push_ident(&mut self, #ident: #ty) { - self.0.push_field(#name, #ident); - } - } -} - -/// Create a setter method for a field. -fn create_set_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, set_ident, name, ty, .. } = field; - let doc = format!("Create a style property for the `{}` field.", name); - quote! { - #[doc = #doc] - #vis fn #set_ident(#ident: #ty) -> ::typst::model::Style { - ::typst::model::Style::Property(::typst::model::Property::new( - <Self as ::typst::model::Element>::func(), - #name, - #ident, - )) - } - } -} - -/// Create the element's `Pack` implementation. -fn create_pack_impl(element: &Elem) -> TokenStream { - let Elem { ident, name, display, keywords, category, docs, .. } = element; - let vtable_func = create_vtable_func(element); - let infos = element - .fields - .iter() - .filter(|field| !field.internal && !field.synthesized) - .map(create_param_info); - let scope = create_scope_builder(element.scope.as_ref()); - let keywords = quote_option(keywords); - quote! { - impl ::typst::model::Element for #ident { - fn pack(self) -> ::typst::model::Content { - self.0 - } - - fn unpack(content: &::typst::model::Content) -> ::std::option::Option<&Self> { - // Safety: Elements are #[repr(transparent)]. - content.is::<Self>().then(|| unsafe { - ::std::mem::transmute(content) - }) - } - - fn func() -> ::typst::model::ElemFunc { - static NATIVE: ::typst::model::NativeElemFunc = ::typst::model::NativeElemFunc { - name: #name, - vtable: #vtable_func, - construct: <#ident as ::typst::model::Construct>::construct, - set: <#ident as ::typst::model::Set>::set, - info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo { - name: #name, - display: #display, - keywords: #keywords, - docs: #docs, - params: ::std::vec![#(#infos),*], - returns: ::typst::eval::CastInfo::Union(::std::vec![ - ::typst::eval::CastInfo::Type("content") - ]), - category: #category, - scope: #scope, - }), - }; - (&NATIVE).into() - } - } - } -} - -/// Create the element's casting vtable. -fn create_vtable_func(element: &Elem) -> TokenStream { - let ident = &element.ident; - let relevant = element.capable.iter().filter(|&ident| ident != "Construct"); - let checks = relevant.map(|capability| { - quote! { - if id == ::std::any::TypeId::of::<dyn #capability>() { - return Some(unsafe { - ::typst::util::fat::vtable(&null as &dyn #capability) - }); - } - } - }); - - quote! { - |id| { - let null = Self(::typst::model::Content::new( - <#ident as ::typst::model::Element>::func() - )); - #(#checks)* - None - } - } -} - -/// Create a parameter info for a field. -fn create_param_info(field: &Field) -> TokenStream { - let Field { - name, - docs, - positional, - variadic, - required, - default, - fold, - ty, - output, - .. - } = field; - let named = !positional; - let settable = field.settable(); - let default_ty = if *fold { &output } else { &ty }; - let default = quote_option(&settable.then(|| { - quote! { - || { - let typed: #default_ty = #default; - ::typst::eval::IntoValue::into_value(typed) - } - } - })); - let ty = if *variadic { - quote! { <#ty as ::typst::eval::Variadics>::Inner } - } else { - quote! { #ty } - }; - quote! { - ::typst::eval::ParamInfo { - name: #name, - docs: #docs, - cast: <#ty as ::typst::eval::Reflect>::describe(), - default: #default, - positional: #positional, - named: #named, - variadic: #variadic, - required: #required, - settable: #settable, - } - } -} - -/// Create the element's `Construct` implementation. -fn create_construct_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - let handlers = element - .fields - .iter() - .filter(|field| { - !field.external - && !field.synthesized - && (!field.internal || field.parse.is_some()) - }) - .map(|field| { - let push_ident = &field.push_ident; - let (prefix, value) = create_field_parser(field); - if field.settable() { - quote! { - #prefix - if let Some(value) = #value { - element.#push_ident(value); - } - } - } else { - quote! { - #prefix - element.#push_ident(#value); - } - } - }); - - quote! { - impl ::typst::model::Construct for #ident { - fn construct( - vm: &mut ::typst::eval::Vm, - args: &mut ::typst::eval::Args, - ) -> ::typst::diag::SourceResult<::typst::model::Content> { - let mut element = Self(::typst::model::Content::new( - <Self as ::typst::model::Element>::func() - )); - #(#handlers)* - Ok(element.0) - } - } - } -} - -/// Create the element's `Set` implementation. -fn create_set_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - let handlers = element - .fields - .iter() - .filter(|field| { - !field.external - && !field.synthesized - && field.settable() - && (!field.internal || field.parse.is_some()) - }) - .map(|field| { - let set_ident = &field.set_ident; - let (prefix, value) = create_field_parser(field); - quote! { - #prefix - if let Some(value) = #value { - styles.set(Self::#set_ident(value)); - } - } - }); - - quote! { - impl ::typst::model::Set for #ident { - fn set( - args: &mut ::typst::eval::Args, - ) -> ::typst::diag::SourceResult<::typst::model::Styles> { - let mut styles = ::typst::model::Styles::new(); - #(#handlers)* - Ok(styles) - } - } - } -} - -/// Create argument parsing code for a field. -fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { - if let Some(BlockWithReturn { prefix, expr }) = &field.parse { - return (quote! { #(#prefix);* }, quote! { #expr }); - } - - let name = &field.name; - let value = if field.variadic { - quote! { args.all()? } - } else if field.required { - quote! { args.expect(#name)? } - } else if field.positional { - quote! { args.find()? } - } else { - quote! { args.named(#name)? } - }; - - (quote! {}, value) -} |
