diff options
Diffstat (limited to 'crates/typst-macros/src/func.rs')
| -rw-r--r-- | crates/typst-macros/src/func.rs | 459 |
1 files changed, 306 insertions, 153 deletions
diff --git a/crates/typst-macros/src/func.rs b/crates/typst-macros/src/func.rs index 13a6eb2d..87d57f19 100644 --- a/crates/typst-macros/src/func.rs +++ b/crates/typst-macros/src/func.rs @@ -1,206 +1,340 @@ use super::*; +use heck::ToKebabCase; + /// Expand the `#[func]` macro. pub fn func(stream: TokenStream, item: &syn::ItemFn) -> Result<TokenStream> { - let func = prepare(stream, item)?; + let func = parse(stream, item)?; Ok(create(&func, item)) } +/// Details about a function. struct Func { name: String, - display: String, - category: String, - keywords: Option<String>, + title: String, + scope: bool, + constructor: bool, + keywords: Vec<String>, + parent: Option<syn::Type>, docs: String, vis: syn::Visibility, ident: Ident, - ident_func: Ident, - parent: Option<syn::Type>, + special: SpecialParams, + params: Vec<Param>, + returns: syn::Type, +} + +/// Special parameters provided by the runtime. +#[derive(Default)] +struct SpecialParams { + self_: Option<Param>, vm: bool, vt: bool, args: bool, span: bool, - params: Vec<Param>, - returns: syn::Type, - scope: Option<BlockWithReturn>, } +/// Details about a function parameter. struct Param { + binding: Binding, + ident: Ident, + ty: syn::Type, name: String, docs: String, - external: bool, named: bool, variadic: bool, + external: bool, default: Option<syn::Expr>, - ident: Ident, - ty: syn::Type, } -fn prepare(stream: TokenStream, item: &syn::ItemFn) -> Result<Func> { - let sig = &item.sig; +/// How a parameter is bound. +enum Binding { + /// Normal parameter. + Owned, + /// `&self`. + Ref, + /// `&mut self`. + RefMut, +} - let Parent(parent) = syn::parse2(stream)?; +/// The `..` in `#[func(..)]`. +pub struct Meta { + pub scope: bool, + pub name: Option<String>, + pub title: Option<String>, + pub constructor: bool, + pub keywords: Vec<String>, + pub parent: Option<syn::Type>, +} - let mut vm = false; - let mut vt = false; - let mut args = false; - let mut span = false; +impl Parse for Meta { + fn parse(input: ParseStream) -> Result<Self> { + Ok(Self { + scope: parse_flag::<kw::scope>(input)?, + name: parse_string::<kw::name>(input)?, + title: parse_string::<kw::title>(input)?, + constructor: parse_flag::<kw::constructor>(input)?, + keywords: parse_string_array::<kw::keywords>(input)?, + parent: parse_key_value::<kw::parent, _>(input)?, + }) + } +} + +/// Parse details about the function from the fn item. +fn parse(stream: TokenStream, item: &syn::ItemFn) -> Result<Func> { + let meta: Meta = syn::parse2(stream)?; + let (name, title) = + determine_name_and_title(meta.name, meta.title, &item.sig.ident, None)?; + + let docs = documentation(&item.attrs); + + let mut special = SpecialParams::default(); let mut params = vec![]; - for input in &sig.inputs { - let syn::FnArg::Typed(typed) = input else { - println!("option a"); - bail!(input, "self is not allowed here"); - }; - - let syn::Pat::Ident(syn::PatIdent { - by_ref: None, mutability: None, ident, .. - }) = &*typed.pat - else { - bail!(typed.pat, "expected identifier"); - }; - - match ident.to_string().as_str() { - "vm" => vm = true, - "vt" => vt = true, - "args" => args = true, - "span" => span = true, - _ => { - 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(), - }); - - validate_attrs(&attrs)?; - } - } + for input in &item.sig.inputs { + parse_param(&mut special, &mut params, meta.parent.as_ref(), input)?; } - let mut attrs = item.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 func = Func { - name: sig.ident.to_string().trim_end_matches('_').replace('_', "-"), - display, - category, - keywords, + let returns = match &item.sig.output { + syn::ReturnType::Default => parse_quote! { () }, + syn::ReturnType::Type(_, ty) => ty.as_ref().clone(), + }; + + if meta.parent.is_some() && meta.scope { + bail!(item, "scoped function cannot have a scope"); + } + + Ok(Func { + name, + title, + scope: meta.scope, + constructor: meta.constructor, + keywords: meta.keywords, + parent: meta.parent, docs, vis: item.vis.clone(), - ident: sig.ident.clone(), - ident_func: Ident::new( - &format!("{}_func", sig.ident.to_string().trim_end_matches('_')), - sig.ident.span(), - ), - parent, + ident: item.sig.ident.clone(), + special, params, - returns: match &sig.output { - syn::ReturnType::Default => parse_quote! { () }, - syn::ReturnType::Type(_, ty) => ty.as_ref().clone(), - }, - scope: parse_attr(&mut attrs, "scope")?.flatten(), - vm, - vt, - args, - span, + returns, + }) +} + +/// Parse details about a functino parameter. +fn parse_param( + special: &mut SpecialParams, + params: &mut Vec<Param>, + parent: Option<&syn::Type>, + input: &syn::FnArg, +) -> Result<()> { + let typed = match input { + syn::FnArg::Receiver(recv) => { + let mut binding = Binding::Owned; + if recv.reference.is_some() { + if recv.mutability.is_some() { + binding = Binding::RefMut + } else { + binding = Binding::Ref + } + }; + + special.self_ = Some(Param { + binding, + ident: syn::Ident::new("self_", recv.self_token.span), + ty: match parent { + Some(ty) => ty.clone(), + None => bail!(recv, "explicit parent type required"), + }, + name: "self".into(), + docs: documentation(&recv.attrs), + named: false, + variadic: false, + external: false, + default: None, + }); + return Ok(()); + } + syn::FnArg::Typed(typed) => typed, + }; + + let syn::Pat::Ident(syn::PatIdent { by_ref: None, mutability: None, ident, .. }) = + &*typed.pat + else { + bail!(typed.pat, "expected identifier"); }; - Ok(func) + match ident.to_string().as_str() { + "vm" => special.vm = true, + "vt" => special.vt = true, + "args" => special.args = true, + "span" => special.span = true, + _ => { + let mut attrs = typed.attrs.clone(); + params.push(Param { + binding: Binding::Owned, + ident: ident.clone(), + ty: (*typed.ty).clone(), + name: ident.to_string().to_kebab_case(), + docs: documentation(&attrs), + named: has_attr(&mut attrs, "named"), + variadic: has_attr(&mut attrs, "variadic"), + external: has_attr(&mut attrs, "external"), + default: parse_attr(&mut attrs, "default")?.map(|expr| { + expr.unwrap_or_else( + || parse_quote! { ::std::default::Default::default() }, + ) + }), + }); + validate_attrs(&attrs)?; + } + } + + Ok(()) } +/// Produce the function's definition. fn create(func: &Func, item: &syn::ItemFn) -> TokenStream { + let eval = quote! { ::typst::eval }; + + let Func { docs, vis, ident, .. } = func; + let item = rewrite_fn_item(item); + let ty = create_func_ty(func); + let data = create_func_data(func); + + let creator = if ty.is_some() { + quote! { + impl #eval::NativeFunc for #ident { + fn data() -> &'static #eval::NativeFuncData { + static DATA: #eval::NativeFuncData = #data; + &DATA + } + } + } + } else { + let ident_data = quote::format_ident!("{ident}_data"); + quote! { + #[doc(hidden)] + #vis fn #ident_data() -> &'static #eval::NativeFuncData { + static DATA: #eval::NativeFuncData = #data; + &DATA + } + } + }; + + quote! { + #[doc = #docs] + #[allow(dead_code)] + #item + + #[doc(hidden)] + #ty + #creator + } +} + +/// Create native function data for the function. +fn create_func_data(func: &Func) -> TokenStream { + let eval = quote! { ::typst::eval }; + let Func { + ident, name, - display, - category, + title, docs, - vis, - ident, - ident_func, + keywords, returns, + scope, + parent, + constructor, .. } = func; - let handlers = func - .params - .iter() - .filter(|param| !param.external) - .map(create_param_parser); + let scope = if *scope { + quote! { <#ident as #eval::NativeScope>::scope() } + } else { + quote! { #eval::Scope::new() } + }; - let args = func - .params - .iter() - .filter(|param| !param.external) - .map(|param| ¶m.ident); + let closure = create_wrapper_closure(func); + let params = func.special.self_.iter().chain(&func.params).map(create_param_info); - let parent = func.parent.as_ref().map(|ty| quote! { #ty:: }); - let vm_ = func.vm.then(|| quote! { vm, }); - let vt_ = func.vt.then(|| quote! { &mut vm.vt, }); - let args_ = func.args.then(|| quote! { args.take(), }); - let span_ = func.span.then(|| quote! { args.span, }); - let wrapper = quote! { - |vm, args| { - let __typst_func = #parent #ident; - #(#handlers)* - let output = __typst_func(#(#args,)* #vm_ #vt_ #args_ #span_); - ::typst::eval::IntoResult::into_result(output, args.span) - } + let name = if *constructor { + quote! { <#parent as #eval::NativeType>::NAME } + } else { + quote! { #name } }; - let mut item = item.clone(); - item.attrs.clear(); - - let inputs = item.sig.inputs.iter().cloned().filter_map(|mut input| { - if let syn::FnArg::Typed(typed) = &mut input { - if typed.attrs.iter().any(|attr| attr.path().is_ident("external")) { - return None; - } - typed.attrs.clear(); + quote! { + #eval::NativeFuncData { + function: #closure, + name: #name, + title: #title, + docs: #docs, + keywords: &[#(#keywords),*], + scope: #eval::Lazy::new(|| #scope), + params: #eval::Lazy::new(|| ::std::vec![#(#params),*]), + returns: #eval::Lazy::new(|| <#returns as #eval::Reflect>::output()), } - Some(input) - }); - - item.sig.inputs = parse_quote! { #(#inputs),* }; + } +} - let keywords = quote_option(&func.keywords); - let params = func.params.iter().map(create_param_info); - let scope = create_scope_builder(func.scope.as_ref()); +/// Create a type that shadows the function. +fn create_func_ty(func: &Func) -> Option<TokenStream> { + if func.parent.is_some() { + return None; + } - quote! { + let Func { vis, ident, .. } = func; + Some(quote! { #[doc(hidden)] - #vis fn #ident_func() -> &'static ::typst::eval::NativeFunc { - static FUNC: ::typst::eval::NativeFunc = ::typst::eval::NativeFunc { - func: #wrapper, - info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo { - name: #name, - display: #display, - keywords: #keywords, - category: #category, - docs: #docs, - params: ::std::vec![#(#params),*], - returns: <#returns as ::typst::eval::Reflect>::describe(), - scope: #scope, - }), - }; - &FUNC + #[allow(non_camel_case_types)] + #vis enum #ident {} + }) +} + +/// Create the runtime-compatible wrapper closure that parses arguments. +fn create_wrapper_closure(func: &Func) -> TokenStream { + // These handlers parse the arguments. + let handlers = { + let func_handlers = func + .params + .iter() + .filter(|param| !param.external) + .map(create_param_parser); + let self_handler = func.special.self_.as_ref().map(create_param_parser); + quote! { + #self_handler + #(#func_handlers)* } + }; - #[doc = #docs] - #item + // This is the actual function call. + let call = { + let self_ = func + .special + .self_ + .as_ref() + .map(bind) + .map(|tokens| quote! { #tokens, }); + let vm_ = func.special.vm.then(|| quote! { vm, }); + let vt_ = func.special.vt.then(|| quote! { &mut vm.vt, }); + let args_ = func.special.args.then(|| quote! { args.take(), }); + let span_ = func.special.span.then(|| quote! { args.span, }); + let forwarded = func.params.iter().filter(|param| !param.external).map(bind); + quote! { + __typst_func(#self_ #vm_ #vt_ #args_ #span_ #(#forwarded,)*) + } + }; + + // This is the whole wrapped closure. + let ident = &func.ident; + let parent = func.parent.as_ref().map(|ty| quote! { #ty:: }); + quote! { + |vm, args| { + let __typst_func = #parent #ident; + #handlers + let output = #call; + ::typst::eval::IntoResult::into_result(output, args.span) + } } } @@ -226,7 +360,7 @@ fn create_param_info(param: &Param) -> TokenStream { ::typst::eval::ParamInfo { name: #name, docs: #docs, - cast: <#ty as ::typst::eval::Reflect>::describe(), + input: <#ty as ::typst::eval::Reflect>::input(), default: #default, positional: #positional, named: #named, @@ -258,10 +392,29 @@ fn create_param_parser(param: &Param) -> TokenStream { quote! { let mut #ident: #ty = #value; } } -struct Parent(Option<syn::Type>); - -impl Parse for Parent { - fn parse(input: ParseStream) -> Result<Self> { - Ok(Self(if !input.is_empty() { Some(input.parse()?) } else { None })) +/// Apply the binding to a parameter. +fn bind(param: &Param) -> TokenStream { + let ident = ¶m.ident; + match param.binding { + Binding::Owned => quote! { #ident }, + Binding::Ref => quote! { &#ident }, + Binding::RefMut => quote! { &mut #ident }, } } + +/// Removes attributes and so on from the native function. +fn rewrite_fn_item(item: &syn::ItemFn) -> syn::ItemFn { + let inputs = item.sig.inputs.iter().cloned().filter_map(|mut input| { + if let syn::FnArg::Typed(typed) = &mut input { + if typed.attrs.iter().any(|attr| attr.path().is_ident("external")) { + return None; + } + typed.attrs.clear(); + } + Some(input) + }); + let mut item = item.clone(); + item.attrs.clear(); + item.sig.inputs = parse_quote! { #(#inputs),* }; + item +} |
