diff options
Diffstat (limited to 'macros/src')
| -rw-r--r-- | macros/src/capable.rs | 48 | ||||
| -rw-r--r-- | macros/src/castable.rs | 135 | ||||
| -rw-r--r-- | macros/src/func.rs | 16 | ||||
| -rw-r--r-- | macros/src/lib.rs | 88 | ||||
| -rw-r--r-- | macros/src/node.rs | 796 | ||||
| -rw-r--r-- | macros/src/symbols.rs | 84 | ||||
| -rw-r--r-- | macros/src/util.rs | 88 |
7 files changed, 623 insertions, 632 deletions
diff --git a/macros/src/capable.rs b/macros/src/capable.rs deleted file mode 100644 index dcfdfc82..00000000 --- a/macros/src/capable.rs +++ /dev/null @@ -1,48 +0,0 @@ -use syn::parse::Parser; -use syn::punctuated::Punctuated; -use syn::Token; - -use super::*; - -/// Expand the `#[capability]` macro. -pub fn capability(item: syn::ItemTrait) -> Result<TokenStream> { - let ident = &item.ident; - Ok(quote! { - #item - impl ::typst::model::Capability for dyn #ident {} - }) -} - -/// Expand the `#[capable(..)]` macro. -pub fn capable(attr: TokenStream, item: syn::Item) -> Result<TokenStream> { - 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 and enums are supported"), - }; - - let (params, args, clause) = generics.split_for_impl(); - let checks = Punctuated::<Ident, Token![,]>::parse_terminated - .parse2(attr)? - .into_iter() - .map(|capability| { - quote! { - if id == ::std::any::TypeId::of::<dyn #capability>() { - return Some(unsafe { - ::typst::util::fat::vtable(self as &dyn #capability) - }); - } - } - }); - - Ok(quote! { - #item - - unsafe impl #params ::typst::model::Capable for #ident #args #clause { - fn vtable(&self, id: ::std::any::TypeId) -> ::std::option::Option<*const ()> { - #(#checks)* - None - } - } - }) -} diff --git a/macros/src/castable.rs b/macros/src/castable.rs index c39df90a..c0d0c1ad 100644 --- a/macros/src/castable.rs +++ b/macros/src/castable.rs @@ -1,11 +1,7 @@ -use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::Token; - use super::*; -/// Expand the `castable!` macro. -pub fn castable(stream: TokenStream) -> Result<TokenStream> { +/// Expand the `cast_from_value!` macro. +pub fn cast_from_value(stream: TokenStream) -> Result<TokenStream> { let castable: Castable = syn::parse2(stream)?; let ty = &castable.ty; @@ -41,6 +37,77 @@ pub fn castable(stream: TokenStream) -> Result<TokenStream> { }) } +/// Expand the `cast_to_value!` macro. +pub fn cast_to_value(stream: TokenStream) -> Result<TokenStream> { + let cast: Cast = syn::parse2(stream)?; + let Pattern::Ty(pat, ty) = &cast.pattern else { + bail!(callsite, "expected pattern"); + }; + + let expr = &cast.expr; + Ok(quote! { + impl ::std::convert::From<#ty> for ::typst::eval::Value { + fn from(#pat: #ty) -> Self { + #expr + } + } + }) +} + +struct Castable { + ty: syn::Type, + name: Option<syn::LitStr>, + casts: Punctuated<Cast, Token![,]>, +} + +struct Cast { + attrs: Vec<syn::Attribute>, + pattern: Pattern, + expr: syn::Expr, +} + +enum Pattern { + Str(syn::LitStr), + Ty(syn::Pat, syn::Type), +} + +impl Parse for Castable { + fn parse(input: ParseStream) -> Result<Self> { + let ty = input.parse()?; + let mut name = None; + if input.peek(Token![:]) { + let _: syn::Token![:] = input.parse()?; + name = Some(input.parse()?); + } + let _: syn::Token![,] = input.parse()?; + let casts = Punctuated::parse_terminated(input)?; + Ok(Self { ty, name, casts }) + } +} + +impl Parse for Cast { + fn parse(input: ParseStream) -> Result<Self> { + let attrs = input.call(syn::Attribute::parse_outer)?; + let pattern = input.parse()?; + let _: syn::Token![=>] = input.parse()?; + let expr = input.parse()?; + Ok(Self { attrs, pattern, expr }) + } +} + +impl Parse for Pattern { + fn parse(input: ParseStream) -> Result<Self> { + if input.peek(syn::LitStr) { + Ok(Pattern::Str(input.parse()?)) + } else { + let pat = input.parse()?; + let _: syn::Token![:] = input.parse()?; + let ty = input.parse()?; + Ok(Pattern::Ty(pat, ty)) + } + } +} + /// Create the castable's `is` function. fn create_is_func(castable: &Castable) -> TokenStream { let mut string_arms = vec![]; @@ -163,7 +230,7 @@ fn create_describe_func(castable: &Castable) -> TokenStream { if let Some(name) = &castable.name { infos.push(quote! { - CastInfo::Type(#name) + ::typst::eval::CastInfo::Type(#name) }); } @@ -173,57 +240,3 @@ fn create_describe_func(castable: &Castable) -> TokenStream { } } } - -struct Castable { - ty: syn::Type, - name: Option<syn::LitStr>, - casts: Punctuated<Cast, Token![,]>, -} - -impl Parse for Castable { - fn parse(input: ParseStream) -> Result<Self> { - let ty = input.parse()?; - let mut name = None; - if input.peek(Token![:]) { - let _: syn::Token![:] = input.parse()?; - name = Some(input.parse()?); - } - let _: syn::Token![,] = input.parse()?; - let casts = Punctuated::parse_terminated(input)?; - Ok(Self { ty, name, casts }) - } -} - -struct Cast { - attrs: Vec<syn::Attribute>, - pattern: Pattern, - expr: syn::Expr, -} - -impl Parse for Cast { - fn parse(input: ParseStream) -> Result<Self> { - let attrs = input.call(syn::Attribute::parse_outer)?; - let pattern = input.parse()?; - let _: syn::Token![=>] = input.parse()?; - let expr = input.parse()?; - Ok(Self { attrs, pattern, expr }) - } -} - -enum Pattern { - Str(syn::LitStr), - Ty(syn::Pat, syn::Type), -} - -impl Parse for Pattern { - fn parse(input: ParseStream) -> Result<Self> { - if input.peek(syn::LitStr) { - Ok(Pattern::Str(input.parse()?)) - } else { - let pat = input.parse()?; - let _: syn::Token![:] = input.parse()?; - let ty = input.parse()?; - Ok(Pattern::Ty(pat, ty)) - } - } -} diff --git a/macros/src/func.rs b/macros/src/func.rs index f65c135e..01c3ca0e 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -1,30 +1,22 @@ -use unscanny::Scanner; - use super::*; /// Expand the `#[func]` macro. pub fn func(item: syn::Item) -> Result<TokenStream> { - let docs = match &item { + let mut docs = match &item { syn::Item::Struct(item) => documentation(&item.attrs), syn::Item::Enum(item) => documentation(&item.attrs), syn::Item::Fn(item) => documentation(&item.attrs), _ => String::new(), }; - let first = docs.lines().next().unwrap(); - let display = first.strip_prefix("# ").unwrap(); - let display = display.trim(); - - let mut docs = docs[first.len()..].to_string(); let (params, returns) = params(&mut docs)?; - let category = section(&mut docs, "Category", 2).expect("missing category"); let docs = docs.trim(); let info = quote! { ::typst::eval::FuncInfo { name, - display: #display, - category: #category, + display: "TODO", + category: "TODO", docs: #docs, params: ::std::vec![#(#params),*], returns: ::std::vec![#(#returns),*] @@ -82,7 +74,7 @@ pub fn func(item: syn::Item) -> Result<TokenStream> { } /// Extract a section. -pub fn section(docs: &mut String, title: &str, level: usize) -> Option<String> { +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)?; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index cd0f9988..c1a8b2ae 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -2,32 +2,23 @@ extern crate proc_macro; -/// Return an error at the given item. -macro_rules! bail { - (callsite, $fmt:literal $($tts:tt)*) => { - return Err(syn::Error::new( - proc_macro2::Span::call_site(), - format!(concat!("typst: ", $fmt) $($tts)*) - )) - }; - ($item:expr, $fmt:literal $($tts:tt)*) => { - return Err(syn::Error::new_spanned( - &$item, - format!(concat!("typst: ", $fmt) $($tts)*) - )) - }; -} - -mod capable; +#[macro_use] +mod util; mod castable; mod func; mod node; mod symbols; use proc_macro::TokenStream as BoundaryStream; -use proc_macro2::{TokenStream, TokenTree}; -use quote::{quote, quote_spanned}; -use syn::{parse_quote, Ident, Result}; +use proc_macro2::TokenStream; +use quote::quote; +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. #[proc_macro_attribute] @@ -38,33 +29,25 @@ pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { /// Implement `Node` for a struct. #[proc_macro_attribute] -pub fn node(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { - let item = syn::parse_macro_input!(item as syn::ItemImpl); - node::node(item).unwrap_or_else(|err| err.to_compile_error()).into() -} - -/// Implement `Capability` for a trait. -#[proc_macro_attribute] -pub fn capability(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { - let item = syn::parse_macro_input!(item as syn::ItemTrait); - capable::capability(item) +pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { + let item = syn::parse_macro_input!(item as syn::ItemStruct); + node::node(stream.into(), item) .unwrap_or_else(|err| err.to_compile_error()) .into() } -/// Implement `Capable` for a type. -#[proc_macro_attribute] -pub fn capable(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { - let item = syn::parse_macro_input!(item as syn::Item); - capable::capable(stream.into(), item) +/// Implement `Cast` and optionally `Type` for a type. +#[proc_macro] +pub fn cast_from_value(stream: BoundaryStream) -> BoundaryStream { + castable::cast_from_value(stream.into()) .unwrap_or_else(|err| err.to_compile_error()) .into() } -/// Implement `Cast` and optionally `Type` for a type. +/// Implement `From<T> for Value` for a type `T`. #[proc_macro] -pub fn castable(stream: BoundaryStream) -> BoundaryStream { - castable::castable(stream.into()) +pub fn cast_to_value(stream: BoundaryStream) -> BoundaryStream { + castable::cast_to_value(stream.into()) .unwrap_or_else(|err| err.to_compile_error()) .into() } @@ -76,32 +59,3 @@ pub fn symbols(stream: BoundaryStream) -> BoundaryStream { .unwrap_or_else(|err| err.to_compile_error()) .into() } - -/// Extract documentation comments from an attribute list. -fn documentation(attrs: &[syn::Attribute]) -> String { - let mut doc = String::new(); - - // Parse doc comments. - for attr in attrs { - if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() { - if meta.path.is_ident("doc") { - if let syn::Lit::Str(string) = &meta.lit { - let full = string.value(); - let line = full.strip_prefix(' ').unwrap_or(&full); - doc.push_str(line); - doc.push('\n'); - } - } - } - } - - doc.trim().into() -} - -/// Dedent documentation text. -fn dedent(text: &str) -> String { - text.lines() - .map(|s| s.strip_prefix(" ").unwrap_or(s)) - .collect::<Vec<_>>() - .join("\n") -} diff --git a/macros/src/node.rs b/macros/src/node.rs index 0d59a402..8a6660ca 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -1,309 +1,370 @@ -use syn::punctuated::Punctuated; -use syn::spanned::Spanned; -use syn::Token; - use super::*; /// Expand the `#[node]` macro. -pub fn node(body: syn::ItemImpl) -> Result<TokenStream> { - let node = prepare(body)?; - create(&node) +pub fn node(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> { + let node = prepare(stream, &body)?; + Ok(create(&node)) } -/// Details about a node. struct Node { - body: syn::ItemImpl, - params: Punctuated<syn::GenericParam, Token![,]>, - self_ty: syn::Type, - self_name: String, - self_args: Punctuated<syn::GenericArgument, Token![,]>, - properties: Vec<Property>, - construct: Option<syn::ImplItemMethod>, - set: Option<syn::ImplItemMethod>, - field: Option<syn::ImplItemMethod>, + attrs: Vec<syn::Attribute>, + vis: syn::Visibility, + ident: Ident, + name: String, + capable: Vec<Ident>, + set: Option<syn::Block>, + fields: Vec<Field>, } -/// A style property. -struct Property { +struct Field { attrs: Vec<syn::Attribute>, vis: syn::Visibility, - name: Ident, - value_ty: syn::Type, - output_ty: syn::Type, - default: syn::Expr, - skip: bool, - referenced: bool, + ident: Ident, + with_ident: Ident, + name: String, + + positional: bool, + required: bool, + variadic: bool, + + named: bool, shorthand: Option<Shorthand>, - resolve: bool, + + settable: bool, fold: bool, + resolve: bool, + skip: bool, + + ty: syn::Type, + default: Option<syn::Expr>, } -/// The shorthand form of a style property. enum Shorthand { Positional, Named(Ident), } -/// Preprocess the impl block of a node. -fn prepare(body: syn::ItemImpl) -> Result<Node> { - // Extract the generic type arguments. - let params = body.generics.params.clone(); +impl Node { + fn inherent(&self) -> impl Iterator<Item = &Field> + Clone { + self.fields.iter().filter(|field| !field.settable) + } - // Extract the node type for which we want to generate properties. - let self_ty = (*body.self_ty).clone(); - let self_path = match &self_ty { - syn::Type::Path(path) => path, - ty => bail!(ty, "must be a path type"), - }; + fn settable(&self) -> impl Iterator<Item = &Field> + Clone { + self.fields.iter().filter(|field| field.settable) + } +} - // Split up the type into its name and its generic type arguments. - let last = self_path.path.segments.last().unwrap(); - let self_name = last.ident.to_string(); - let self_args = match &last.arguments { - syn::PathArguments::AngleBracketed(args) => args.args.clone(), - _ => Punctuated::new(), +/// Preprocess the node's definition. +fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> { + let syn::Fields::Named(named) = &body.fields else { + bail!(body, "expected named fields"); }; - let mut properties = vec![]; - let mut construct = None; - let mut set = None; - let mut field = None; + let mut fields = vec![]; + for field in &named.named { + let Some(mut ident) = field.ident.clone() else { + bail!(field, "expected named field"); + }; - // Parse the properties and methods. - for item in &body.items { - match item { - syn::ImplItem::Const(item) => { - properties.push(prepare_property(item)?); - } - syn::ImplItem::Method(method) => { - match method.sig.ident.to_string().as_str() { - "construct" => construct = Some(method.clone()), - "set" => set = Some(method.clone()), - "field" => field = Some(method.clone()), - _ => bail!(method, "unexpected method"), - } - } - _ => bail!(item, "unexpected item"), + let mut attrs = field.attrs.clone(); + let settable = has_attr(&mut attrs, "settable"); + if settable { + ident = Ident::new(&ident.to_string().to_uppercase(), ident.span()); } + + let field = Field { + vis: field.vis.clone(), + ident: ident.clone(), + with_ident: Ident::new(&format!("with_{}", ident), ident.span()), + name: kebab_case(&ident), + + positional: has_attr(&mut attrs, "positional"), + required: has_attr(&mut attrs, "required"), + variadic: has_attr(&mut attrs, "variadic"), + + named: has_attr(&mut attrs, "named"), + shorthand: parse_attr(&mut attrs, "shorthand")?.map(|v| match v { + None => Shorthand::Positional, + Some(ident) => Shorthand::Named(ident), + }), + + settable, + fold: has_attr(&mut attrs, "fold"), + resolve: has_attr(&mut attrs, "resolve"), + skip: has_attr(&mut attrs, "skip"), + + ty: field.ty.clone(), + default: parse_attr(&mut attrs, "default")?.map(|opt| { + opt.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }) + }), + + attrs: { + validate_attrs(&attrs)?; + attrs + }, + }; + + if !field.positional && !field.named && !field.variadic && !field.settable { + bail!(ident, "expected positional, named, variadic, or settable"); + } + + if !field.required && !field.variadic && field.default.is_none() { + bail!(ident, "non-required fields must have a default value"); + } + + fields.push(field); } + let capable = Punctuated::<Ident, Token![,]>::parse_terminated + .parse2(stream)? + .into_iter() + .collect(); + + let mut attrs = body.attrs.clone(); Ok(Node { - body, - params, - self_ty, - self_name, - self_args, - properties, - construct, - set, - field, + vis: body.vis.clone(), + ident: body.ident.clone(), + name: body.ident.to_string().trim_end_matches("Node").to_lowercase(), + capable, + fields, + set: parse_attr(&mut attrs, "set")?.flatten(), + attrs: { + validate_attrs(&attrs)?; + attrs + }, }) } -/// Preprocess and validate a property constant. -fn prepare_property(item: &syn::ImplItemConst) -> Result<Property> { - let mut attrs = item.attrs.clone(); - let tokens = match attrs +/// Produce the node's definition. +fn create(node: &Node) -> TokenStream { + let attrs = &node.attrs; + let vis = &node.vis; + let ident = &node.ident; + let name = &node.name; + let new = create_new_func(node); + let construct = node + .capable .iter() - .position(|attr| attr.path.is_ident("property")) - .map(|i| attrs.remove(i)) - { - Some(attr) => attr.parse_args::<TokenStream>()?, - None => TokenStream::default(), - }; + .all(|capability| capability != "Construct") + .then(|| create_construct_impl(node)); + let set = create_set_impl(node); + let builders = node.inherent().map(create_builder_method); + let accessors = node.inherent().map(create_accessor_method); + let vtable = create_vtable(node); + + let mut modules = vec![]; + let mut items = vec![]; + let scope = quote::format_ident!("__{}_keys", ident); + + for field in node.settable() { + let ident = &field.ident; + let attrs = &field.attrs; + let vis = &field.vis; + let ty = &field.ty; + modules.push(create_field_module(node, field)); + items.push(quote! { + #(#attrs)* + #vis const #ident: #scope::#ident::Key<#ty> + = #scope::#ident::Key(::std::marker::PhantomData); + }); + } - let mut skip = false; - let mut shorthand = None; - let mut referenced = false; - let mut resolve = false; - let mut fold = false; - - // Parse the `#[property(..)]` attribute. - let mut stream = tokens.into_iter().peekable(); - while let Some(token) = stream.next() { - let ident = match token { - TokenTree::Ident(ident) => ident, - TokenTree::Punct(_) => continue, - _ => bail!(token, "invalid token"), - }; + quote! { + #(#attrs)* + #[::typst::eval::func] + #[derive(Debug, Clone, Hash)] + #[repr(transparent)] + #vis struct #ident(::typst::model::Content); - let mut arg = None; - if let Some(TokenTree::Group(group)) = stream.peek() { - let span = group.span(); - let string = group.to_string(); - let ident = string.trim_start_matches('(').trim_end_matches(')'); - if !ident.chars().all(|c| c.is_ascii_alphabetic()) { - bail!(group, "invalid arguments"); - } - arg = Some(Ident::new(ident, span)); - stream.next(); - }; + impl #ident { + #new + #(#builders)* - match ident.to_string().as_str() { - "skip" => skip = true, - "shorthand" => { - shorthand = Some(match arg { - Some(name) => Shorthand::Named(name), - None => Shorthand::Positional, - }); + /// The node's span. + pub fn span(&self) -> Option<::typst::syntax::Span> { + self.0.span() } - "referenced" => referenced = true, - "resolve" => resolve = true, - "fold" => fold = true, - _ => bail!(ident, "invalid attribute"), } - } - if referenced && (fold || resolve) { - bail!(item.ident, "referenced is mutually exclusive with fold and resolve"); - } - - // The type of the property's value is what the user of our macro wrote as - // type of the const, but the real type of the const will be a unique `Key` - // type. - let value_ty = item.ty.clone(); - let output_ty = if referenced { - parse_quote! { &'a #value_ty } - } else if fold && resolve { - parse_quote! { - <<#value_ty as ::typst::model::Resolve>::Output - as ::typst::model::Fold>::Output + impl #ident { + #(#accessors)* + #(#items)* } - } else if fold { - parse_quote! { <#value_ty as ::typst::model::Fold>::Output } - } else if resolve { - parse_quote! { <#value_ty as ::typst::model::Resolve>::Output } - } else { - value_ty.clone() - }; - Ok(Property { - attrs, - vis: item.vis.clone(), - name: item.ident.clone(), - value_ty, - output_ty, - default: item.expr.clone(), - skip, - shorthand, - referenced, - resolve, - fold, - }) -} + impl ::typst::model::Node for #ident { + fn id() -> ::typst::model::NodeId { + static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta { + name: #name, + vtable: #vtable, + }; + ::typst::model::NodeId::from_meta(&META) + } -/// Produce the necessary items for a type to become a node. -fn create(node: &Node) -> Result<TokenStream> { - let params = &node.params; - let self_ty = &node.self_ty; - - let id_method = create_node_id_method(); - let name_method = create_node_name_method(node); - let construct_func = create_node_construct_func(node); - let set_func = create_node_set_func(node); - let properties_func = create_node_properties_func(node); - let field_method = create_node_field_method(node); - - let node_impl = quote! { - impl<#params> ::typst::model::Node for #self_ty { - #id_method - #name_method - #construct_func - #set_func - #properties_func - #field_method + fn pack(self) -> ::typst::model::Content { + self.0 + } } - }; - - let mut modules: Vec<syn::ItemMod> = vec![]; - let mut items: Vec<syn::ImplItem> = vec![]; - let scope = quote::format_ident!("__{}_keys", node.self_name); - for property in node.properties.iter() { - let (key, module) = create_property_module(node, property); - modules.push(module); + #construct + #set - let name = &property.name; - let attrs = &property.attrs; - let vis = &property.vis; - items.push(parse_quote! { - #(#attrs)* - #vis const #name: #scope::#name::#key - = #scope::#name::Key(::std::marker::PhantomData); - }); - } - - let mut body = node.body.clone(); - body.items = items; + impl From<#ident> for ::typst::eval::Value { + fn from(value: #ident) -> Self { + value.0.into() + } + } - Ok(quote! { - #body + #[allow(non_snake_case)] mod #scope { use super::*; - #node_impl #(#modules)* } - }) + } } -/// Create the node's id method. -fn create_node_id_method() -> syn::ImplItemMethod { - parse_quote! { - fn id(&self) -> ::typst::model::NodeId { - ::typst::model::NodeId::of::<Self>() +/// Create the `new` function for the node. +fn create_new_func(node: &Node) -> TokenStream { + let relevant = node.inherent().filter(|field| field.required || field.variadic); + let params = relevant.clone().map(|field| { + let ident = &field.ident; + let ty = &field.ty; + quote! { #ident: #ty } + }); + let pushes = relevant.map(|field| { + let ident = &field.ident; + let with_ident = &field.with_ident; + quote! { .#with_ident(#ident) } + }); + let defaults = node + .inherent() + .filter_map(|field| field.default.as_ref().map(|default| (field, default))) + .map(|(field, default)| { + let with_ident = &field.with_ident; + quote! { .#with_ident(#default) } + }); + quote! { + /// Create a new node. + pub fn new(#(#params),*) -> Self { + Self(::typst::model::Content::new::<Self>()) + #(#pushes)* + #(#defaults)* } } } -/// Create the node's name method. -fn create_node_name_method(node: &Node) -> syn::ImplItemMethod { - let name = node.self_name.trim_end_matches("Node").to_lowercase(); - parse_quote! { - fn name(&self) -> &'static str { - #name +/// Create a builder pattern method for a field. +fn create_builder_method(field: &Field) -> TokenStream { + let Field { with_ident, ident, name, ty, .. } = field; + let doc = format!("Set the [`{}`](Self::{}) field.", name, ident); + quote! { + #[doc = #doc] + pub fn #with_ident(mut self, #ident: #ty) -> Self { + Self(self.0.with_field(#name, #ident)) } } } -/// Create the node's `construct` function. -fn create_node_construct_func(node: &Node) -> syn::ImplItemMethod { - node.construct.clone().unwrap_or_else(|| { - parse_quote! { +/// Create an accessor methods for a field. +fn create_accessor_method(field: &Field) -> TokenStream { + let Field { attrs, vis, ident, name, ty, .. } = field; + quote! { + #(#attrs)* + #vis fn #ident(&self) -> #ty { + self.0.cast_field(#name) + } + } +} + +/// Create the node's `Construct` implementation. +fn create_construct_impl(node: &Node) -> TokenStream { + let ident = &node.ident; + let shorthands = create_construct_shorthands(node); + let builders = node.inherent().map(create_construct_builder_call); + quote! { + impl ::typst::model::Construct for #ident { fn construct( _: &::typst::eval::Vm, args: &mut ::typst::eval::Args, ) -> ::typst::diag::SourceResult<::typst::model::Content> { - ::typst::diag::bail!(args.span, "cannot be constructed manually"); + #(#shorthands)* + Ok(::typst::model::Node::pack( + Self(::typst::model::Content::new::<Self>()) + #(#builders)*)) } } + } +} + +/// Create let bindings for shorthands in the constructor. +fn create_construct_shorthands(node: &Node) -> impl Iterator<Item = TokenStream> + '_ { + let mut shorthands = vec![]; + for field in node.inherent() { + if let Some(Shorthand::Named(named)) = &field.shorthand { + shorthands.push(named); + } + } + + shorthands.sort(); + shorthands.dedup_by_key(|ident| ident.to_string()); + shorthands.into_iter().map(|ident| { + let string = ident.to_string(); + quote! { let #ident = args.named(#string)?; } }) } -/// Create the node's `set` function. -fn create_node_set_func(node: &Node) -> syn::ImplItemMethod { - let user = node.set.as_ref().map(|method| { - let block = &method.block; +/// Create a builder call for the constructor. +fn create_construct_builder_call(field: &Field) -> TokenStream { + let name = &field.name; + let with_ident = &field.with_ident; + + let mut value = if field.variadic { + quote! { args.all()? } + } else if field.required { + quote! { args.expect(#name)? } + } else if let Some(shorthand) = &field.shorthand { + match shorthand { + Shorthand::Positional => quote! { args.named_or_find(#name)? }, + Shorthand::Named(named) => { + quote! { args.named(#name)?.or_else(|| #named.clone()) } + } + } + } else if field.named { + quote! { args.named(#name)? } + } else { + quote! { args.find()? } + }; + + if let Some(default) = &field.default { + value = quote! { #value.unwrap_or(#default) }; + } + + quote! { .#with_ident(#value) } +} + +/// Create the node's `Set` implementation. +fn create_set_impl(node: &Node) -> TokenStream { + let ident = &node.ident; + let custom = node.set.as_ref().map(|block| { quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; } }); let mut shorthands = vec![]; let sets: Vec<_> = node - .properties - .iter() - .filter(|p| !p.skip) - .map(|property| { - let name = &property.name; - let string = name.to_string().replace('_', "-").to_lowercase(); - let value = match &property.shorthand { - Some(Shorthand::Positional) => quote! { args.named_or_find(#string)? }, + .settable() + .filter(|field| !field.skip) + .map(|field| { + let ident = &field.ident; + let name = &field.name; + let value = match &field.shorthand { + Some(Shorthand::Positional) => quote! { args.named_or_find(#name)? }, Some(Shorthand::Named(named)) => { shorthands.push(named); - quote! { args.named(#string)?.or_else(|| #named.clone()) } + quote! { args.named(#name)?.or_else(|| #named.clone()) } } - None => quote! { args.named(#string)? }, + None => quote! { args.named(#name)? }, }; - quote! { styles.set_opt(Self::#name, #value); } + quote! { styles.set_opt(Self::#ident, #value); } }) .collect(); @@ -315,30 +376,12 @@ fn create_node_set_func(node: &Node) -> syn::ImplItemMethod { quote! { let #ident = args.named(#string)?; } }); - parse_quote! { - fn set( - args: &mut ::typst::eval::Args, - constructor: bool, - ) -> ::typst::diag::SourceResult<::typst::model::StyleMap> { - let mut styles = ::typst::model::StyleMap::new(); - #user - #(#bindings)* - #(#sets)* - Ok(styles) - } - } -} - -/// Create the node's `properties` function. -fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod { - let infos = node.properties.iter().filter(|p| !p.skip).map(|property| { - let name = property.name.to_string().replace('_', "-").to_lowercase(); - let value_ty = &property.value_ty; - let shorthand = matches!(property.shorthand, Some(Shorthand::Positional)); - - let docs = documentation(&property.attrs); + let infos = node.fields.iter().filter(|p| !p.skip).map(|field| { + let name = &field.name; + let value_ty = &field.ty; + let shorthand = matches!(field.shorthand, Some(Shorthand::Positional)); + let docs = documentation(&field.attrs); let docs = docs.trim(); - quote! { ::typst::eval::ParamInfo { name: #name, @@ -355,167 +398,142 @@ fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod { } }); - parse_quote! { - fn properties() -> ::std::vec::Vec<::typst::eval::ParamInfo> - where - Self: Sized - { - ::std::vec![#(#infos),*] - } - } -} + quote! { + impl ::typst::model::Set for #ident { + fn set( + args: &mut ::typst::eval::Args, + constructor: bool, + ) -> ::typst::diag::SourceResult<::typst::model::StyleMap> { + let mut styles = ::typst::model::StyleMap::new(); + #custom + #(#bindings)* + #(#sets)* + Ok(styles) + } -/// Create the node's `field` method. -fn create_node_field_method(node: &Node) -> syn::ImplItemMethod { - node.field.clone().unwrap_or_else(|| { - parse_quote! { - fn field( - &self, - _: &str, - ) -> ::std::option::Option<::typst::eval::Value> { - None + fn properties() -> ::std::vec::Vec<::typst::eval::ParamInfo> { + ::std::vec![#(#infos),*] } } - }) + } } -/// Process a single const item. -fn create_property_module(node: &Node, property: &Property) -> (syn::Type, syn::ItemMod) { - let params = &node.params; - let self_args = &node.self_args; - let name = &property.name; - let value_ty = &property.value_ty; - let output_ty = &property.output_ty; - - let key = parse_quote! { Key<#value_ty, #self_args> }; - let phantom_args = self_args.iter().filter(|arg| match arg { - syn::GenericArgument::Type(syn::Type::Path(path)) => { - node.params.iter().all(|param| match param { - syn::GenericParam::Const(c) => !path.path.is_ident(&c.ident), - _ => true, - }) - } - _ => true, - }); +/// Create the module for a single field. +fn create_field_module(node: &Node, field: &Field) -> TokenStream { + let node_ident = &node.ident; + let ident = &field.ident; + let name = &field.name; + let ty = &field.ty; + let default = &field.default; + + let mut output = quote! { #ty }; + if field.resolve { + output = quote! { <#output as ::typst::model::Resolve>::Output }; + } + if field.fold { + output = quote! { <#output as ::typst::model::Fold>::Output }; + } - let name_const = create_property_name_const(node, property); - let node_func = create_property_node_func(node); - let get_method = create_property_get_method(property); - let copy_assertion = create_property_copy_assertion(property); + let value = if field.resolve && field.fold { + quote! { + values + .next() + .map(|value| { + ::typst::model::Fold::fold( + ::typst::model::Resolve::resolve(value, chain), + Self::get(chain, values), + ) + }) + .unwrap_or(#default) + } + } else if field.resolve { + quote! { + ::typst::model::Resolve::resolve( + values.next().unwrap_or(#default), + chain + ) + } + } else if field.fold { + quote! { + values + .next() + .map(|value| { + ::typst::model::Fold::fold( + value, + Self::get(chain, values), + ) + }) + .unwrap_or(#default) + } + } else { + quote! { + values.next().unwrap_or(#default) + } + }; // Generate the contents of the module. let scope = quote! { use super::*; - pub struct Key<__T, #params>( - pub ::std::marker::PhantomData<(__T, #(#phantom_args,)*)> - ); - - impl<#params> ::std::marker::Copy for #key {} - impl<#params> ::std::clone::Clone for #key { + pub struct Key<T>(pub ::std::marker::PhantomData<T>); + impl ::std::marker::Copy for Key<#ty> {} + impl ::std::clone::Clone for Key<#ty> { fn clone(&self) -> Self { *self } } - impl<#params> ::typst::model::Key for #key { - type Value = #value_ty; - type Output<'a> = #output_ty; - #name_const - #node_func - #get_method - } - - #copy_assertion - }; - - // Generate the module code. - let module = parse_quote! { - #[allow(non_snake_case)] - pub mod #name { #scope } - }; - - (key, module) -} + impl ::typst::model::Key for Key<#ty> { + type Value = #ty; + type Output = #output; -/// Create the property's node method. -fn create_property_name_const(node: &Node, property: &Property) -> syn::ImplItemConst { - // The display name, e.g. `TextNode::BOLD`. - let name = format!("{}::{}", node.self_name, &property.name); - parse_quote! { - const NAME: &'static str = #name; - } -} - -/// Create the property's node method. -fn create_property_node_func(node: &Node) -> syn::ImplItemMethod { - let self_ty = &node.self_ty; - parse_quote! { - fn node() -> ::typst::model::NodeId { - ::typst::model::NodeId::of::<#self_ty>() - } - } -} - -/// Create the property's get method. -fn create_property_get_method(property: &Property) -> syn::ImplItemMethod { - let default = &property.default; - let value_ty = &property.value_ty; + fn id() -> ::typst::model::KeyId { + static META: ::typst::model::KeyMeta = ::typst::model::KeyMeta { + name: #name, + }; + ::typst::model::KeyId::from_meta(&META) + } - let value = if property.referenced { - quote! { - values.next().unwrap_or_else(|| { - static LAZY: ::typst::model::once_cell::sync::Lazy<#value_ty> - = ::typst::model::once_cell::sync::Lazy::new(|| #default); - &*LAZY - }) - } - } else if property.resolve && property.fold { - quote! { - match values.next().cloned() { - Some(value) => ::typst::model::Fold::fold( - ::typst::model::Resolve::resolve(value, chain), - Self::get(chain, values), - ), - None => #default, + fn node() -> ::typst::model::NodeId { + ::typst::model::NodeId::of::<#node_ident>() } - } - } else if property.resolve { - quote! { - let value = values.next().cloned().unwrap_or_else(|| #default); - ::typst::model::Resolve::resolve(value, chain) - } - } else if property.fold { - quote! { - match values.next().cloned() { - Some(value) => ::typst::model::Fold::fold(value, Self::get(chain, values)), - None => #default, + + fn get( + chain: ::typst::model::StyleChain, + mut values: impl ::std::iter::Iterator<Item = Self::Value>, + ) -> Self::Output { + #value } } - } else { - quote! { - values.next().copied().unwrap_or(#default) - } }; - parse_quote! { - fn get<'a>( - chain: ::typst::model::StyleChain<'a>, - mut values: impl ::std::iter::Iterator<Item = &'a Self::Value>, - ) -> Self::Output<'a> { - #value - } + // Generate the module code. + quote! { + pub mod #ident { #scope } } } -/// Create the assertion if the property's value must be copyable. -fn create_property_copy_assertion(property: &Property) -> Option<TokenStream> { - let value_ty = &property.value_ty; - let must_be_copy = !property.fold && !property.resolve && !property.referenced; - must_be_copy.then(|| { - quote_spanned! { value_ty.span() => - const _: fn() -> () = || { - fn must_be_copy_fold_resolve_or_referenced<T: ::std::marker::Copy>() {} - must_be_copy_fold_resolve_or_referenced::<#value_ty>(); - }; +/// Create the node's metadata vtable. +fn create_vtable(node: &Node) -> TokenStream { + let ident = &node.ident; + let checks = + node.capable + .iter() + .filter(|&ident| ident != "Construct") + .map(|capability| { + quote! { + if id == ::std::any::TypeId::of::<dyn #capability>() { + return Some(unsafe { + ::typst::util::fat::vtable(& + Self(::typst::model::Content::new::<#ident>()) as &dyn #capability + ) + }); + } + } + }); + + quote! { + |id| { + #(#checks)* + None } - }) + } } diff --git a/macros/src/symbols.rs b/macros/src/symbols.rs index efa4834d..cdb7f5d7 100644 --- a/macros/src/symbols.rs +++ b/macros/src/symbols.rs @@ -1,30 +1,44 @@ -use syn::ext::IdentExt; -use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::Token; - use super::*; /// Expand the `symbols!` macro. pub fn symbols(stream: TokenStream) -> Result<TokenStream> { - let list: List = syn::parse2(stream)?; - let pairs = list.0.iter().map(Symbol::expand); + let list: Punctuated<Symbol, Token![,]> = + Punctuated::parse_terminated.parse2(stream)?; + let pairs = list.iter().map(|symbol| { + let name = symbol.name.to_string(); + let kind = match &symbol.kind { + Kind::Single(c) => quote! { typst::eval::Symbol::new(#c), }, + Kind::Multiple(variants) => { + let variants = variants.iter().map(|variant| { + let name = &variant.name; + let c = &variant.c; + quote! { (#name, #c) } + }); + quote! { + typst::eval::Symbol::list(&[#(#variants),*]) + } + } + }; + quote! { (#name, #kind) } + }); Ok(quote! { &[#(#pairs),*] }) } -struct List(Punctuated<Symbol, Token![,]>); - -impl Parse for List { - fn parse(input: ParseStream) -> Result<Self> { - Punctuated::parse_terminated(input).map(Self) - } -} - struct Symbol { name: syn::Ident, kind: Kind, } +enum Kind { + Single(syn::LitChar), + Multiple(Punctuated<Variant, Token![,]>), +} + +struct Variant { + name: String, + c: syn::LitChar, +} + impl Parse for Symbol { fn parse(input: ParseStream) -> Result<Self> { let name = input.call(Ident::parse_any)?; @@ -34,19 +48,6 @@ impl Parse for Symbol { } } -impl Symbol { - fn expand(&self) -> TokenStream { - let name = self.name.to_string(); - let kind = self.kind.expand(); - quote! { (#name, #kind) } - } -} - -enum Kind { - Single(syn::LitChar), - Multiple(Punctuated<Variant, Token![,]>), -} - impl Parse for Kind { fn parse(input: ParseStream) -> Result<Self> { if input.peek(syn::LitChar) { @@ -59,25 +60,6 @@ impl Parse for Kind { } } -impl Kind { - fn expand(&self) -> TokenStream { - match self { - Self::Single(c) => quote! { typst::eval::Symbol::new(#c), }, - Self::Multiple(variants) => { - let variants = variants.iter().map(Variant::expand); - quote! { - typst::eval::Symbol::list(&[#(#variants),*]) - } - } - } - } -} - -struct Variant { - name: String, - c: syn::LitChar, -} - impl Parse for Variant { fn parse(input: ParseStream) -> Result<Self> { let mut name = String::new(); @@ -94,11 +76,3 @@ impl Parse for Variant { Ok(Self { name, c }) } } - -impl Variant { - fn expand(&self) -> TokenStream { - let name = &self.name; - let c = &self.c; - quote! { (#name, #c) } - } -} diff --git a/macros/src/util.rs b/macros/src/util.rs new file mode 100644 index 00000000..c8c56a05 --- /dev/null +++ b/macros/src/util.rs @@ -0,0 +1,88 @@ +use super::*; + +/// Return an error at the given item. +macro_rules! bail { + (callsite, $fmt:literal $($tts:tt)*) => { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + format!(concat!("typst: ", $fmt) $($tts)*) + )) + }; + ($item:expr, $fmt:literal $($tts:tt)*) => { + return Err(syn::Error::new_spanned( + &$item, + format!(concat!("typst: ", $fmt) $($tts)*) + )) + }; +} + +/// Whether an attribute list has a specified attribute. +pub fn has_attr(attrs: &mut Vec<syn::Attribute>, target: &str) -> bool { + take_attr(attrs, target).is_some() +} + +/// Whether an attribute list has a specified attribute. +pub fn parse_attr<T: Parse>( + attrs: &mut Vec<syn::Attribute>, + target: &str, +) -> Result<Option<Option<T>>> { + take_attr(attrs, target) + .map(|attr| (!attr.tokens.is_empty()).then(|| attr.parse_args()).transpose()) + .transpose() +} + +/// Whether an attribute list has a specified attribute. +pub fn take_attr( + attrs: &mut Vec<syn::Attribute>, + target: &str, +) -> Option<syn::Attribute> { + attrs + .iter() + .position(|attr| attr.path.is_ident(target)) + .map(|i| attrs.remove(i)) +} + +/// Ensure that no unrecognized attributes remain. +pub fn validate_attrs(attrs: &[syn::Attribute]) -> Result<()> { + for attr in attrs { + if !attr.path.is_ident("doc") { + let ident = attr.path.get_ident().unwrap(); + bail!(ident, "unrecognized attribute: {:?}", ident.to_string()); + } + } + Ok(()) +} + +/// Convert an identifier to a kebab-case string. +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(); + + // Parse doc comments. + for attr in attrs { + if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() { + if meta.path.is_ident("doc") { + if let syn::Lit::Str(string) = &meta.lit { + let full = string.value(); + let line = full.strip_prefix(' ').unwrap_or(&full); + doc.push_str(line); + doc.push('\n'); + } + } + } + } + + doc.trim().into() +} |
