diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index d36f8db5c..a00510b66 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -114,22 +114,22 @@ impl TextNode { /// Whether the font weight should be increased by 300. #[property(skip, fold)] - pub(super) const BOLD: Toggle = false; + const BOLD: Toggle = false; /// Whether the font style should be inverted. #[property(skip, fold)] - pub(super) const ITALIC: Toggle = false; + const ITALIC: Toggle = false; /// A case transformation that should be applied to the text. #[property(skip)] - pub(super) const CASE: Option = None; + const CASE: Option = None; /// Whether small capital glyphs should be used. ("smcp") #[property(skip)] - pub(super) const SMALLCAPS: bool = false; + const SMALLCAPS: bool = false; /// A destination the text should be linked to. #[property(skip, referenced)] pub(crate) const LINK: Option = None; /// Decorative lines. #[property(skip, fold)] - pub(super) const DECO: Decoration = vec![]; + const DECO: Decoration = vec![]; fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { // The text constructor is special: It doesn't create a text node. diff --git a/macros/src/capability.rs b/macros/src/capability.rs new file mode 100644 index 000000000..7dd4c42a2 --- /dev/null +++ b/macros/src/capability.rs @@ -0,0 +1,10 @@ +use super::*; + +/// Expand the `#[capability]` macro. +pub fn expand(body: syn::ItemTrait) -> Result { + let ident = &body.ident; + Ok(quote! { + #body + impl ::typst::model::Capability for dyn #ident {} + }) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 7e2caae5e..699deee9b 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -2,437 +2,39 @@ extern crate proc_macro; -use proc_macro::TokenStream; -use proc_macro2::{TokenStream as TokenStream2, TokenTree}; +/// Return an error at the given item. +macro_rules! bail { + ($item:expr, $fmt:literal $($tts:tt)*) => { + return Err(Error::new_spanned( + &$item, + format!(concat!("typst: ", $fmt) $($tts)*) + )) + } +} + +mod capability; +mod node; + +use proc_macro::TokenStream as BoundaryStream; +use proc_macro2::{TokenStream, TokenTree}; use quote::{quote, quote_spanned}; use syn::parse_quote; -use syn::punctuated::Punctuated; -use syn::spanned::Spanned; use syn::{Error, Ident, Result}; -/// Implement `Capability` for a trait. -#[proc_macro_attribute] -pub fn capability(_: TokenStream, item: TokenStream) -> TokenStream { - let item_trait = syn::parse_macro_input!(item as syn::ItemTrait); - let name = &item_trait.ident; - quote! { - #item_trait - impl ::typst::model::Capability for dyn #name {} - } - .into() -} - /// Implement `Node` for a struct. #[proc_macro_attribute] -pub fn node(stream: TokenStream, item: TokenStream) -> TokenStream { - let impl_block = syn::parse_macro_input!(item as syn::ItemImpl); - expand_node(stream.into(), impl_block) +pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { + let item = syn::parse_macro_input!(item as syn::ItemImpl); + node::expand(stream.into(), item) .unwrap_or_else(|err| err.to_compile_error()) .into() } -/// Expand an impl block for a node. -fn expand_node( - stream: TokenStream2, - mut impl_block: syn::ItemImpl, -) -> Result { - // Split the node type into name and generic type arguments. - let params = &impl_block.generics.params; - let self_ty = &*impl_block.self_ty; - let (self_name, self_args) = parse_self(self_ty)?; - - let module = quote::format_ident!("{}_types", self_name); - - let mut key_modules = vec![]; - let mut properties = vec![]; - let mut construct = None; - let mut set = None; - let mut field = None; - - for item in std::mem::take(&mut impl_block.items) { - match item { - syn::ImplItem::Const(mut item) => { - let (property, module) = - process_const(&mut item, params, self_ty, &self_name, &self_args)?; - properties.push(property); - key_modules.push(module); - impl_block.items.push(syn::ImplItem::Const(item)); - } - syn::ImplItem::Method(method) => { - match method.sig.ident.to_string().as_str() { - "construct" => construct = Some(method), - "set" => set = Some(method), - "field" => field = Some(method), - _ => return Err(Error::new(method.span(), "unexpected method")), - } - } - _ => return Err(Error::new(item.span(), "unexpected item")), - } - } - - let construct = construct.unwrap_or_else(|| { - parse_quote! { - fn construct( - _: &mut model::Vm, - _: &mut model::Args, - ) -> typst::diag::SourceResult { - unimplemented!() - } - } - }); - - let set = generate_set(&properties, set); - - let field = field.unwrap_or_else(|| { - parse_quote! { - fn field(&self, name: &str) -> Option { - None - } - } - }); - - let items: syn::punctuated::Punctuated = - parse_quote! { #stream }; - - let checks = items.iter().map(|cap| { - quote! { - if id == TypeId::of::() { - return Some(unsafe { typst::util::fat::vtable(self as &dyn #cap) }); - } - } - }); - - let vtable = quote! { - fn vtable(&self, id: TypeId) -> Option<*const ()> { - #(#checks)* - None - } - }; - - let name = self_name.trim_end_matches("Node").to_lowercase(); - - // Put everything into a module with a hopefully unique type to isolate - // it from the outside. - Ok(quote! { - #[allow(non_snake_case)] - mod #module { - use ::std::any::TypeId; - use ::std::marker::PhantomData; - use ::once_cell::sync::Lazy; - use ::typst::model; - use super::*; - - #impl_block - - impl<#params> model::Node for #self_ty { - #construct - #set - #field - - fn id(&self) -> model::NodeId { - model::NodeId::of::() - } - - fn name(&self) -> &'static str { - #name - } - - #vtable - } - - #(#key_modules)* - } - }) -} - -/// Parse the name and generic type arguments of the node type. -fn parse_self( - self_ty: &syn::Type, -) -> Result<(String, Punctuated)> { - // Extract the node type for which we want to generate properties. - let path = match self_ty { - syn::Type::Path(path) => path, - ty => return Err(Error::new(ty.span(), "must be a path type")), - }; - - // Split up the type into its name and its generic type arguments. - let last = path.path.segments.last().unwrap(); - let self_name = last.ident.to_string(); - let self_args = match &last.arguments { - syn::PathArguments::AngleBracketed(args) => args.args.clone(), - _ => Punctuated::new(), - }; - - Ok((self_name, self_args)) -} - -/// Process a single const item. -fn process_const( - item: &mut syn::ImplItemConst, - params: &Punctuated, - self_ty: &syn::Type, - self_name: &str, - self_args: &Punctuated, -) -> Result<(Property, syn::ItemMod)> { - let property = parse_property(item)?; - - // The display name, e.g. `TextNode::BOLD`. - let name = format!("{}::{}", self_name, &item.ident); - - // The type of the property's value is what the user of our macro wrote - // as type of the const ... - let value_ty = &item.ty; - let output_ty = if property.referenced { - parse_quote!(&'a #value_ty) - } else if property.fold && property.resolve { - parse_quote!(<<#value_ty as model::Resolve>::Output as model::Fold>::Output) - } else if property.fold { - parse_quote!(<#value_ty as model::Fold>::Output) - } else if property.resolve { - parse_quote!(<#value_ty as model::Resolve>::Output) - } else { - value_ty.clone() - }; - - // ... but the real type of the const becomes this ... - let key = quote! { Key<#value_ty, #self_args> }; - let phantom_args = self_args.iter().filter(|arg| match arg { - syn::GenericArgument::Type(syn::Type::Path(path)) => { - params.iter().all(|param| match param { - syn::GenericParam::Const(c) => !path.path.is_ident(&c.ident), - _ => true, - }) - } - _ => true, - }); - - let default = &item.expr; - - // Ensure that the type is - // - either `Copy`, or - // - that the property is referenced, or - // - that the property isn't copy but can't be referenced because it needs - // folding. - let get; - let mut copy = None; - - if property.referenced { - get = quote! { - values.next().unwrap_or_else(|| { - static LAZY: Lazy<#value_ty> = Lazy::new(|| #default); - &*LAZY - }) - }; - } else if property.resolve && property.fold { - get = quote! { - match values.next().cloned() { - Some(value) => model::Fold::fold( - model::Resolve::resolve(value, chain), - Self::get(chain, values), - ), - None => #default, - } - }; - } else if property.resolve { - get = quote! { - let value = values.next().cloned().unwrap_or_else(|| #default); - model::Resolve::resolve(value, chain) - }; - } else if property.fold { - get = quote! { - match values.next().cloned() { - Some(value) => model::Fold::fold(value, Self::get(chain, values)), - None => #default, - } - }; - } else { - get = quote! { - values.next().copied().unwrap_or(#default) - }; - - copy = Some(quote_spanned! { item.ty.span() => - const _: fn() -> () = || { - fn type_must_be_copy_or_fold_or_referenced() {} - type_must_be_copy_or_fold_or_referenced::<#value_ty>(); - }; - }); - } - - // Generate the module code. - let module_name = &item.ident; - let module = parse_quote! { - #[allow(non_snake_case)] - mod #module_name { - use super::*; - - pub struct Key(pub PhantomData<(VALUE, #(#phantom_args,)*)>); - - impl<#params> Copy for #key {} - impl<#params> Clone for #key { - fn clone(&self) -> Self { - *self - } - } - - impl<'a, #params> model::Key<'a> for #key { - type Value = #value_ty; - type Output = #output_ty; - - const NAME: &'static str = #name; - - fn node() -> model::NodeId { - model::NodeId::of::<#self_ty>() - } - - fn get( - chain: model::StyleChain<'a>, - mut values: impl Iterator, - ) -> Self::Output { - #get - } - } - - #copy - } - }; - - // Replace type and initializer expression with the `Key`. - item.ty = parse_quote! { #module_name::#key }; - item.expr = parse_quote! { #module_name::Key(PhantomData) }; - - Ok((property, module)) -} - -/// A style property. -struct Property { - name: Ident, - skip: bool, - referenced: bool, - shorthand: Option, - resolve: bool, - fold: bool, -} - -enum Shorthand { - Positional, - Named(Ident), -} - -/// Parse a style property attribute. -fn parse_property(item: &mut syn::ImplItemConst) -> Result { - let mut property = Property { - name: item.ident.clone(), - skip: false, - shorthand: None, - referenced: false, - resolve: false, - fold: false, - }; - - if let Some(idx) = item - .attrs - .iter() - .position(|attr| attr.path.get_ident().map_or(false, |name| name == "property")) - { - let attr = item.attrs.remove(idx); - let mut stream = attr.parse_args::()?.into_iter().peekable(); - while let Some(token) = stream.next() { - match token { - TokenTree::Ident(ident) => match ident.to_string().as_str() { - "skip" => property.skip = true, - "shorthand" => { - let short = if let Some(TokenTree::Group(group)) = stream.peek() { - let span = group.span(); - let repr = group.to_string(); - let ident = repr.trim_matches(|c| matches!(c, '(' | ')')); - if !ident.chars().all(|c| c.is_ascii_alphabetic()) { - return Err(Error::new(span, "invalid args")); - } - stream.next(); - Shorthand::Named(Ident::new(ident, span)) - } else { - Shorthand::Positional - }; - property.shorthand = Some(short); - } - "referenced" => property.referenced = true, - "resolve" => property.resolve = true, - "fold" => property.fold = true, - _ => return Err(Error::new(ident.span(), "invalid attribute")), - }, - TokenTree::Punct(_) => {} - _ => return Err(Error::new(token.span(), "invalid token")), - } - } - } - - let span = property.name.span(); - if property.skip && property.shorthand.is_some() { - return Err(Error::new(span, "skip and shorthand are mutually exclusive")); - } - - if property.referenced && (property.fold || property.resolve) { - return Err(Error::new( - span, - "referenced is mutually exclusive with fold and resolve", - )); - } - - Ok(property) -} - -/// Auto-generate a `set` function from properties. -fn generate_set( - properties: &[Property], - user: Option, -) -> syn::ImplItemMethod { - let user = user.map(|method| { - let block = &method.block; - quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; } - }); - - let mut shorthands = vec![]; - let sets: Vec<_> = properties - .iter() - .filter(|p| !p.skip) - .map(|property| { - let name = &property.name; - let string = name.to_string().replace('_', "-").to_lowercase(); - - let value = if let Some(short) = &property.shorthand { - match short { - Shorthand::Positional => quote! { args.named_or_find(#string)? }, - Shorthand::Named(named) => { - shorthands.push(named); - quote! { args.named(#string)?.or_else(|| #named.clone()) } - } - } - } else { - quote! { args.named(#string)? } - }; - - quote! { styles.set_opt(Self::#name, #value); } - }) - .collect(); - - shorthands.sort(); - shorthands.dedup_by_key(|ident| ident.to_string()); - - let bindings = shorthands.into_iter().map(|ident| { - let string = ident.to_string(); - quote! { let #ident = args.named(#string)?; } - }); - - parse_quote! { - fn set( - args: &mut model::Args, - constructor: bool, - ) -> typst::diag::SourceResult { - let mut styles = model::StyleMap::new(); - #user - #(#bindings)* - #(#sets)* - Ok(styles) - } - } +/// Implement `Capability` for a trait. +#[proc_macro_attribute] +pub fn capability(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { + let item = syn::parse_macro_input!(item as syn::ItemTrait); + capability::expand(item) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } diff --git a/macros/src/node.rs b/macros/src/node.rs new file mode 100644 index 000000000..b92dabcc4 --- /dev/null +++ b/macros/src/node.rs @@ -0,0 +1,516 @@ +use syn::parse::Parser; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::Token; + +use super::*; + +/// Expand the `#[node]` macro. +pub fn expand(attr: TokenStream, body: syn::ItemImpl) -> Result { + let node = prepare(attr, body)?; + let scope = create(&node)?; + Ok(quote! { + const _: () = { #scope }; + }) +} + +/// Details about a node. +struct Node { + body: syn::ItemImpl, + params: Punctuated, + self_ty: syn::Type, + self_name: String, + self_args: Punctuated, + capabilities: Vec, + properties: Vec, + construct: Option, + set: Option, + field: Option, +} + +/// A style property. +struct Property { + attrs: Vec, + vis: syn::Visibility, + name: Ident, + value_ty: syn::Type, + output_ty: syn::Type, + default: syn::Expr, + skip: bool, + referenced: bool, + shorthand: Option, + resolve: bool, + fold: bool, +} + +/// The shorthand form of a style property. +enum Shorthand { + Positional, + Named(Ident), +} + +/// Preprocess the impl block of a node. +fn prepare(attr: TokenStream, body: syn::ItemImpl) -> Result { + // Extract the generic type arguments. + let params = body.generics.params.clone(); + + // Extract the node type for which we want to generate properties. + let self_ty = (*body.self_ty).clone(); + let self_path = match &self_ty { + syn::Type::Path(path) => path, + ty => bail!(ty, "must be a path type"), + }; + + // Split up the type into its name and its generic type arguments. + let last = self_path.path.segments.last().unwrap(); + let self_name = last.ident.to_string(); + let self_args = match &last.arguments { + syn::PathArguments::AngleBracketed(args) => args.args.clone(), + _ => Punctuated::new(), + }; + + // Parse the capabilities. + let capabilities: Vec<_> = Punctuated::::parse_terminated + .parse2(attr)? + .into_iter() + .collect(); + + let mut properties = vec![]; + let mut construct = None; + let mut set = None; + let mut field = None; + + // Parse the properties and methods. + for item in &body.items { + match item { + syn::ImplItem::Const(item) => { + properties.push(prepare_property(item)?); + } + syn::ImplItem::Method(method) => { + match method.sig.ident.to_string().as_str() { + "construct" => construct = Some(method.clone()), + "set" => set = Some(method.clone()), + "field" => field = Some(method.clone()), + _ => bail!(method, "unexpected method"), + } + } + _ => bail!(item, "unexpected item"), + } + } + + Ok(Node { + body, + params, + self_ty, + self_name, + self_args, + capabilities, + properties, + construct, + set, + field, + }) +} + +/// Preprocess and validate a property constant. +fn prepare_property(item: &syn::ImplItemConst) -> Result { + let mut attrs = item.attrs.clone(); + let tokens = match attrs + .iter() + .position(|attr| attr.path.is_ident("property")) + .map(|i| attrs.remove(i)) + { + Some(attr) => attr.parse_args::()?, + None => TokenStream::default(), + }; + + let mut skip = false; + let mut shorthand = None; + let mut referenced = false; + let mut resolve = false; + let mut fold = false; + + // Parse the `#[property(..)]` attribute. + let mut stream = tokens.into_iter().peekable(); + while let Some(token) = stream.next() { + let ident = match token { + TokenTree::Ident(ident) => ident, + TokenTree::Punct(_) => continue, + _ => bail!(token, "invalid token"), + }; + + let mut arg = None; + if let Some(TokenTree::Group(group)) = stream.peek() { + let span = group.span(); + let string = group.to_string(); + let ident = string.trim_start_matches('(').trim_end_matches(')'); + if !ident.chars().all(|c| c.is_ascii_alphabetic()) { + bail!(group, "invalid arguments"); + } + arg = Some(Ident::new(ident, span)); + stream.next(); + }; + + match ident.to_string().as_str() { + "skip" => skip = true, + "shorthand" => { + shorthand = Some(match arg { + Some(name) => Shorthand::Named(name), + None => Shorthand::Positional, + }); + } + "referenced" => referenced = true, + "resolve" => resolve = true, + "fold" => fold = true, + _ => bail!(ident, "invalid attribute"), + } + } + + if skip && shorthand.is_some() { + bail!(item.ident, "skip and shorthand are mutually exclusive"); + } + + if referenced && (fold || resolve) { + bail!(item.ident, "referenced is mutually exclusive with fold and resolve"); + } + + // The type of the property's value is what the user of our macro wrote as + // type of the const, but the real type of the const will be a unique `Key` + // type. + let value_ty = item.ty.clone(); + let output_ty = if referenced { + parse_quote! { &'a #value_ty } + } else if fold && resolve { + parse_quote! { + <<#value_ty as ::typst::model::Resolve>::Output + as ::typst::model::Fold>::Output + } + } else if fold { + parse_quote! { <#value_ty as ::typst::model::Fold>::Output } + } else if resolve { + parse_quote! { <#value_ty as ::typst::model::Resolve>::Output } + } else { + value_ty.clone() + }; + + Ok(Property { + attrs, + vis: item.vis.clone(), + name: item.ident.clone(), + value_ty, + output_ty, + default: item.expr.clone(), + skip, + shorthand, + referenced, + resolve, + fold, + }) +} + +/// Produce the necessary items for a type to become a node. +fn create(node: &Node) -> Result { + let params = &node.params; + let self_ty = &node.self_ty; + + let id_method = create_node_id_method(); + let name_method = create_node_name_method(node); + let construct_func = create_node_construct_func(node); + let set_func = create_node_set_func(node); + let field_method = create_node_field_method(node); + let vtable_method = create_node_vtable_method(node); + + let node_impl = quote! { + impl<#params> ::typst::model::Node for #self_ty { + #id_method + #name_method + #construct_func + #set_func + #field_method + #vtable_method + } + }; + + let mut modules: Vec = vec![]; + let mut items: Vec = vec![]; + + for property in &node.properties { + let (key, module) = create_property_module(node, &property); + modules.push(module); + + let name = &property.name; + let attrs = &property.attrs; + let vis = &property.vis; + items.push(parse_quote! { + #(#attrs)* + #vis const #name: #name::#key = #name::Key(::std::marker::PhantomData); + }); + } + + let mut body = node.body.clone(); + body.items = items; + + Ok(quote! { + #body + #node_impl + #(#modules)* + }) +} + +/// Create the node's id method. +fn create_node_id_method() -> syn::ImplItemMethod { + parse_quote! { + fn id(&self) -> ::typst::model::NodeId { + ::typst::model::NodeId::of::() + } + } +} + +/// Create the node's name method. +fn create_node_name_method(node: &Node) -> syn::ImplItemMethod { + let name = node.self_name.trim_end_matches("Node").to_lowercase(); + parse_quote! { + fn name(&self) -> &'static str { + #name + } + } +} + +/// Create the node's `construct` function. +fn create_node_construct_func(node: &Node) -> syn::ImplItemMethod { + node.construct.clone().unwrap_or_else(|| { + parse_quote! { + fn construct( + _: &mut ::typst::model::Vm, + _: &mut ::typst::model::Args, + ) -> ::typst::diag::SourceResult<::typst::model::Content> { + unimplemented!() + } + } + }) +} + +/// Create the node's `set` function. +fn create_node_set_func(node: &Node) -> syn::ImplItemMethod { + let user = node.set.as_ref().map(|method| { + let block = &method.block; + quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; } + }); + + let mut shorthands = vec![]; + let sets: Vec<_> = node + .properties + .iter() + .filter(|p| !p.skip) + .map(|property| { + let name = &property.name; + let string = name.to_string().replace('_', "-").to_lowercase(); + let value = match &property.shorthand { + Some(Shorthand::Positional) => quote! { args.named_or_find(#string)? }, + Some(Shorthand::Named(named)) => { + shorthands.push(named); + quote! { args.named(#string)?.or_else(|| #named.clone()) } + } + None => quote! { args.named(#string)? }, + }; + + quote! { styles.set_opt(Self::#name, #value); } + }) + .collect(); + + shorthands.sort(); + shorthands.dedup_by_key(|ident| ident.to_string()); + + let bindings = shorthands.into_iter().map(|ident| { + let string = ident.to_string(); + quote! { let #ident = args.named(#string)?; } + }); + + parse_quote! { + fn set( + args: &mut ::typst::model::Args, + constructor: bool, + ) -> ::typst::diag::SourceResult<::typst::model::StyleMap> { + let mut styles = ::typst::model::StyleMap::new(); + #user + #(#bindings)* + #(#sets)* + Ok(styles) + } + } +} + +/// Create the node's `field` method. +fn create_node_field_method(node: &Node) -> syn::ImplItemMethod { + node.field.clone().unwrap_or_else(|| { + parse_quote! { + fn field( + &self, + _: &str, + ) -> ::std::option::Option<::typst::model::Value> { + None + } + } + }) +} + +/// Create the node's capability accessor method. +fn create_node_vtable_method(node: &Node) -> syn::ImplItemMethod { + let checks = node.capabilities.iter().map(|capability| { + quote! { + if id == ::std::any::TypeId::of::() { + return Some(unsafe { + ::typst::util::fat::vtable(self as &dyn #capability) + }); + } + } + }); + + parse_quote! { + fn vtable(&self, id: ::std::any::TypeId) -> ::std::option::Option<*const ()> { + #(#checks)* + None + } + } +} + +/// Process a single const item. +fn create_property_module(node: &Node, property: &Property) -> (syn::Type, syn::ItemMod) { + let params = &node.params; + let self_args = &node.self_args; + let name = &property.name; + let value_ty = &property.value_ty; + let output_ty = &property.output_ty; + + let key = parse_quote! { Key<#value_ty, #self_args> }; + let phantom_args = self_args.iter().filter(|arg| match arg { + syn::GenericArgument::Type(syn::Type::Path(path)) => { + node.params.iter().all(|param| match param { + syn::GenericParam::Const(c) => !path.path.is_ident(&c.ident), + _ => true, + }) + } + _ => true, + }); + + let name_const = create_property_name_const(node, property); + let node_func = create_property_node_func(node); + let get_method = create_property_get_method(property); + let copy_assertion = create_property_copy_assertion(property); + + // Generate the contents of the module. + let scope = quote! { + use super::*; + + pub struct Key<__T, #params>( + pub ::std::marker::PhantomData<(__T, #(#phantom_args,)*)> + ); + + impl<#params> ::std::marker::Copy for #key {} + impl<#params> ::std::clone::Clone for #key { + fn clone(&self) -> Self { *self } + } + + impl<'a, #params> ::typst::model::Key<'a> for #key { + type Value = #value_ty; + type Output = #output_ty; + #name_const + #node_func + #get_method + } + + #copy_assertion + }; + + // Generate the module code. + let module = parse_quote! { + #[allow(non_snake_case)] + mod #name { #scope } + }; + + (key, module) +} + +/// Create the property's node method. +fn create_property_name_const(node: &Node, property: &Property) -> syn::ImplItemConst { + // The display name, e.g. `TextNode::BOLD`. + let name = format!("{}::{}", node.self_name, &property.name); + parse_quote! { + const NAME: &'static str = #name; + } +} + +/// Create the property's node method. +fn create_property_node_func(node: &Node) -> syn::ImplItemMethod { + let self_ty = &node.self_ty; + parse_quote! { + fn node() -> ::typst::model::NodeId { + ::typst::model::NodeId::of::<#self_ty>() + } + } +} + +/// Create the property's get method. +fn create_property_get_method(property: &Property) -> syn::ImplItemMethod { + let default = &property.default; + let value_ty = &property.value_ty; + + let value = if property.referenced { + quote! { + values.next().unwrap_or_else(|| { + static LAZY: ::typst::model::once_cell::sync::Lazy<#value_ty> + = ::typst::model::once_cell::sync::Lazy::new(|| #default); + &*LAZY + }) + } + } else if property.resolve && property.fold { + quote! { + match values.next().cloned() { + Some(value) => ::typst::model::Fold::fold( + ::typst::model::Resolve::resolve(value, chain), + Self::get(chain, values), + ), + None => #default, + } + } + } else if property.resolve { + quote! { + let value = values.next().cloned().unwrap_or_else(|| #default); + ::typst::model::Resolve::resolve(value, chain) + } + } else if property.fold { + quote! { + match values.next().cloned() { + Some(value) => ::typst::model::Fold::fold(value, Self::get(chain, values)), + None => #default, + } + } + } else { + quote! { + values.next().copied().unwrap_or(#default) + } + }; + + parse_quote! { + fn get( + chain: ::typst::model::StyleChain<'a>, + mut values: impl ::std::iter::Iterator, + ) -> Self::Output { + #value + } + } +} + +/// Create the assertion if the property's value must be copyable. +fn create_property_copy_assertion(property: &Property) -> Option { + let value_ty = &property.value_ty; + let must_be_copy = !property.fold && !property.resolve && !property.referenced; + must_be_copy.then(|| { + quote_spanned! { value_ty.span() => + const _: fn() -> () = || { + fn must_be_copy_fold_resolve_or_referenced() {} + must_be_copy_fold_resolve_or_referenced::<#value_ty>(); + }; + } + }) +} diff --git a/src/model/content.rs b/src/model/content.rs index 210b8bdef..c28082c2f 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -281,6 +281,12 @@ pub trait Node: 'static { Content(Arc::new(self), vec![], None) } + /// A unique identifier of the node type. + fn id(&self) -> NodeId; + + /// The node's name. + fn name(&self) -> &'static str; + /// Construct a node from the arguments. /// /// This is passed only the arguments that remain after execution of the @@ -300,12 +306,6 @@ pub trait Node: 'static { /// Access a field on this node. fn field(&self, name: &str) -> Option; - /// A unique identifier of the node type. - fn id(&self) -> NodeId; - - /// The node's name. - fn name(&self) -> &'static str; - /// Extract the pointer of the vtable of the trait object with the /// given type `id` if this node implements that trait. fn vtable(&self, id: TypeId) -> Option<*const ()>; diff --git a/src/model/mod.rs b/src/model/mod.rs index 20bca106d..0f946a400 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -23,6 +23,8 @@ mod ops; mod scope; mod vm; +#[doc(hidden)] +pub use once_cell; pub use typst_macros::{capability, node}; pub use self::args::*;