summaryrefslogtreecommitdiff
path: root/macros/src/node.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-19 22:28:49 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-19 22:39:19 +0100
commitab43bd802eafe33977a91893907e67553e099569 (patch)
treeaf4dead92b143348f52e2e8f869df3f7dfd7322a /macros/src/node.rs
parentd6aaae0cea1e79eecd85dc94ab85b9ad8eff48e8 (diff)
Renaming and refactoring
Diffstat (limited to 'macros/src/node.rs')
-rw-r--r--macros/src/node.rs521
1 files changed, 0 insertions, 521 deletions
diff --git a/macros/src/node.rs b/macros/src/node.rs
deleted file mode 100644
index 68d43d9c..00000000
--- a/macros/src/node.rs
+++ /dev/null
@@ -1,521 +0,0 @@
-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 {
- name: String,
- display: String,
- category: String,
- docs: String,
- vis: syn::Visibility,
- ident: Ident,
- capable: Vec<Ident>,
- fields: Vec<Field>,
-}
-
-struct Field {
- name: String,
- docs: String,
- internal: bool,
- external: bool,
- positional: bool,
- required: bool,
- variadic: bool,
- synthesized: bool,
- fold: bool,
- resolve: bool,
- parse: Option<FieldParser>,
- default: syn::Expr,
- vis: syn::Visibility,
- ident: Ident,
- ident_in: Ident,
- with_ident: Ident,
- push_ident: Ident,
- set_ident: Ident,
- ty: syn::Type,
- output: syn::Type,
-}
-
-impl Field {
- fn inherent(&self) -> bool {
- self.required || self.variadic
- }
-
- fn settable(&self) -> bool {
- !self.inherent()
- }
-}
-
-struct FieldParser {
- prefix: Vec<syn::Stmt>,
- expr: syn::Stmt,
-}
-
-impl Parse for FieldParser {
- fn parse(input: ParseStream) -> Result<Self> {
- let mut stmts = syn::Block::parse_within(input)?;
- let Some(expr) = stmts.pop() else {
- return Err(input.error("expected at least on expression"));
- };
- Ok(Self { prefix: stmts, expr })
- }
-}
-
-/// 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(ident) = field.ident.clone() else {
- bail!(field, "expected named field");
- };
-
- 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;
-
- if ident == "label" {
- bail!(ident, "invalid field name");
- }
-
- let mut field = Field {
- name: kebab_case(&ident),
- docs: documentation(&attrs),
- internal: has_attr(&mut attrs, "internal"),
- external: has_attr(&mut attrs, "external"),
- positional,
- required,
- variadic,
- synthesized: has_attr(&mut attrs, "synthesized"),
- fold: has_attr(&mut attrs, "fold"),
- resolve: has_attr(&mut attrs, "resolve"),
- parse: parse_attr(&mut attrs, "parse")?.flatten(),
- default: parse_attr(&mut attrs, "default")?
- .flatten()
- .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }),
- 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()),
- push_ident: Ident::new(&format!("push_{}", 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) {
- bail!(ident, "required fields cannot be folded or resolved");
- }
-
- if field.required && !field.positional {
- bail!(ident, "only positional fields can be required");
- }
-
- if field.resolve {
- let output = &field.output;
- field.output = parse_quote! { <#output as ::typst::model::Resolve>::Output };
- }
- if field.fold {
- let output = &field.output;
- field.output = parse_quote! { <#output as ::typst::model::Fold>::Output };
- }
-
- validate_attrs(&attrs)?;
- fields.push(field);
- }
-
- let capable = Punctuated::<Ident, Token![,]>::parse_terminated
- .parse2(stream)?
- .into_iter()
- .collect();
-
- 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(),
- capable,
- fields,
- };
-
- validate_attrs(&body.attrs)?;
- Ok(node)
-}
-
-/// Produce the node's definition.
-fn create(node: &Node) -> TokenStream {
- let Node { vis, ident, docs, .. } = node;
- let all = node.fields.iter().filter(|field| !field.external);
- let settable = all.clone().filter(|field| !field.synthesized && field.settable());
-
- // Inherent methods and functions.
- let new = create_new_func(node);
- let field_methods = all.clone().map(create_field_method);
- let field_in_methods = settable.clone().map(create_field_in_method);
- let with_field_methods = all.clone().map(create_with_field_method);
- let push_field_methods = all.map(create_push_field_method);
- let field_style_methods = settable.map(create_set_field_method);
-
- // Trait implementations.
- let node_impl = create_node_impl(node);
- let construct_impl = node
- .capable
- .iter()
- .all(|capability| capability != "Construct")
- .then(|| create_construct_impl(node));
- let set_impl = create_set_impl(node);
- let locatable_impl = node
- .capable
- .iter()
- .any(|capability| capability == "Locatable")
- .then(|| quote! { impl ::typst::model::Locatable for #ident {} });
-
- quote! {
- #[doc = #docs]
- #[derive(Debug, Clone, Hash)]
- #[repr(transparent)]
- #vis struct #ident(pub ::typst::model::Content);
-
- impl #ident {
- #new
- #(#field_methods)*
- #(#field_in_methods)*
- #(#with_field_methods)*
- #(#push_field_methods)*
- #(#field_style_methods)*
-
- /// The node's span.
- pub fn span(&self) -> ::typst::syntax::Span {
- self.0.span()
- }
- }
-
- #node_impl
- #construct_impl
- #set_impl
- #locatable_impl
-
- impl From<#ident> for ::typst::eval::Value {
- fn from(value: #ident) -> Self {
- value.0.into()
- }
- }
- }
-}
-
-/// Create the `new` function for the node.
-fn create_new_func(node: &Node) -> TokenStream {
- let relevant = node
- .fields
- .iter()
- .filter(|field| !field.external && !field.synthesized && field.inherent());
- let params = relevant.clone().map(|Field { ident, ty, .. }| {
- quote! { #ident: #ty }
- });
- let builder_calls = relevant.map(|Field { ident, with_ident, .. }| {
- quote! { .#with_ident(#ident) }
- });
- quote! {
- /// Create a new node.
- pub fn new(#(#params),*) -> Self {
- Self(::typst::model::Content::new(<Self as ::typst::model::Node>::id()))
- #(#builder_calls)*
- }
- }
-}
-
-/// Create an accessor methods for a field.
-fn create_field_method(field: &Field) -> TokenStream {
- let Field { vis, docs, ident, name, output, .. } = field;
- if field.inherent() || field.synthesized {
- quote! {
- #[doc = #docs]
- #[track_caller]
- #vis fn #ident(&self) -> #output {
- self.0.expect_field(#name)
- }
- }
- } else {
- let access =
- create_style_chain_access(field, quote! { self.0.field(#name).cloned() });
- quote! {
- #[doc = #docs]
- #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output {
- #access
- }
- }
- }
-}
-
-/// Create a style chain access method for a field.
-fn create_field_in_method(field: &Field) -> TokenStream {
- let Field { vis, ident_in, name, output, .. } = field;
- let doc = format!("Access the `{}` field in the given style chain.", name);
- let access = create_style_chain_access(field, quote! { None });
- quote! {
- #[doc = #doc]
- #vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output {
- #access
- }
- }
-}
-
-/// Create a style chain access method for a field.
-fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStream {
- let Field { name, ty, default, .. } = field;
- let getter = match (field.fold, field.resolve) {
- (false, false) => quote! { get },
- (false, true) => quote! { get_resolve },
- (true, false) => quote! { get_fold },
- (true, true) => quote! { get_resolve_fold },
- };
-
- quote! {
- styles.#getter::<#ty>(
- ::typst::model::NodeId::of::<Self>(),
- #name,
- #inherent,
- || #default,
- )
- }
-}
-
-/// Create a builder pattern method for a field.
-fn create_with_field_method(field: &Field) -> TokenStream {
- let Field { vis, ident, with_ident, name, ty, .. } = field;
- let doc = format!("Set the [`{}`](Self::{}) field.", name, ident);
- quote! {
- #[doc = #doc]
- #vis fn #with_ident(mut self, #ident: #ty) -> Self {
- Self(self.0.with_field(#name, #ident))
- }
- }
-}
-
-/// Create a set-style method for a field.
-fn create_push_field_method(field: &Field) -> TokenStream {
- let Field { vis, ident, push_ident, name, ty, .. } = field;
- let doc = format!("Push the [`{}`](Self::{}) field.", name, ident);
- quote! {
- #[doc = #doc]
- #vis fn #push_ident(&mut self, #ident: #ty) {
- self.0.push_field(#name, #ident);
- }
- }
-}
-
-/// Create a setter method for a field.
-fn create_set_field_method(field: &Field) -> TokenStream {
- let Field { vis, ident, set_ident, name, ty, .. } = field;
- let doc = format!("Create a style property for the `{}` field.", name);
- quote! {
- #[doc = #doc]
- #vis fn #set_ident(#ident: #ty) -> ::typst::model::Style {
- ::typst::model::Style::Property(::typst::model::Property::new(
- ::typst::model::NodeId::of::<Self>(),
- #name.into(),
- #ident.into()
- ))
- }
- }
-}
-
-/// Create the node's `Node` implementation.
-fn create_node_impl(node: &Node) -> TokenStream {
- let Node { ident, name, display, category, docs, .. } = node;
- let vtable_func = create_vtable_func(node);
- let infos = node
- .fields
- .iter()
- .filter(|field| !field.internal && !field.synthesized)
- .map(create_param_info);
- quote! {
- impl ::typst::model::Node for #ident {
- fn id() -> ::typst::model::NodeId {
- static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
- name: #name,
- vtable: #vtable_func,
- construct: <#ident as ::typst::model::Construct>::construct,
- set: <#ident 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,
- }),
- };
- ::typst::model::NodeId(&META)
- }
-
- fn pack(self) -> ::typst::model::Content {
- self.0
- }
- }
- }
-}
-
-/// Create the node's casting vtable.
-fn create_vtable_func(node: &Node) -> TokenStream {
- let ident = &node.ident;
- let relevant = node.capable.iter().filter(|&ident| ident != "Construct");
- let checks = relevant.map(|capability| {
- quote! {
- if id == ::std::any::TypeId::of::<dyn #capability>() {
- return Some(unsafe {
- ::typst::util::fat::vtable(&null as &dyn #capability)
- });
- }
- }
- });
-
- quote! {
- |id| {
- let null = Self(::typst::model::Content::new(<#ident as ::typst::model::Node>::id()));
- #(#checks)*
- None
- }
- }
-}
-
-/// Create a parameter info for a field.
-fn create_param_info(field: &Field) -> TokenStream {
- let Field { name, docs, positional, variadic, required, ty, .. } = field;
- let named = !positional;
- let settable = field.settable();
- 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: #settable,
- }
- }
-}
-
-/// Create the node's `Construct` implementation.
-fn create_construct_impl(node: &Node) -> TokenStream {
- let ident = &node.ident;
- let handlers = node
- .fields
- .iter()
- .filter(|field| {
- !field.external
- && !field.synthesized
- && (!field.internal || field.parse.is_some())
- })
- .map(|field| {
- let push_ident = &field.push_ident;
- let (prefix, value) = create_field_parser(field);
- if field.settable() {
- quote! {
- #prefix
- if let Some(value) = #value {
- node.#push_ident(value);
- }
- }
- } else {
- quote! {
- #prefix
- node.#push_ident(#value);
- }
- }
- });
-
- quote! {
- impl ::typst::model::Construct for #ident {
- fn construct(
- vm: &::typst::eval::Vm,
- args: &mut ::typst::eval::Args,
- ) -> ::typst::diag::SourceResult<::typst::model::Content> {
- let mut node = Self(::typst::model::Content::new(<Self as ::typst::model::Node>::id()));
- #(#handlers)*
- Ok(node.0)
- }
- }
- }
-}
-
-/// Create the node's `Set` implementation.
-fn create_set_impl(node: &Node) -> TokenStream {
- let ident = &node.ident;
- let handlers = node
- .fields
- .iter()
- .filter(|field| {
- !field.external
- && !field.synthesized
- && field.settable()
- && (!field.internal || field.parse.is_some())
- })
- .map(|field| {
- let set_ident = &field.set_ident;
- let (prefix, value) = create_field_parser(field);
- quote! {
- #prefix
- if let Some(value) = #value {
- styles.set(Self::#set_ident(value));
- }
- }
- });
-
- quote! {
- impl ::typst::model::Set for #ident {
- fn set(
- args: &mut ::typst::eval::Args,
- ) -> ::typst::diag::SourceResult<::typst::model::StyleMap> {
- let mut styles = ::typst::model::StyleMap::new();
- #(#handlers)*
- Ok(styles)
- }
- }
- }
-}
-
-/// Create argument parsing code for a field.
-fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) {
- 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 {
- quote! { args.expect(#name)? }
- } else if field.positional {
- quote! { args.find()? }
- } else {
- quote! { args.named(#name)? }
- };
-
- (quote! {}, value)
-}