summaryrefslogtreecommitdiff
path: root/macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'macros/src')
-rw-r--r--macros/src/func.rs312
-rw-r--r--macros/src/lib.rs5
-rw-r--r--macros/src/node.rs162
-rw-r--r--macros/src/util.rs16
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(&section);
- 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) = &param.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),
+ }
+}