540 lines
16 KiB
Rust
540 lines
16 KiB
Rust
use super::*;
|
|
|
|
/// Expand the `#[node]` macro.
|
|
pub fn node(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
|
|
let node = prepare(stream, &body)?;
|
|
Ok(create(&node))
|
|
}
|
|
|
|
struct Node {
|
|
attrs: Vec<syn::Attribute>,
|
|
vis: syn::Visibility,
|
|
ident: Ident,
|
|
name: String,
|
|
capable: Vec<Ident>,
|
|
set: Option<syn::Block>,
|
|
fields: Vec<Field>,
|
|
}
|
|
|
|
struct Field {
|
|
attrs: Vec<syn::Attribute>,
|
|
vis: syn::Visibility,
|
|
ident: Ident,
|
|
with_ident: Ident,
|
|
name: String,
|
|
|
|
positional: bool,
|
|
required: bool,
|
|
variadic: bool,
|
|
|
|
named: bool,
|
|
shorthand: Option<Shorthand>,
|
|
|
|
settable: bool,
|
|
fold: bool,
|
|
resolve: bool,
|
|
skip: bool,
|
|
|
|
ty: syn::Type,
|
|
default: Option<syn::Expr>,
|
|
}
|
|
|
|
enum Shorthand {
|
|
Positional,
|
|
Named(Ident),
|
|
}
|
|
|
|
impl Node {
|
|
fn inherent(&self) -> impl Iterator<Item = &Field> + Clone {
|
|
self.fields.iter().filter(|field| !field.settable)
|
|
}
|
|
|
|
fn settable(&self) -> impl Iterator<Item = &Field> + Clone {
|
|
self.fields.iter().filter(|field| field.settable)
|
|
}
|
|
}
|
|
|
|
/// 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 fields = vec![];
|
|
for field in &named.named {
|
|
let Some(mut ident) = field.ident.clone() else {
|
|
bail!(field, "expected named field");
|
|
};
|
|
|
|
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 {
|
|
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
|
|
},
|
|
})
|
|
}
|
|
|
|
/// 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()
|
|
.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);
|
|
});
|
|
}
|
|
|
|
quote! {
|
|
#(#attrs)*
|
|
#[::typst::eval::func]
|
|
#[derive(Debug, Clone, Hash)]
|
|
#[repr(transparent)]
|
|
#vis struct #ident(::typst::model::Content);
|
|
|
|
impl #ident {
|
|
#new
|
|
#(#builders)*
|
|
|
|
/// The node's span.
|
|
pub fn span(&self) -> Option<::typst::syntax::Span> {
|
|
self.0.span()
|
|
}
|
|
}
|
|
|
|
impl #ident {
|
|
#(#accessors)*
|
|
#(#items)*
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
fn pack(self) -> ::typst::model::Content {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
#construct
|
|
#set
|
|
|
|
impl From<#ident> for ::typst::eval::Value {
|
|
fn from(value: #ident) -> Self {
|
|
value.0.into()
|
|
}
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
mod #scope {
|
|
use super::*;
|
|
#(#modules)*
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 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 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> {
|
|
#(#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 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
|
|
.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(#name)?.or_else(|| #named.clone()) }
|
|
}
|
|
None => quote! { args.named(#name)? },
|
|
};
|
|
|
|
quote! { styles.set_opt(Self::#ident, #value); }
|
|
})
|
|
.collect();
|
|
|
|
shorthands.sort();
|
|
shorthands.dedup_by_key(|ident| ident.to_string());
|
|
|
|
let bindings = shorthands.into_iter().map(|ident| {
|
|
let string = ident.to_string();
|
|
quote! { let #ident = args.named(#string)?; }
|
|
});
|
|
|
|
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,
|
|
docs: #docs,
|
|
cast: <#value_ty as ::typst::eval::Cast<
|
|
::typst::syntax::Spanned<::typst::eval::Value>
|
|
>>::describe(),
|
|
named: true,
|
|
positional: #shorthand,
|
|
required: false,
|
|
variadic: false,
|
|
settable: true,
|
|
}
|
|
}
|
|
});
|
|
|
|
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)
|
|
}
|
|
|
|
fn properties() -> ::std::vec::Vec<::typst::eval::ParamInfo> {
|
|
::std::vec![#(#infos),*]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 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>(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 ::typst::model::Key for Key<#ty> {
|
|
type Value = #ty;
|
|
type Output = #output;
|
|
|
|
fn id() -> ::typst::model::KeyId {
|
|
static META: ::typst::model::KeyMeta = ::typst::model::KeyMeta {
|
|
name: #name,
|
|
};
|
|
::typst::model::KeyId::from_meta(&META)
|
|
}
|
|
|
|
fn node() -> ::typst::model::NodeId {
|
|
::typst::model::NodeId::of::<#node_ident>()
|
|
}
|
|
|
|
fn get(
|
|
chain: ::typst::model::StyleChain,
|
|
mut values: impl ::std::iter::Iterator<Item = Self::Value>,
|
|
) -> Self::Output {
|
|
#value
|
|
}
|
|
}
|
|
};
|
|
|
|
// Generate the module code.
|
|
quote! {
|
|
pub mod #ident { #scope }
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
}
|
|
}
|