summaryrefslogtreecommitdiff
path: root/macros/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-09 14:17:24 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-09 14:42:14 +0100
commitc38d72383d2068361d635d6c1c78ba97aa917801 (patch)
treee758418a2d704d69dee88faf4a9a9c69b25b47ca /macros/src
parentd7a65fa26d131179d9d82226e5ee1b562084e48a (diff)
Make all optional fields settable
Diffstat (limited to 'macros/src')
-rw-r--r--macros/src/func.rs9
-rw-r--r--macros/src/node.rs471
2 files changed, 217 insertions, 263 deletions
diff --git a/macros/src/func.rs b/macros/src/func.rs
index 9889b3ae..843c193e 100644
--- a/macros/src/func.rs
+++ b/macros/src/func.rs
@@ -143,8 +143,8 @@ fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> {
for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) {
match part {
- "named" => named = true,
"positional" => positional = true,
+ "named" => named = true,
"required" => required = true,
"variadic" => variadic = true,
"settable" => settable = true,
@@ -152,8 +152,7 @@ fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> {
}
}
- if (!named && !positional) || (variadic && !positional) || (required && variadic)
- {
+ if (!named && !positional) || (variadic && !positional) {
bail!(callsite, "invalid combination of parameter flags");
}
@@ -169,10 +168,10 @@ fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> {
cast: <#ty as ::typst::eval::Cast<
::typst::syntax::Spanned<::typst::eval::Value>
>>::describe(),
- named: #named,
positional: #positional,
- required: #required,
+ named: #named,
variadic: #variadic,
+ required: #required,
settable: #settable,
}
});
diff --git a/macros/src/node.rs b/macros/src/node.rs
index a7298841..739cc79d 100644
--- a/macros/src/node.rs
+++ b/macros/src/node.rs
@@ -12,48 +12,64 @@ struct Node {
ident: Ident,
name: String,
capable: Vec<Ident>,
- set: Option<syn::Block>,
fields: Vec<Field>,
}
+impl Node {
+ fn inherent(&self) -> impl Iterator<Item = &Field> {
+ self.fields.iter().filter(|field| field.inherent())
+ }
+
+ fn settable(&self) -> impl Iterator<Item = &Field> {
+ self.fields.iter().filter(|field| field.settable())
+ }
+}
+
struct Field {
attrs: Vec<syn::Attribute>,
vis: syn::Visibility,
+
+ name: String,
ident: Ident,
ident_in: Ident,
with_ident: Ident,
set_ident: Ident,
- name: String,
+ internal: bool,
positional: bool,
required: bool,
variadic: bool,
-
- named: bool,
- shorthand: Option<Shorthand>,
-
- settable: bool,
fold: bool,
resolve: bool,
- skip: bool,
+ parse: Option<FieldParser>,
ty: syn::Type,
output: syn::Type,
- default: Option<syn::Expr>,
+ default: syn::Expr,
}
-enum Shorthand {
- Positional,
- Named(Ident),
-}
+impl Field {
+ fn inherent(&self) -> bool {
+ self.required || self.variadic
+ }
-impl Node {
- fn inherent(&self) -> impl Iterator<Item = &Field> + Clone {
- self.fields.iter().filter(|field| !field.settable)
+ fn settable(&self) -> bool {
+ !self.inherent()
}
+}
+
+struct FieldParser {
+ prefix: Vec<syn::Stmt>,
+ expr: syn::Stmt,
+}
- fn settable(&self) -> impl Iterator<Item = &Field> + Clone {
- self.fields.iter().filter(|field| field.settable)
+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 })
}
}
@@ -70,34 +86,30 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
};
let mut attrs = field.attrs.clone();
+ let variadic = has_attr(&mut attrs, "variadic");
+
let mut field = Field {
vis: field.vis.clone(),
+
+ name: kebab_case(&ident),
ident: ident.clone(),
ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
- 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: has_attr(&mut attrs, "settable"),
+ internal: has_attr(&mut attrs, "internal"),
+ positional: has_attr(&mut attrs, "positional") || variadic,
+ required: has_attr(&mut attrs, "required") || variadic,
+ variadic,
fold: has_attr(&mut attrs, "fold"),
resolve: has_attr(&mut attrs, "resolve"),
- skip: has_attr(&mut attrs, "skip"),
+ parse: parse_attr(&mut attrs, "parse")?.flatten(),
ty: field.ty.clone(),
output: field.ty.clone(),
- default: parse_attr(&mut attrs, "default")?.map(|opt| {
- opt.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() })
- }),
+ default: parse_attr(&mut attrs, "default")?
+ .flatten()
+ .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }),
attrs: {
validate_attrs(&attrs)?;
@@ -105,6 +117,14 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
},
};
+ 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 };
@@ -114,14 +134,6 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
field.output = parse_quote! { <#output as ::typst::model::Fold>::Output };
}
- 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);
}
@@ -130,14 +142,13 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
.into_iter()
.collect();
- let mut attrs = body.attrs.clone();
+ let 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
@@ -153,10 +164,10 @@ fn create(node: &Node) -> TokenStream {
// Inherent methods and functions.
let new = create_new_func(node);
- let field_methods = node.inherent().map(create_field_method);
- let with_fields_methods = node.inherent().map(create_with_field_method);
+ let field_methods = node.fields.iter().map(create_field_method);
let field_in_methods = node.settable().map(create_field_in_method);
- let field_style_methods = node.settable().map(create_field_style_method);
+ let with_fields_methods = node.fields.iter().map(create_with_field_method);
+ let field_style_methods = node.settable().map(create_set_field_method);
// Trait implementations.
let construct = node
@@ -177,8 +188,8 @@ fn create(node: &Node) -> TokenStream {
impl #ident {
#new
#(#field_methods)*
- #(#with_fields_methods)*
#(#field_in_methods)*
+ #(#with_fields_methods)*
#(#field_style_methods)*
/// The node's span.
@@ -201,121 +212,90 @@ fn create(node: &Node) -> TokenStream {
/// 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;
+ let params = node.inherent().map(|Field { ident, ty, .. }| {
quote! { #ident: #ty }
});
- let pushes = relevant.map(|field| {
- let ident = &field.ident;
- let with_ident = &field.with_ident;
+ let builder_calls = node.inherent().map(|Field { ident, 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)*
+ #(#builder_calls)*
}
}
}
/// Create an accessor methods for a field.
fn create_field_method(field: &Field) -> TokenStream {
- let Field { attrs, vis, ident, name, ty, .. } = field;
- quote! {
- #(#attrs)*
- #vis fn #ident(&self) -> #ty {
- self.0.cast_field(#name)
+ let Field { attrs, vis, ident, name, output, .. } = field;
+ if field.inherent() {
+ quote! {
+ #(#attrs)*
+ #vis fn #ident(&self) -> #output {
+ self.0.cast_required_field(#name)
+ }
+ }
+ } else {
+ let access =
+ create_style_chain_access(field, quote! { self.0.field(#name).cloned() });
+ quote! {
+ #(#attrs)*
+ #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output {
+ #access
+ }
}
}
}
-/// 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);
+/// 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 #with_ident(mut self, #ident: #ty) -> Self {
- Self(self.0.with_field(#name, #ident))
+ #vis fn #ident_in(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, ty, output, default, .. } = field;
-
- let doc = format!("Access the `{}` field in the given style chain.", name);
- let args = quote! { ::typst::model::NodeId::of::<Self>(), #name };
-
- let body = if field.fold && field.resolve {
- quote! {
- fn next(
- mut values: impl ::std::iter::Iterator<Item = #ty>,
- styles: ::typst::model::StyleChain,
- ) -> #output {
- values
- .next()
- .map(|value| {
- ::typst::model::Fold::fold(
- ::typst::model::Resolve::resolve(value, styles),
- next(values, styles),
- )
- })
- .unwrap_or_else(|| #default)
- }
- next(styles.properties(#args), styles)
- }
- } else if field.fold {
- quote! {
- fn next(
- mut values: impl ::std::iter::Iterator<Item = #ty>,
- styles: ::typst::model::StyleChain,
- ) -> #output {
- values
- .next()
- .map(|value| {
- ::typst::model::Fold::fold(value, next(values, styles))
- })
- .unwrap_or_else(|| #default)
- }
- next(styles.properties(#args), styles)
- }
- } else if field.resolve {
- quote! {
- ::typst::model::Resolve::resolve(
- styles.property::<#ty>(#args).unwrap_or_else(|| #default),
- styles
- )
- }
- } else {
- quote! {
- styles.property(#args).unwrap_or_else(|| #default)
- }
+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]
- #[allow(unused)]
- #vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output {
- #body
+ #vis fn #with_ident(mut self, #ident: #ty) -> Self {
+ Self(self.0.with_field(#name, #ident))
}
}
}
-/// Create a style creation method for a field.
-fn create_field_style_method(field: &Field) -> TokenStream {
+/// 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! {
@@ -335,8 +315,17 @@ fn create_node_impl(node: &Node) -> TokenStream {
let ident = &node.ident;
let name = &node.name;
let vtable_func = create_vtable_func(node);
+ let infos = node
+ .fields
+ .iter()
+ .filter(|field| !field.internal)
+ .map(create_param_info);
quote! {
impl ::typst::model::Node for #ident {
+ fn pack(self) -> ::typst::model::Content {
+ self.0
+ }
+
fn id() -> ::typst::model::NodeId {
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
name: #name,
@@ -345,179 +334,145 @@ fn create_node_impl(node: &Node) -> TokenStream {
::typst::model::NodeId::from_meta(&META)
}
- fn pack(self) -> ::typst::model::Content {
- self.0
+ fn params() -> ::std::vec::Vec<::typst::eval::ParamInfo> {
+ ::std::vec![#(#infos),*]
}
}
}
}
-/// Create the node's metadata vtable.
+/// Create the node's casting vtable.
fn create_vtable_func(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
- )
- });
- }
- }
- });
+ 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>());
#(#checks)*
None
}
}
}
+/// Create a parameter info for a field.
+fn create_param_info(field: &Field) -> TokenStream {
+ let Field { name, positional, variadic, required, ty, .. } = field;
+ let named = !positional;
+ let settable = field.settable();
+ let docs = documentation(&field.attrs);
+ let docs = docs.trim();
+ 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 shorthands = create_construct_shorthands(node);
- let builders = node.inherent().map(create_construct_builder_call);
+ let handlers = node
+ .fields
+ .iter()
+ .filter(|field| !field.internal || field.parse.is_some())
+ .map(|field| {
+ let with_ident = &field.with_ident;
+ let (prefix, value) = create_field_parser(field);
+ if field.settable() {
+ quote! {
+ #prefix
+ if let Some(value) = #value {
+ node = node.#with_ident(value);
+ }
+ }
+ } else {
+ quote! {
+ #prefix
+ node = node.#with_ident(#value);
+ }
+ }
+ });
+
quote! {
impl ::typst::model::Construct for #ident {
fn construct(
- _: &::typst::eval::Vm,
+ vm: &::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)*))
+ let mut node = Self(::typst::model::Content::new::<Self>());
+ #(#handlers)*
+ Ok(node.0)
}
}
}
}
-/// 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)
+ let handlers = node
+ .fields
+ .iter()
+ .filter(|field| field.settable() && (!field.internal || field.parse.is_some()))
.map(|field| {
- let name = &field.name;
let set_ident = &field.set_ident;
- 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()) }
+ let (prefix, value) = create_field_parser(field);
+ quote! {
+ #prefix
+ if let Some(value) = #value {
+ styles.set(Self::#set_ident(value));
}
- None => quote! { args.named(#name)? },
- };
-
- quote! { styles.set_opt(#value.map(Self::#set_ident)); }
- })
- .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)*
+ #(#handlers)*
Ok(styles)
}
-
- fn properties() -> ::std::vec::Vec<::typst::eval::ParamInfo> {
- ::std::vec![#(#infos),*]
- }
}
}
}
+
+/// Create argument parsing code for a field.
+fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) {
+ let name = &field.name;
+ if let Some(FieldParser { prefix, expr }) = &field.parse {
+ return (quote! { #(#prefix);* }, quote! { #expr });
+ }
+
+ let 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)
+}