mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
185 lines
4.9 KiB
Rust
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) = ¶m.default {
|
|
value = quote! { #value.unwrap_or_else(|| #default) }
|
|
}
|
|
|
|
quote! { let #ident: #ty = #value; }
|
|
}
|