diff options
Diffstat (limited to 'macros/src/node.rs')
| -rw-r--r-- | macros/src/node.rs | 471 |
1 files changed, 213 insertions, 258 deletions
diff --git a/macros/src/node.rs b/macros/src/node.rs index a7298841..739cc79d 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -12,48 +12,64 @@ struct Node { ident: Ident, name: String, capable: Vec<Ident>, - set: Option<syn::Block>, fields: Vec<Field>, } +impl Node { + fn inherent(&self) -> impl Iterator<Item = &Field> { + self.fields.iter().filter(|field| field.inherent()) + } + + fn settable(&self) -> impl Iterator<Item = &Field> { + self.fields.iter().filter(|field| field.settable()) + } +} + struct Field { attrs: Vec<syn::Attribute>, vis: syn::Visibility, + + name: String, ident: Ident, ident_in: Ident, with_ident: Ident, set_ident: Ident, - name: String, + internal: bool, positional: bool, required: bool, variadic: bool, - - named: bool, - shorthand: Option<Shorthand>, - - settable: bool, fold: bool, resolve: bool, - skip: bool, + parse: Option<FieldParser>, ty: syn::Type, output: syn::Type, - default: Option<syn::Expr>, + default: syn::Expr, } -enum Shorthand { - Positional, - Named(Ident), -} +impl Field { + fn inherent(&self) -> bool { + self.required || self.variadic + } -impl Node { - fn inherent(&self) -> impl Iterator<Item = &Field> + Clone { - self.fields.iter().filter(|field| !field.settable) + fn settable(&self) -> bool { + !self.inherent() } +} + +struct FieldParser { + prefix: Vec<syn::Stmt>, + expr: syn::Stmt, +} - fn settable(&self) -> impl Iterator<Item = &Field> + Clone { - self.fields.iter().filter(|field| field.settable) +impl Parse for FieldParser { + fn parse(input: ParseStream) -> Result<Self> { + let mut stmts = syn::Block::parse_within(input)?; + let Some(expr) = stmts.pop() else { + return Err(input.error("expected at least on expression")); + }; + Ok(Self { prefix: stmts, expr }) } } @@ -70,34 +86,30 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> { }; let mut attrs = field.attrs.clone(); + let variadic = has_attr(&mut attrs, "variadic"); + let mut field = Field { vis: field.vis.clone(), + + name: kebab_case(&ident), ident: ident.clone(), ident_in: Ident::new(&format!("{}_in", ident), ident.span()), with_ident: Ident::new(&format!("with_{}", ident), ident.span()), set_ident: Ident::new(&format!("set_{}", ident), ident.span()), - name: kebab_case(&ident), - - positional: has_attr(&mut attrs, "positional"), - required: has_attr(&mut attrs, "required"), - variadic: has_attr(&mut attrs, "variadic"), - - named: has_attr(&mut attrs, "named"), - shorthand: parse_attr(&mut attrs, "shorthand")?.map(|v| match v { - None => Shorthand::Positional, - Some(ident) => Shorthand::Named(ident), - }), - settable: has_attr(&mut attrs, "settable"), + internal: has_attr(&mut attrs, "internal"), + positional: has_attr(&mut attrs, "positional") || variadic, + required: has_attr(&mut attrs, "required") || variadic, + variadic, fold: has_attr(&mut attrs, "fold"), resolve: has_attr(&mut attrs, "resolve"), - skip: has_attr(&mut attrs, "skip"), + parse: parse_attr(&mut attrs, "parse")?.flatten(), ty: field.ty.clone(), output: field.ty.clone(), - default: parse_attr(&mut attrs, "default")?.map(|opt| { - opt.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }) - }), + default: parse_attr(&mut attrs, "default")? + .flatten() + .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }), attrs: { validate_attrs(&attrs)?; @@ -105,6 +117,14 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> { }, }; + 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 }; @@ -114,14 +134,6 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> { field.output = parse_quote! { <#output as ::typst::model::Fold>::Output }; } - if !field.positional && !field.named && !field.variadic && !field.settable { - bail!(ident, "expected positional, named, variadic, or settable"); - } - - if !field.required && !field.variadic && field.default.is_none() { - bail!(ident, "non-required fields must have a default value"); - } - fields.push(field); } @@ -130,14 +142,13 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> { .into_iter() .collect(); - let mut attrs = body.attrs.clone(); + let attrs = body.attrs.clone(); Ok(Node { vis: body.vis.clone(), ident: body.ident.clone(), name: body.ident.to_string().trim_end_matches("Node").to_lowercase(), capable, fields, - set: parse_attr(&mut attrs, "set")?.flatten(), attrs: { validate_attrs(&attrs)?; attrs @@ -153,10 +164,10 @@ fn create(node: &Node) -> TokenStream { // Inherent methods and functions. let new = create_new_func(node); - let field_methods = node.inherent().map(create_field_method); - let with_fields_methods = node.inherent().map(create_with_field_method); + let field_methods = node.fields.iter().map(create_field_method); let field_in_methods = node.settable().map(create_field_in_method); - let field_style_methods = node.settable().map(create_field_style_method); + let with_fields_methods = node.fields.iter().map(create_with_field_method); + let field_style_methods = node.settable().map(create_set_field_method); // Trait implementations. let construct = node @@ -177,8 +188,8 @@ fn create(node: &Node) -> TokenStream { impl #ident { #new #(#field_methods)* - #(#with_fields_methods)* #(#field_in_methods)* + #(#with_fields_methods)* #(#field_style_methods)* /// The node's span. @@ -201,121 +212,90 @@ fn create(node: &Node) -> TokenStream { /// Create the `new` function for the node. fn create_new_func(node: &Node) -> TokenStream { - let relevant = node.inherent().filter(|field| field.required || field.variadic); - let params = relevant.clone().map(|field| { - let ident = &field.ident; - let ty = &field.ty; + let params = node.inherent().map(|Field { ident, ty, .. }| { quote! { #ident: #ty } }); - let pushes = relevant.map(|field| { - let ident = &field.ident; - let with_ident = &field.with_ident; + let builder_calls = node.inherent().map(|Field { ident, with_ident, .. }| { quote! { .#with_ident(#ident) } }); - let defaults = node - .inherent() - .filter_map(|field| field.default.as_ref().map(|default| (field, default))) - .map(|(field, default)| { - let with_ident = &field.with_ident; - quote! { .#with_ident(#default) } - }); quote! { /// Create a new node. pub fn new(#(#params),*) -> Self { Self(::typst::model::Content::new::<Self>()) - #(#pushes)* - #(#defaults)* + #(#builder_calls)* } } } /// Create an accessor methods for a field. fn create_field_method(field: &Field) -> TokenStream { - let Field { attrs, vis, ident, name, ty, .. } = field; - quote! { - #(#attrs)* - #vis fn #ident(&self) -> #ty { - self.0.cast_field(#name) + let Field { attrs, vis, ident, name, output, .. } = field; + if field.inherent() { + quote! { + #(#attrs)* + #vis fn #ident(&self) -> #output { + self.0.cast_required_field(#name) + } + } + } else { + let access = + create_style_chain_access(field, quote! { self.0.field(#name).cloned() }); + quote! { + #(#attrs)* + #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { + #access + } } } } -/// 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); +/// 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 #with_ident(mut self, #ident: #ty) -> Self { - Self(self.0.with_field(#name, #ident)) + #vis fn #ident_in(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, ty, output, default, .. } = field; - - let doc = format!("Access the `{}` field in the given style chain.", name); - let args = quote! { ::typst::model::NodeId::of::<Self>(), #name }; - - let body = if field.fold && field.resolve { - quote! { - fn next( - mut values: impl ::std::iter::Iterator<Item = #ty>, - styles: ::typst::model::StyleChain, - ) -> #output { - values - .next() - .map(|value| { - ::typst::model::Fold::fold( - ::typst::model::Resolve::resolve(value, styles), - next(values, styles), - ) - }) - .unwrap_or_else(|| #default) - } - next(styles.properties(#args), styles) - } - } else if field.fold { - quote! { - fn next( - mut values: impl ::std::iter::Iterator<Item = #ty>, - styles: ::typst::model::StyleChain, - ) -> #output { - values - .next() - .map(|value| { - ::typst::model::Fold::fold(value, next(values, styles)) - }) - .unwrap_or_else(|| #default) - } - next(styles.properties(#args), styles) - } - } else if field.resolve { - quote! { - ::typst::model::Resolve::resolve( - styles.property::<#ty>(#args).unwrap_or_else(|| #default), - styles - ) - } - } else { - quote! { - styles.property(#args).unwrap_or_else(|| #default) - } +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>( + ::typst::model::NodeId::of::<Self>(), + #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] - #[allow(unused)] - #vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output { - #body + #vis fn #with_ident(mut self, #ident: #ty) -> Self { + Self(self.0.with_field(#name, #ident)) } } } -/// Create a style creation method for a field. -fn create_field_style_method(field: &Field) -> TokenStream { +/// 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! { @@ -335,8 +315,17 @@ fn create_node_impl(node: &Node) -> TokenStream { let ident = &node.ident; let name = &node.name; let vtable_func = create_vtable_func(node); + let infos = node + .fields + .iter() + .filter(|field| !field.internal) + .map(create_param_info); quote! { impl ::typst::model::Node for #ident { + fn pack(self) -> ::typst::model::Content { + self.0 + } + fn id() -> ::typst::model::NodeId { static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta { name: #name, @@ -345,179 +334,145 @@ fn create_node_impl(node: &Node) -> TokenStream { ::typst::model::NodeId::from_meta(&META) } - fn pack(self) -> ::typst::model::Content { - self.0 + fn params() -> ::std::vec::Vec<::typst::eval::ParamInfo> { + ::std::vec![#(#infos),*] } } } } -/// Create the node's metadata vtable. +/// Create the node's casting vtable. fn create_vtable_func(node: &Node) -> TokenStream { let ident = &node.ident; - let checks = - node.capable - .iter() - .filter(|&ident| ident != "Construct") - .map(|capability| { - quote! { - if id == ::std::any::TypeId::of::<dyn #capability>() { - return Some(unsafe { - ::typst::util::fat::vtable(& - Self(::typst::model::Content::new::<#ident>()) as &dyn #capability - ) - }); - } - } - }); + let relevant = node.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>()); #(#checks)* None } } } +/// Create a parameter info for a field. +fn create_param_info(field: &Field) -> TokenStream { + let Field { name, positional, variadic, required, ty, .. } = field; + let named = !positional; + let settable = field.settable(); + let docs = documentation(&field.attrs); + let docs = docs.trim(); + quote! { + ::typst::eval::ParamInfo { + name: #name, + docs: #docs, + cast: <#ty as ::typst::eval::Cast< + ::typst::syntax::Spanned<::typst::eval::Value> + >>::describe(), + positional: #positional, + named: #named, + variadic: #variadic, + required: #required, + settable: #settable, + } + } +} + /// Create the node's `Construct` implementation. fn create_construct_impl(node: &Node) -> TokenStream { let ident = &node.ident; - let shorthands = create_construct_shorthands(node); - let builders = node.inherent().map(create_construct_builder_call); + let handlers = node + .fields + .iter() + .filter(|field| !field.internal || field.parse.is_some()) + .map(|field| { + let with_ident = &field.with_ident; + let (prefix, value) = create_field_parser(field); + if field.settable() { + quote! { + #prefix + if let Some(value) = #value { + node = node.#with_ident(value); + } + } + } else { + quote! { + #prefix + node = node.#with_ident(#value); + } + } + }); + quote! { impl ::typst::model::Construct for #ident { fn construct( - _: &::typst::eval::Vm, + vm: &::typst::eval::Vm, args: &mut ::typst::eval::Args, ) -> ::typst::diag::SourceResult<::typst::model::Content> { - #(#shorthands)* - Ok(::typst::model::Node::pack( - Self(::typst::model::Content::new::<Self>()) - #(#builders)*)) + let mut node = Self(::typst::model::Content::new::<Self>()); + #(#handlers)* + Ok(node.0) } } } } -/// Create let bindings for shorthands in the constructor. -fn create_construct_shorthands(node: &Node) -> impl Iterator<Item = TokenStream> + '_ { - let mut shorthands = vec![]; - for field in node.inherent() { - if let Some(Shorthand::Named(named)) = &field.shorthand { - shorthands.push(named); - } - } - - shorthands.sort(); - shorthands.dedup_by_key(|ident| ident.to_string()); - shorthands.into_iter().map(|ident| { - let string = ident.to_string(); - quote! { let #ident = args.named(#string)?; } - }) -} - -/// Create a builder call for the constructor. -fn create_construct_builder_call(field: &Field) -> TokenStream { - let name = &field.name; - let with_ident = &field.with_ident; - - let mut value = if field.variadic { - quote! { args.all()? } - } else if field.required { - quote! { args.expect(#name)? } - } else if let Some(shorthand) = &field.shorthand { - match shorthand { - Shorthand::Positional => quote! { args.named_or_find(#name)? }, - Shorthand::Named(named) => { - quote! { args.named(#name)?.or_else(|| #named.clone()) } - } - } - } else if field.named { - quote! { args.named(#name)? } - } else { - quote! { args.find()? } - }; - - if let Some(default) = &field.default { - value = quote! { #value.unwrap_or(#default) }; - } - - quote! { .#with_ident(#value) } -} - /// Create the node's `Set` implementation. fn create_set_impl(node: &Node) -> TokenStream { let ident = &node.ident; - let custom = node.set.as_ref().map(|block| { - quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; } - }); - - let mut shorthands = vec![]; - let sets: Vec<_> = node - .settable() - .filter(|field| !field.skip) + let handlers = node + .fields + .iter() + .filter(|field| field.settable() && (!field.internal || field.parse.is_some())) .map(|field| { - let name = &field.name; let set_ident = &field.set_ident; - let value = match &field.shorthand { - Some(Shorthand::Positional) => quote! { args.named_or_find(#name)? }, - Some(Shorthand::Named(named)) => { - shorthands.push(named); - quote! { args.named(#name)?.or_else(|| #named.clone()) } + let (prefix, value) = create_field_parser(field); + quote! { + #prefix + if let Some(value) = #value { + styles.set(Self::#set_ident(value)); } - None => quote! { args.named(#name)? }, - }; - - quote! { styles.set_opt(#value.map(Self::#set_ident)); } - }) - .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)?; } - }); - - let infos = node.fields.iter().filter(|p| !p.skip).map(|field| { - let name = &field.name; - let value_ty = &field.ty; - let shorthand = matches!(field.shorthand, Some(Shorthand::Positional)); - let docs = documentation(&field.attrs); - let docs = docs.trim(); - quote! { - ::typst::eval::ParamInfo { - name: #name, - docs: #docs, - cast: <#value_ty as ::typst::eval::Cast< - ::typst::syntax::Spanned<::typst::eval::Value> - >>::describe(), - named: true, - positional: #shorthand, - required: false, - variadic: false, - settable: true, } - } - }); + }); quote! { impl ::typst::model::Set for #ident { fn set( args: &mut ::typst::eval::Args, - constructor: bool, ) -> ::typst::diag::SourceResult<::typst::model::StyleMap> { let mut styles = ::typst::model::StyleMap::new(); - #custom - #(#bindings)* - #(#sets)* + #(#handlers)* Ok(styles) } - - fn properties() -> ::std::vec::Vec<::typst::eval::ParamInfo> { - ::std::vec![#(#infos),*] - } } } } + +/// Create argument parsing code for a field. +fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { + let name = &field.name; + if let Some(FieldParser { prefix, expr }) = &field.parse { + return (quote! { #(#prefix);* }, quote! { #expr }); + } + + 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) +} |
