typst/macros/src/func.rs
2023-03-28 09:38:01 +02:00

185 lines
4.9 KiB
Rust

use quote::ToTokens;
use super::*;
/// Expand the `#[func]` macro.
pub fn func(item: syn::ItemFn) -> Result<TokenStream> {
let func = prepare(&item)?;
Ok(create(&func))
}
struct Func {
name: String,
display: String,
category: String,
docs: String,
vis: syn::Visibility,
ident: Ident,
params: Vec<Param>,
returns: Vec<String>,
body: syn::Block,
}
struct Param {
name: String,
docs: String,
external: bool,
named: bool,
variadic: bool,
default: Option<syn::Expr>,
ident: Ident,
ty: syn::Type,
}
fn prepare(item: &syn::ItemFn) -> Result<Func> {
let sig = &item.sig;
let mut params = vec![];
for input in &sig.inputs {
let syn::FnArg::Typed(typed) = input else {
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");
};
if sig.output.to_token_stream().to_string() != "-> Value" {
bail!(sig.output, "must return `Value`");
}
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)?;
}
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(),
};
validate_attrs(&item.attrs)?;
Ok(func)
}
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() -> &'static ::typst::eval::NativeFunc {
static FUNC: ::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,
}),
};
&FUNC
}
}
}
/// 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,
}
}
}
/// 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)? }
};
if let Some(default) = &param.default {
value = quote! { #value.unwrap_or_else(|| #default) }
}
quote! { let #ident: #ty = #value; }
}