diff options
Diffstat (limited to 'macros/src')
| -rw-r--r-- | macros/src/func.rs | 312 | ||||
| -rw-r--r-- | macros/src/lib.rs | 5 | ||||
| -rw-r--r-- | macros/src/node.rs | 162 | ||||
| -rw-r--r-- | macros/src/util.rs | 16 |
4 files changed, 253 insertions, 242 deletions
diff --git a/macros/src/func.rs b/macros/src/func.rs index 843c193e..87324120 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -1,183 +1,183 @@ +use quote::ToTokens; + use super::*; /// Expand the `#[func]` macro. -pub fn func(mut item: syn::Item) -> Result<TokenStream> { - let attrs = match &mut item { - syn::Item::Struct(item) => &mut item.attrs, - syn::Item::Fn(item) => &mut item.attrs, - _ => bail!(item, "expected struct or fn"), - }; +pub fn func(item: syn::ItemFn) -> Result<TokenStream> { + let func = prepare(&item)?; + Ok(create(&func)) +} - let docs = documentation(&attrs); +struct Func { + name: String, + display: String, + category: String, + docs: String, + vis: syn::Visibility, + ident: Ident, + params: Vec<Param>, + returns: Vec<String>, + body: syn::Block, +} - let mut lines: Vec<_> = docs.lines().collect(); - let Some(category) = lines.pop().and_then(|s| s.strip_prefix("Category: ")) else { - bail!(item, "expected category"); - }; - let Some(display) = lines.pop().and_then(|s| s.strip_prefix("Display: ")) else { - bail!(item, "expected display name"); - }; +struct Param { + name: String, + docs: String, + external: bool, + named: bool, + variadic: bool, + default: Option<syn::Expr>, + ident: Ident, + ty: syn::Type, +} - let mut docs = lines.join("\n"); - let (params, returns) = params(&mut docs)?; - let docs = docs.trim(); - attrs.retain(|attr| !attr.path.is_ident("doc")); - attrs.push(parse_quote! { #[doc = #docs] }); - - let info = quote! { - ::typst::eval::FuncInfo { - name, - display: #display, - category: #category, - docs: #docs, - params: ::std::vec![#(#params),*], - returns: ::std::vec![#(#returns),*] - } - }; +fn prepare(item: &syn::ItemFn) -> Result<Func> { + let sig = &item.sig; - if let syn::Item::Fn(item) = &item { - let vis = &item.vis; - let ident = &item.sig.ident; - let s = ident.to_string(); - let mut chars = s.trim_end_matches('_').chars(); - let ty = quote::format_ident!( - "{}{}Func", - chars.next().unwrap().to_ascii_uppercase(), - chars.as_str() - ); - - let full = if item.sig.inputs.len() == 1 { - quote! { |_, args| #ident(args) } - } else { - quote! { #ident } + let mut params = vec![]; + for input in &sig.inputs { + let syn::FnArg::Typed(typed) = input else { + bail!(input, "self is not allowed here"); }; - Ok(quote! { - #item - - #[doc(hidden)] - #vis enum #ty {} - - impl::typst::eval::FuncType for #ty { - fn create_func(name: &'static str) -> ::typst::eval::Func { - ::typst::eval::Func::from_fn(#full, #info) - } - } - }) - } else { - let (ident, generics) = match &item { - syn::Item::Struct(s) => (&s.ident, &s.generics), - syn::Item::Enum(s) => (&s.ident, &s.generics), - _ => bail!(item, "only structs, enums, and functions are supported"), + let syn::Pat::Ident(syn::PatIdent { + by_ref: None, + mutability: None, + ident, + .. + }) = &*typed.pat else { + bail!(typed.pat, "expected identifier"); }; - let (params, args, clause) = generics.split_for_impl(); + if sig.output.to_token_stream().to_string() != "-> Value" { + bail!(sig.output, "must return `Value`"); + } - Ok(quote! { - #item + let mut attrs = typed.attrs.clone(); + params.push(Param { + name: kebab_case(ident), + docs: documentation(&attrs), + external: has_attr(&mut attrs, "external"), + named: has_attr(&mut attrs, "named"), + variadic: has_attr(&mut attrs, "variadic"), + default: parse_attr(&mut attrs, "default")?.map(|expr| { + expr.unwrap_or_else( + || parse_quote! { ::std::default::Default::default() }, + ) + }), + ident: ident.clone(), + ty: (*typed.ty).clone(), + }); - impl #params ::typst::eval::FuncType for #ident #args #clause { - fn create_func(name: &'static str) -> ::typst::eval::Func { - ::typst::eval::Func::from_node::<Self>(#info) - } - } - }) + validate_attrs(&attrs)?; } -} - -/// Extract a section. -fn section(docs: &mut String, title: &str, level: usize) -> Option<String> { - let hashtags = "#".repeat(level); - let needle = format!("\n{hashtags} {title}\n"); - let start = docs.find(&needle)?; - let rest = &docs[start..]; - let len = rest[1..] - .find("\n# ") - .or_else(|| rest[1..].find("\n## ")) - .or_else(|| rest[1..].find("\n### ")) - .map(|x| 1 + x) - .unwrap_or(rest.len()); - let end = start + len; - let section = docs[start + needle.len()..end].trim().to_owned(); - docs.replace_range(start..end, ""); - Some(section) -} -/// Parse the parameter section. -fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> { - let Some(section) = section(docs, "Parameters", 2) else { - return Ok((vec![], vec![])); + let docs = documentation(&item.attrs); + let mut lines = docs.split("\n").collect(); + let returns = meta_line(&mut lines, "Returns")? + .split(" or ") + .map(Into::into) + .collect(); + let category = meta_line(&mut lines, "Category")?.into(); + let display = meta_line(&mut lines, "Display")?.into(); + let docs = lines.join("\n").trim().into(); + + let func = Func { + name: sig.ident.to_string().replace('_', ""), + display, + category, + docs, + vis: item.vis.clone(), + ident: sig.ident.clone(), + params, + returns, + body: (*item.block).clone(), }; - let mut s = Scanner::new(§ion); - let mut infos = vec![]; - let mut returns = vec![]; - - while s.eat_if('-') { - let mut named = false; - let mut positional = false; - let mut required = false; - let mut variadic = false; - let mut settable = false; - - s.eat_whitespace(); - let name = s.eat_until(':'); - s.expect(": "); - - if name == "returns" { - returns = s - .eat_until('\n') - .split(" or ") - .map(str::trim) - .map(Into::into) - .collect(); - s.eat_whitespace(); - continue; - } + validate_attrs(&item.attrs)?; + Ok(func) +} - s.expect('`'); - let ty: syn::Type = syn::parse_str(s.eat_until('`'))?; - s.expect('`'); - s.eat_whitespace(); - s.expect('('); - - for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) { - match part { - "positional" => positional = true, - "named" => named = true, - "required" => required = true, - "variadic" => variadic = true, - "settable" => settable = true, - _ => bail!(callsite, "unknown parameter flag {:?}", part), +fn create(func: &Func) -> TokenStream { + let Func { + name, + display, + category, + docs, + vis, + ident, + params, + returns, + body, + .. + } = func; + let handlers = params.iter().filter(|param| !param.external).map(create_param_parser); + let params = params.iter().map(create_param_info); + quote! { + #[doc = #docs] + #vis fn #ident() -> ::typst::eval::NativeFunc { + ::typst::eval::NativeFunc { + func: |vm, args| { + #(#handlers)* + #[allow(unreachable_code)] + Ok(#body) + }, + info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo { + name: #name, + display: #display, + docs: #docs, + params: ::std::vec![#(#params),*], + returns: ::std::vec![#(#returns),*], + category: #category, + }), } } + } +} - if (!named && !positional) || (variadic && !positional) { - bail!(callsite, "invalid combination of parameter flags"); +/// Create a parameter info for a field. +fn create_param_info(param: &Param) -> TokenStream { + let Param { name, docs, named, variadic, ty, default, .. } = param; + let positional = !named; + let required = default.is_none(); + 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::Cast< + ::typst::syntax::Spanned<::typst::eval::Value> + >>::describe(), + positional: #positional, + named: #named, + variadic: #variadic, + required: #required, + settable: false, } + } +} - s.expect(')'); - - let docs = dedent(s.eat_until("\n-").trim()); - let docs = docs.trim(); - - infos.push(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 argument parsing code for a parameter. +fn create_param_parser(param: &Param) -> TokenStream { + let Param { name, ident, ty, .. } = param; + + let mut value = if param.variadic { + quote! { args.all()? } + } else if param.named { + quote! { args.named(#name)? } + } else if param.default.is_some() { + quote! { args.eat()? } + } else { + quote! { args.expect(#name)? } + }; - s.eat_whitespace(); + if let Some(default) = ¶m.default { + value = quote! { #value.unwrap_or_else(|| #default) } } - Ok((infos, returns)) + quote! { let #ident: #ty = #value; } } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c1a8b2ae..889eaa7b 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -16,14 +16,13 @@ use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream, Parser}; use syn::punctuated::Punctuated; use syn::{parse_quote, Ident, Result, Token}; -use unscanny::Scanner; use self::util::*; -/// Implement `FuncType` for a type or function. +/// Turns a function into a `NativeFunc`. #[proc_macro_attribute] pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { - let item = syn::parse_macro_input!(item as syn::Item); + let item = syn::parse_macro_input!(item as syn::ItemFn); func::func(item).unwrap_or_else(|err| err.to_compile_error()).into() } diff --git a/macros/src/node.rs b/macros/src/node.rs index 739cc79d..92faf7dd 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -7,45 +7,35 @@ pub fn node(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> { } struct Node { - attrs: Vec<syn::Attribute>, + name: String, + display: String, + category: String, + docs: String, vis: syn::Visibility, ident: Ident, - name: String, capable: Vec<Ident>, 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, - + docs: String, internal: bool, + external: bool, positional: bool, required: bool, variadic: bool, fold: bool, resolve: bool, parse: Option<FieldParser>, - + default: syn::Expr, + vis: syn::Visibility, + ident: Ident, + ident_in: Ident, + with_ident: Ident, + set_ident: Ident, ty: syn::Type, output: syn::Type, - default: syn::Expr, } impl Field { @@ -87,34 +77,30 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> { 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; 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()), - + docs: documentation(&attrs), internal: has_attr(&mut attrs, "internal"), - positional: has_attr(&mut attrs, "positional") || variadic, - required: has_attr(&mut attrs, "required") || variadic, + external: has_attr(&mut attrs, "external"), + positional, + required, variadic, fold: has_attr(&mut attrs, "fold"), resolve: has_attr(&mut attrs, "resolve"), parse: parse_attr(&mut attrs, "parse")?.flatten(), - - ty: field.ty.clone(), - output: field.ty.clone(), default: parse_attr(&mut attrs, "default")? .flatten() .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }), - - attrs: { - validate_attrs(&attrs)?; - attrs - }, + 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()), + set_ident: Ident::new(&format!("set_{}", ident), ident.span()), + ty: field.ty.clone(), + output: field.ty.clone(), }; if field.required && (field.fold || field.resolve) { @@ -134,6 +120,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> { field.output = parse_quote! { <#output as ::typst::model::Fold>::Output }; } + validate_attrs(&attrs)?; fields.push(field); } @@ -142,32 +129,39 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> { .into_iter() .collect(); - let attrs = body.attrs.clone(); - Ok(Node { + let docs = documentation(&body.attrs); + let mut lines = docs.split("\n").collect(); + let category = meta_line(&mut lines, "Category")?.into(); + let display = meta_line(&mut lines, "Display")?.into(); + let docs = lines.join("\n").trim().into(); + + let node = Node { + name: body.ident.to_string().trim_end_matches("Node").to_lowercase(), + display, + category, + docs, vis: body.vis.clone(), ident: body.ident.clone(), - name: body.ident.to_string().trim_end_matches("Node").to_lowercase(), capable, fields, - attrs: { - validate_attrs(&attrs)?; - attrs - }, - }) + }; + + validate_attrs(&body.attrs)?; + Ok(node) } /// Produce the node's definition. fn create(node: &Node) -> TokenStream { - let attrs = &node.attrs; - let vis = &node.vis; - let ident = &node.ident; + let Node { vis, ident, docs, .. } = node; + let all = node.fields.iter().filter(|field| !field.external); + let settable = all.clone().filter(|field| field.settable()); // Inherent methods and functions. let new = create_new_func(node); - let field_methods = node.fields.iter().map(create_field_method); - let field_in_methods = node.settable().map(create_field_in_method); - let with_fields_methods = node.fields.iter().map(create_with_field_method); - let field_style_methods = node.settable().map(create_set_field_method); + let field_methods = all.clone().map(create_field_method); + let field_in_methods = settable.clone().map(create_field_in_method); + let with_fields_methods = all.map(create_with_field_method); + let field_style_methods = settable.map(create_set_field_method); // Trait implementations. let construct = node @@ -179,8 +173,7 @@ fn create(node: &Node) -> TokenStream { let node = create_node_impl(node); quote! { - #(#attrs)* - #[::typst::eval::func] + #[doc = #docs] #[derive(Debug, Clone, Hash)] #[repr(transparent)] #vis struct #ident(::typst::model::Content); @@ -212,10 +205,11 @@ fn create(node: &Node) -> TokenStream { /// Create the `new` function for the node. fn create_new_func(node: &Node) -> TokenStream { - let params = node.inherent().map(|Field { ident, ty, .. }| { + let relevant = node.fields.iter().filter(|field| !field.external && field.inherent()); + let params = relevant.clone().map(|Field { ident, ty, .. }| { quote! { #ident: #ty } }); - let builder_calls = node.inherent().map(|Field { ident, with_ident, .. }| { + let builder_calls = relevant.map(|Field { ident, with_ident, .. }| { quote! { .#with_ident(#ident) } }); quote! { @@ -229,10 +223,10 @@ fn create_new_func(node: &Node) -> TokenStream { /// Create an accessor methods for a field. fn create_field_method(field: &Field) -> TokenStream { - let Field { attrs, vis, ident, name, output, .. } = field; + let Field { vis, docs, ident, name, output, .. } = field; if field.inherent() { quote! { - #(#attrs)* + #[doc = #docs] #vis fn #ident(&self) -> #output { self.0.cast_required_field(#name) } @@ -241,7 +235,7 @@ fn create_field_method(field: &Field) -> TokenStream { let access = create_style_chain_access(field, quote! { self.0.field(#name).cloned() }); quote! { - #(#attrs)* + #[doc = #docs] #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { #access } @@ -312,8 +306,7 @@ fn create_set_field_method(field: &Field) -> TokenStream { /// Create the node's `Node` implementation. fn create_node_impl(node: &Node) -> TokenStream { - let ident = &node.ident; - let name = &node.name; + let Node { ident, name, display, category, docs, .. } = node; let vtable_func = create_vtable_func(node); let infos = node .fields @@ -322,10 +315,6 @@ fn create_node_impl(node: &Node) -> TokenStream { .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, @@ -334,8 +323,24 @@ fn create_node_impl(node: &Node) -> TokenStream { ::typst::model::NodeId::from_meta(&META) } - fn params() -> ::std::vec::Vec<::typst::eval::ParamInfo> { - ::std::vec![#(#infos),*] + fn pack(self) -> ::typst::model::Content { + self.0 + } + + fn func() -> ::typst::eval::NodeFunc { + ::typst::eval::NodeFunc { + id: Self::id(), + construct: <Self as ::typst::model::Construct>::construct, + set: <Self as ::typst::model::Set>::set, + info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo { + name: #name, + display: #display, + docs: #docs, + params: ::std::vec![#(#infos),*], + returns: ::std::vec!["content"], + category: #category, + }), + } } } } @@ -366,11 +371,14 @@ fn create_vtable_func(node: &Node) -> TokenStream { /// Create a parameter info for a field. fn create_param_info(field: &Field) -> TokenStream { - let Field { name, positional, variadic, required, ty, .. } = field; + let Field { name, docs, positional, variadic, required, ty, .. } = field; let named = !positional; let settable = field.settable(); - let docs = documentation(&field.attrs); - let docs = docs.trim(); + let ty = if *variadic { + quote! { <#ty as ::typst::eval::Variadics>::Inner } + } else { + quote! { #ty } + }; quote! { ::typst::eval::ParamInfo { name: #name, @@ -393,7 +401,7 @@ fn create_construct_impl(node: &Node) -> TokenStream { let handlers = node .fields .iter() - .filter(|field| !field.internal || field.parse.is_some()) + .filter(|field| !field.external && (!field.internal || field.parse.is_some())) .map(|field| { let with_ident = &field.with_ident; let (prefix, value) = create_field_parser(field); @@ -432,7 +440,11 @@ fn create_set_impl(node: &Node) -> TokenStream { let handlers = node .fields .iter() - .filter(|field| field.settable() && (!field.internal || field.parse.is_some())) + .filter(|field| { + !field.external + && field.settable() + && (!field.internal || field.parse.is_some()) + }) .map(|field| { let set_ident = &field.set_ident; let (prefix, value) = create_field_parser(field); @@ -459,11 +471,11 @@ fn create_set_impl(node: &Node) -> TokenStream { /// 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 name = &field.name; let value = if field.variadic { quote! { args.all()? } } else if field.required { diff --git a/macros/src/util.rs b/macros/src/util.rs index c8c56a05..d94ba932 100644 --- a/macros/src/util.rs +++ b/macros/src/util.rs @@ -58,14 +58,6 @@ pub fn kebab_case(name: &Ident) -> String { name.to_string().to_lowercase().replace('_', "-") } -/// Dedent documentation text. -pub fn dedent(text: &str) -> String { - text.lines() - .map(|s| s.strip_prefix(" ").unwrap_or(s)) - .collect::<Vec<_>>() - .join("\n") -} - /// Extract documentation comments from an attribute list. pub fn documentation(attrs: &[syn::Attribute]) -> String { let mut doc = String::new(); @@ -86,3 +78,11 @@ pub fn documentation(attrs: &[syn::Attribute]) -> String { doc.trim().into() } + +/// Extract a line of metadata from documentation. +pub fn meta_line<'a>(lines: &mut Vec<&'a str>, key: &str) -> Result<&'a str> { + match lines.pop().and_then(|line| line.strip_prefix(&format!("{key}:"))) { + Some(value) => Ok(value.trim()), + None => bail!(callsite, "missing metadata key: {}", key), + } +} |
