summaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
Diffstat (limited to 'macros')
-rw-r--r--macros/Cargo.toml1
-rw-r--r--macros/src/castable.rs2
-rw-r--r--macros/src/func.rs122
-rw-r--r--macros/src/lib.rs11
-rw-r--r--macros/src/node.rs47
5 files changed, 133 insertions, 50 deletions
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index 20ca0262..679a5bec 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -14,3 +14,4 @@ bench = false
proc-macro2 = "1"
quote = "1"
syn = { version = "1", features = ["full"] }
+unscanny = "0.1"
diff --git a/macros/src/castable.rs b/macros/src/castable.rs
index 48cdf9e1..6738ee1d 100644
--- a/macros/src/castable.rs
+++ b/macros/src/castable.rs
@@ -150,7 +150,7 @@ fn create_describe_func(castable: &Castable) -> TokenStream {
let mut infos = vec![];
for cast in &castable.casts {
- let docs = doc_comment(&cast.attrs);
+ let docs = documentation(&cast.attrs);
infos.push(match &cast.pattern {
Pattern::Str(lit) => {
quote! { ::typst::model::CastInfo::Value(#lit.into(), #docs) }
diff --git a/macros/src/func.rs b/macros/src/func.rs
index 4523d48a..98fca6a4 100644
--- a/macros/src/func.rs
+++ b/macros/src/func.rs
@@ -1,36 +1,26 @@
+use proc_macro2::Span;
+use unscanny::Scanner;
+
use super::*;
/// Expand the `#[func]` macro.
pub fn func(item: syn::Item) -> Result<TokenStream> {
- let doc_comment = match &item {
- syn::Item::Struct(item) => doc_comment(&item.attrs),
- syn::Item::Enum(item) => doc_comment(&item.attrs),
- syn::Item::Fn(item) => doc_comment(&item.attrs),
+ 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 mut tags = vec![];
- let mut kept = vec![];
- for line in doc_comment.lines() {
- let line = line.trim();
- if let Some(suffix) = line.trim_end_matches(".").strip_prefix("Tags: ") {
- tags.extend(suffix.split(", "));
- } else {
- kept.push(line);
- }
- }
-
- while kept.last().map_or(false, |line| line.is_empty()) {
- kept.pop();
- }
-
- let docs = kept.join("\n");
+ let tags = tags(&mut docs);
+ let params = params(&mut docs)?;
+ let docs = docs.trim();
let info = quote! {
::typst::model::FuncInfo {
name,
docs: #docs,
tags: &[#(#tags),*],
- params: ::std::vec![],
+ params: ::std::vec![#(#params),*],
}
};
@@ -83,3 +73,93 @@ pub fn func(item: syn::Item) -> Result<TokenStream> {
})
}
}
+
+/// Extract a section.
+pub fn section(docs: &mut String, title: &str) -> Option<String> {
+ let needle = format!("# {title}\n");
+ let start = docs.find(&needle)?;
+ let rest = &docs[start..];
+ let len = rest[1..].find('#').map(|x| 1 + x).unwrap_or(rest.len());
+ let end = start + len;
+ let section = docs[start + needle.len()..].to_owned();
+ docs.replace_range(start..end, "");
+ Some(section)
+}
+
+/// Parse the tag section.
+pub fn tags(docs: &mut String) -> Vec<String> {
+ section(docs, "Tags")
+ .unwrap_or_default()
+ .lines()
+ .filter_map(|line| line.strip_prefix('-'))
+ .map(|s| s.trim().into())
+ .collect()
+}
+
+/// Parse the parameter section.
+pub fn params(docs: &mut String) -> Result<Vec<TokenStream>> {
+ let Some(section) = section(docs, "Parameters") else { return Ok(vec![]) };
+ let mut s = Scanner::new(&section);
+ let mut infos = vec![];
+
+ while s.eat_if('-') {
+ s.eat_whitespace();
+ let name = s.eat_until(':');
+ s.expect(": ");
+ let ty: syn::Type = syn::parse_str(s.eat_until(char::is_whitespace))?;
+ s.eat_whitespace();
+ let mut named = false;
+ let mut positional = false;
+ let mut required = false;
+ let mut variadic = false;
+ let mut settable = false;
+ s.expect('(');
+ for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) {
+ match part {
+ "named" => named = true,
+ "positional" => positional = true,
+ "required" => required = true,
+ "variadic" => variadic = true,
+ "settable" => settable = true,
+ _ => {
+ return Err(syn::Error::new(
+ Span::call_site(),
+ format!("unknown parameter flag {:?}", part),
+ ))
+ }
+ }
+ }
+
+ if (!named && !positional)
+ || (variadic && !positional)
+ || (named && variadic)
+ || (required && variadic)
+ {
+ return Err(syn::Error::new(
+ Span::call_site(),
+ "invalid combination of parameter flags",
+ ));
+ }
+
+ s.expect(')');
+ let docs = dedent(s.eat_until("\n-").trim());
+ infos.push(quote! {
+ ::typst::model::ParamInfo {
+ name: #name,
+ docs: #docs,
+ cast: <#ty as ::typst::model::Cast<
+ ::typst::syntax::Spanned<::typst::model::Value>
+ >>::describe(),
+ named: #named,
+ positional: #positional,
+ required: #required,
+ variadic: #variadic,
+ settable: #settable,
+ }
+ });
+
+ s.eat_whitespace();
+ }
+
+ Ok(infos)
+}
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 15dc3ee7..a03dc30e 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -64,7 +64,7 @@ pub fn castable(stream: BoundaryStream) -> BoundaryStream {
}
/// Extract documentation comments from an attribute list.
-fn doc_comment(attrs: &[syn::Attribute]) -> String {
+fn documentation(attrs: &[syn::Attribute]) -> String {
let mut doc = String::new();
// Parse doc comments.
@@ -72,7 +72,9 @@ fn doc_comment(attrs: &[syn::Attribute]) -> String {
if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() {
if meta.path.is_ident("doc") {
if let syn::Lit::Str(string) = &meta.lit {
- doc.push_str(&string.value());
+ let full = string.value();
+ let line = full.strip_prefix(' ').unwrap_or(&full);
+ doc.push_str(line);
doc.push('\n');
}
}
@@ -81,3 +83,8 @@ fn doc_comment(attrs: &[syn::Attribute]) -> String {
doc.trim().into()
}
+
+/// Dedent documentation text.
+fn dedent(text: &str) -> String {
+ text.lines().map(str::trim).collect::<Vec<_>>().join("\n")
+}
diff --git a/macros/src/node.rs b/macros/src/node.rs
index ad079c0e..1e03c441 100644
--- a/macros/src/node.rs
+++ b/macros/src/node.rs
@@ -36,7 +36,6 @@ struct Property {
shorthand: Option<Shorthand>,
resolve: bool,
fold: bool,
- reflect: bool,
}
/// The shorthand form of a style property.
@@ -118,7 +117,6 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result<Property> {
let mut referenced = false;
let mut resolve = false;
let mut fold = false;
- let mut reflect = false;
// Parse the `#[property(..)]` attribute.
let mut stream = tokens.into_iter().peekable();
@@ -152,7 +150,6 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result<Property> {
"referenced" => referenced = true,
"resolve" => resolve = true,
"fold" => fold = true,
- "reflect" => reflect = true,
_ => bail!(ident, "invalid attribute"),
}
}
@@ -192,7 +189,6 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result<Property> {
referenced,
resolve,
fold,
- reflect,
})
}
@@ -275,9 +271,9 @@ fn create_node_construct_func(node: &Node) -> syn::ImplItemMethod {
parse_quote! {
fn construct(
_: &::typst::model::Vm,
- _: &mut ::typst::model::Args,
+ args: &mut ::typst::model::Args,
) -> ::typst::diag::SourceResult<::typst::model::Content> {
- unimplemented!()
+ ::typst::diag::bail!(args.span, "cannot be constructed manually");
}
}
})
@@ -335,27 +331,26 @@ fn create_node_set_func(node: &Node) -> syn::ImplItemMethod {
/// Create the node's `properties` function.
fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod {
- let infos = node
- .properties
- .iter()
- .filter(|p| !p.skip || p.reflect)
- .map(|property| {
- let name = property.name.to_string().replace('_', "-").to_lowercase();
- let docs = doc_comment(&property.attrs);
- let value_ty = &property.value_ty;
- let shorthand = matches!(property.shorthand, Some(Shorthand::Positional));
- quote! {
- ::typst::model::ParamInfo {
- name: #name,
- docs: #docs,
- settable: true,
- shorthand: #shorthand,
- cast: <#value_ty as ::typst::model::Cast<
- ::typst::syntax::Spanned<::typst::model::Value>
- >>::describe(),
- }
+ let infos = node.properties.iter().filter(|p| !p.skip).map(|property| {
+ let name = property.name.to_string().replace('_', "-").to_lowercase();
+ let docs = documentation(&property.attrs);
+ let value_ty = &property.value_ty;
+ let shorthand = matches!(property.shorthand, Some(Shorthand::Positional));
+ quote! {
+ ::typst::model::ParamInfo {
+ name: #name,
+ docs: #docs,
+ cast: <#value_ty as ::typst::model::Cast<
+ ::typst::syntax::Spanned<::typst::model::Value>
+ >>::describe(),
+ named: true,
+ positional: #shorthand,
+ required: false,
+ variadic: false,
+ settable: true,
}
- });
+ }
+ });
parse_quote! {
fn properties() -> ::std::vec::Vec<::typst::model::ParamInfo>