diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-10 12:55:21 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-10 12:55:21 +0100 |
| commit | 62f35602a87574dcc607f1637aeae1be574981ff (patch) | |
| tree | 363a1918006e06d7d79dc2ace5f8e59cd3b6bb19 /macros/src/func.rs | |
| parent | c38d72383d2068361d635d6c1c78ba97aa917801 (diff) | |
New #[func] macro
Diffstat (limited to 'macros/src/func.rs')
| -rw-r--r-- | macros/src/func.rs | 312 |
1 files changed, 156 insertions, 156 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; } } |
