summaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-07 15:17:13 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-07 15:17:13 +0100
commit25b5bd117529cd04bb789e1988eb3a3db8025a0e (patch)
tree2fbb4650903123da047a1f1f11a0abda95286e12 /macros
parent6ab7760822ccd24b4ef126d4737d41f1be15fe19 (diff)
Fully untyped model
Diffstat (limited to 'macros')
-rw-r--r--macros/src/capable.rs48
-rw-r--r--macros/src/castable.rs135
-rw-r--r--macros/src/func.rs16
-rw-r--r--macros/src/lib.rs88
-rw-r--r--macros/src/node.rs796
-rw-r--r--macros/src/symbols.rs84
-rw-r--r--macros/src/util.rs88
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()
+}