mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Better proc macros
This commit is contained in:
parent
921b40cf9c
commit
8f36fca684
@ -1,7 +1,9 @@
|
|||||||
|
use heck::ToKebabCase;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Expand the `#[derive(Cast)]` macro.
|
/// Expand the `#[derive(Cast)]` macro.
|
||||||
pub fn derive_cast(item: &DeriveInput) -> Result<TokenStream> {
|
pub fn derive_cast(item: DeriveInput) -> Result<TokenStream> {
|
||||||
let ty = &item.ident;
|
let ty = &item.ident;
|
||||||
|
|
||||||
let syn::Data::Enum(data) = &item.data else {
|
let syn::Data::Enum(data) = &item.data else {
|
||||||
@ -19,7 +21,7 @@ pub fn derive_cast(item: &DeriveInput) -> Result<TokenStream> {
|
|||||||
{
|
{
|
||||||
attr.parse_args::<syn::LitStr>()?.value()
|
attr.parse_args::<syn::LitStr>()?.value()
|
||||||
} else {
|
} else {
|
||||||
kebab_case(&variant.ident)
|
variant.ident.to_string().to_kebab_case()
|
||||||
};
|
};
|
||||||
|
|
||||||
variants.push(Variant {
|
variants.push(Variant {
|
||||||
@ -62,20 +64,25 @@ struct Variant {
|
|||||||
|
|
||||||
/// Expand the `cast!` macro.
|
/// Expand the `cast!` macro.
|
||||||
pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
||||||
let input: CastInput = syn::parse2(stream)?;
|
|
||||||
let ty = &input.ty;
|
|
||||||
let eval = quote! { ::typst::eval };
|
let eval = quote! { ::typst::eval };
|
||||||
|
|
||||||
|
let input: CastInput = syn::parse2(stream)?;
|
||||||
|
let ty = &input.ty;
|
||||||
let castable_body = create_castable_body(&input);
|
let castable_body = create_castable_body(&input);
|
||||||
let describe_body = create_describe_body(&input);
|
let input_body = create_input_body(&input);
|
||||||
|
let output_body = create_output_body(&input);
|
||||||
let into_value_body = create_into_value_body(&input);
|
let into_value_body = create_into_value_body(&input);
|
||||||
let from_value_body = create_from_value_body(&input);
|
let from_value_body = create_from_value_body(&input);
|
||||||
|
|
||||||
let reflect = (!input.from_value.is_empty() || input.name.is_some()).then(|| {
|
let reflect = (!input.from_value.is_empty() || input.dynamic).then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
impl #eval::Reflect for #ty {
|
impl #eval::Reflect for #ty {
|
||||||
fn describe() -> #eval::CastInfo {
|
fn input() -> #eval::CastInfo {
|
||||||
#describe_body
|
#input_body
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output() -> #eval::CastInfo {
|
||||||
|
#output_body
|
||||||
}
|
}
|
||||||
|
|
||||||
fn castable(value: &#eval::Value) -> bool {
|
fn castable(value: &#eval::Value) -> bool {
|
||||||
@ -85,7 +92,7 @@ pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let into_value = (input.into_value.is_some() || input.name.is_some()).then(|| {
|
let into_value = (input.into_value.is_some() || input.dynamic).then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
impl #eval::IntoValue for #ty {
|
impl #eval::IntoValue for #ty {
|
||||||
fn into_value(self) -> #eval::Value {
|
fn into_value(self) -> #eval::Value {
|
||||||
@ -95,7 +102,7 @@ pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let from_value = (!input.from_value.is_empty() || input.name.is_some()).then(|| {
|
let from_value = (!input.from_value.is_empty() || input.dynamic).then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
impl #eval::FromValue for #ty {
|
impl #eval::FromValue for #ty {
|
||||||
fn from_value(value: #eval::Value) -> ::typst::diag::StrResult<Self> {
|
fn from_value(value: #eval::Value) -> ::typst::diag::StrResult<Self> {
|
||||||
@ -105,55 +112,42 @@ pub fn cast(stream: TokenStream) -> Result<TokenStream> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let ty = input.name.as_ref().map(|name| {
|
|
||||||
quote! {
|
|
||||||
impl #eval::Type for #ty {
|
|
||||||
const TYPE_NAME: &'static str = #name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#reflect
|
#reflect
|
||||||
#into_value
|
#into_value
|
||||||
#from_value
|
#from_value
|
||||||
#ty
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The input to `cast!`.
|
/// The input to `cast!`.
|
||||||
struct CastInput {
|
struct CastInput {
|
||||||
ty: syn::Type,
|
ty: syn::Type,
|
||||||
name: Option<syn::LitStr>,
|
dynamic: bool,
|
||||||
into_value: Option<syn::Expr>,
|
into_value: Option<syn::Expr>,
|
||||||
from_value: Punctuated<Cast, Token![,]>,
|
from_value: Punctuated<Cast, Token![,]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for CastInput {
|
impl Parse for CastInput {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let ty;
|
let mut dynamic = false;
|
||||||
let mut name = None;
|
|
||||||
if input.peek(syn::Token![type]) {
|
if input.peek(syn::Token![type]) {
|
||||||
let _: syn::Token![type] = input.parse()?;
|
let _: syn::Token![type] = input.parse()?;
|
||||||
ty = input.parse()?;
|
dynamic = true;
|
||||||
let _: syn::Token![:] = input.parse()?;
|
|
||||||
name = Some(input.parse()?);
|
|
||||||
} else {
|
|
||||||
ty = input.parse()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ty = input.parse()?;
|
||||||
let _: syn::Token![,] = input.parse()?;
|
let _: syn::Token![,] = input.parse()?;
|
||||||
|
|
||||||
let mut into_value = None;
|
let mut to_value = None;
|
||||||
if input.peek(syn::Token![self]) {
|
if input.peek(syn::Token![self]) {
|
||||||
let _: syn::Token![self] = input.parse()?;
|
let _: syn::Token![self] = input.parse()?;
|
||||||
let _: syn::Token![=>] = input.parse()?;
|
let _: syn::Token![=>] = input.parse()?;
|
||||||
into_value = Some(input.parse()?);
|
to_value = Some(input.parse()?);
|
||||||
let _: syn::Token![,] = input.parse()?;
|
let _: syn::Token![,] = input.parse()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let from_value = Punctuated::parse_terminated(input)?;
|
let from_value = Punctuated::parse_terminated(input)?;
|
||||||
Ok(Self { ty, name, into_value, from_value })
|
Ok(Self { ty, dynamic, into_value: to_value, from_value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +206,7 @@ fn create_castable_body(input: &CastInput) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dynamic_check = input.name.is_some().then(|| {
|
let dynamic_check = input.dynamic.then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
if let ::typst::eval::Value::Dyn(dynamic) = &value {
|
if let ::typst::eval::Value::Dyn(dynamic) = &value {
|
||||||
if dynamic.is::<Self>() {
|
if dynamic.is::<Self>() {
|
||||||
@ -241,7 +235,7 @@ fn create_castable_body(input: &CastInput) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_describe_body(input: &CastInput) -> TokenStream {
|
fn create_input_body(input: &CastInput) -> TokenStream {
|
||||||
let mut infos = vec![];
|
let mut infos = vec![];
|
||||||
|
|
||||||
for cast in &input.from_value {
|
for cast in &input.from_value {
|
||||||
@ -256,14 +250,14 @@ fn create_describe_body(input: &CastInput) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Pattern::Ty(_, ty) => {
|
Pattern::Ty(_, ty) => {
|
||||||
quote! { <#ty as ::typst::eval::Reflect>::describe() }
|
quote! { <#ty as ::typst::eval::Reflect>::input() }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = &input.name {
|
if input.dynamic {
|
||||||
infos.push(quote! {
|
infos.push(quote! {
|
||||||
::typst::eval::CastInfo::Type(#name)
|
::typst::eval::CastInfo::Type(::typst::eval::Type::of::<Self>())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,6 +266,14 @@ fn create_describe_body(input: &CastInput) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_output_body(input: &CastInput) -> TokenStream {
|
||||||
|
if input.dynamic {
|
||||||
|
quote! { ::typst::eval::CastInfo::Type(::typst::eval::Type::of::<Self>()) }
|
||||||
|
} else {
|
||||||
|
quote! { Self::input() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn create_into_value_body(input: &CastInput) -> TokenStream {
|
fn create_into_value_body(input: &CastInput) -> TokenStream {
|
||||||
if let Some(expr) = &input.into_value {
|
if let Some(expr) = &input.into_value {
|
||||||
quote! { #expr }
|
quote! { #expr }
|
||||||
@ -301,7 +303,7 @@ fn create_from_value_body(input: &CastInput) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dynamic_check = input.name.is_some().then(|| {
|
let dynamic_check = input.dynamic.then(|| {
|
||||||
quote! {
|
quote! {
|
||||||
if let ::typst::eval::Value::Dyn(dynamic) = &value {
|
if let ::typst::eval::Value::Dyn(dynamic) = &value {
|
||||||
if let Some(concrete) = dynamic.downcast::<Self>() {
|
if let Some(concrete) = dynamic.downcast::<Self>() {
|
@ -1,152 +1,175 @@
|
|||||||
|
use heck::ToKebabCase;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Expand the `#[element]` macro.
|
/// Expand the `#[elem]` macro.
|
||||||
pub fn element(stream: TokenStream, body: &syn::ItemStruct) -> Result<TokenStream> {
|
pub fn elem(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
|
||||||
let element = prepare(stream, body)?;
|
let element = parse(stream, &body)?;
|
||||||
Ok(create(&element))
|
Ok(create(&element))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Details about an element.
|
||||||
struct Elem {
|
struct Elem {
|
||||||
name: String,
|
name: String,
|
||||||
display: String,
|
title: String,
|
||||||
category: String,
|
scope: bool,
|
||||||
keywords: Option<String>,
|
keywords: Vec<String>,
|
||||||
docs: String,
|
docs: String,
|
||||||
vis: syn::Visibility,
|
vis: syn::Visibility,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
capable: Vec<Ident>,
|
capabilities: Vec<Ident>,
|
||||||
fields: Vec<Field>,
|
fields: Vec<Field>,
|
||||||
scope: Option<BlockWithReturn>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Details about an element field.
|
||||||
struct 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<BlockWithReturn>,
|
|
||||||
default: syn::Expr,
|
|
||||||
vis: syn::Visibility,
|
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
ident_in: Ident,
|
ident_in: Ident,
|
||||||
with_ident: Ident,
|
with_ident: Ident,
|
||||||
push_ident: Ident,
|
push_ident: Ident,
|
||||||
set_ident: Ident,
|
set_ident: Ident,
|
||||||
|
vis: syn::Visibility,
|
||||||
ty: syn::Type,
|
ty: syn::Type,
|
||||||
output: syn::Type,
|
output: syn::Type,
|
||||||
|
name: String,
|
||||||
|
docs: String,
|
||||||
|
positional: bool,
|
||||||
|
required: bool,
|
||||||
|
variadic: bool,
|
||||||
|
resolve: bool,
|
||||||
|
fold: bool,
|
||||||
|
internal: bool,
|
||||||
|
external: bool,
|
||||||
|
synthesized: bool,
|
||||||
|
parse: Option<BlockWithReturn>,
|
||||||
|
default: syn::Expr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Field {
|
impl Field {
|
||||||
|
/// Whether the field is present on every instance of the element.
|
||||||
fn inherent(&self) -> bool {
|
fn inherent(&self) -> bool {
|
||||||
self.required || self.variadic
|
self.required || self.variadic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the field can be used with set rules.
|
||||||
fn settable(&self) -> bool {
|
fn settable(&self) -> bool {
|
||||||
!self.inherent()
|
!self.inherent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preprocess the element's definition.
|
/// The `..` in `#[elem(..)]`.
|
||||||
fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> {
|
struct Meta {
|
||||||
|
scope: bool,
|
||||||
|
name: Option<String>,
|
||||||
|
title: Option<String>,
|
||||||
|
keywords: Vec<String>,
|
||||||
|
capabilities: Vec<Ident>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Meta {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
scope: parse_flag::<kw::scope>(input)?,
|
||||||
|
name: parse_string::<kw::name>(input)?,
|
||||||
|
title: parse_string::<kw::title>(input)?,
|
||||||
|
keywords: parse_string_array::<kw::keywords>(input)?,
|
||||||
|
capabilities: Punctuated::<Ident, Token![,]>::parse_terminated(input)?
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse details about the element from its struct definition.
|
||||||
|
fn parse(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> {
|
||||||
|
let meta: Meta = syn::parse2(stream)?;
|
||||||
|
let (name, title) = determine_name_and_title(
|
||||||
|
meta.name,
|
||||||
|
meta.title,
|
||||||
|
&body.ident,
|
||||||
|
Some(|base| base.trim_end_matches("Elem")),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let docs = documentation(&body.attrs);
|
||||||
|
|
||||||
let syn::Fields::Named(named) = &body.fields else {
|
let syn::Fields::Named(named) = &body.fields else {
|
||||||
bail!(body, "expected named fields");
|
bail!(body, "expected named fields");
|
||||||
};
|
};
|
||||||
|
let fields = named.named.iter().map(parse_field).collect::<Result<_>>()?;
|
||||||
|
|
||||||
let mut fields = vec![];
|
Ok(Elem {
|
||||||
for field in &named.named {
|
name,
|
||||||
let Some(ident) = field.ident.clone() else {
|
title,
|
||||||
bail!(field, "expected named field");
|
scope: meta.scope,
|
||||||
};
|
keywords: meta.keywords,
|
||||||
|
|
||||||
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 mut attrs = body.attrs.clone();
|
|
||||||
let docs = documentation(&attrs);
|
|
||||||
let mut lines = docs.split('\n').collect();
|
|
||||||
let keywords = meta_line(&mut lines, "Keywords").ok().map(Into::into);
|
|
||||||
let category = meta_line(&mut lines, "Category")?.into();
|
|
||||||
let display = meta_line(&mut lines, "Display")?.into();
|
|
||||||
let docs = lines.join("\n").trim().into();
|
|
||||||
|
|
||||||
let element = Elem {
|
|
||||||
name: body.ident.to_string().trim_end_matches("Elem").to_lowercase(),
|
|
||||||
display,
|
|
||||||
category,
|
|
||||||
keywords,
|
|
||||||
docs,
|
docs,
|
||||||
vis: body.vis.clone(),
|
vis: body.vis.clone(),
|
||||||
ident: body.ident.clone(),
|
ident: body.ident.clone(),
|
||||||
capable,
|
capabilities: meta.capabilities,
|
||||||
fields,
|
fields,
|
||||||
scope: parse_attr(&mut attrs, "scope")?.flatten(),
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_field(field: &syn::Field) -> Result<Field> {
|
||||||
|
let Some(ident) = field.ident.clone() else {
|
||||||
|
bail!(field, "expected named field");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ident == "label" {
|
||||||
|
bail!(ident, "invalid field name");
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
let mut field = Field {
|
||||||
|
name: ident.to_string().to_kebab_case(),
|
||||||
|
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)?;
|
validate_attrs(&attrs)?;
|
||||||
Ok(element)
|
|
||||||
|
Ok(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce the element's definition.
|
/// Produce the element's definition.
|
||||||
@ -166,13 +189,13 @@ fn create(element: &Elem) -> TokenStream {
|
|||||||
// Trait implementations.
|
// Trait implementations.
|
||||||
let element_impl = create_pack_impl(element);
|
let element_impl = create_pack_impl(element);
|
||||||
let construct_impl = element
|
let construct_impl = element
|
||||||
.capable
|
.capabilities
|
||||||
.iter()
|
.iter()
|
||||||
.all(|capability| capability != "Construct")
|
.all(|capability| capability != "Construct")
|
||||||
.then(|| create_construct_impl(element));
|
.then(|| create_construct_impl(element));
|
||||||
let set_impl = create_set_impl(element);
|
let set_impl = create_set_impl(element);
|
||||||
let locatable_impl = element
|
let locatable_impl = element
|
||||||
.capable
|
.capabilities
|
||||||
.iter()
|
.iter()
|
||||||
.any(|capability| capability == "Locatable")
|
.any(|capability| capability == "Locatable")
|
||||||
.then(|| quote! { impl ::typst::model::Locatable for #ident {} });
|
.then(|| quote! { impl ::typst::model::Locatable for #ident {} });
|
||||||
@ -231,7 +254,7 @@ fn create_new_func(element: &Elem) -> TokenStream {
|
|||||||
/// Create a new element.
|
/// Create a new element.
|
||||||
pub fn new(#(#params),*) -> Self {
|
pub fn new(#(#params),*) -> Self {
|
||||||
Self(::typst::model::Content::new(
|
Self(::typst::model::Content::new(
|
||||||
<Self as ::typst::model::Element>::func()
|
<Self as ::typst::model::NativeElement>::elem()
|
||||||
))
|
))
|
||||||
#(#builder_calls)*
|
#(#builder_calls)*
|
||||||
}
|
}
|
||||||
@ -285,7 +308,7 @@ fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStrea
|
|||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
styles.#getter::<#ty>(
|
styles.#getter::<#ty>(
|
||||||
<Self as ::typst::model::Element>::func(),
|
<Self as ::typst::model::NativeElement>::elem(),
|
||||||
#name,
|
#name,
|
||||||
#inherent,
|
#inherent,
|
||||||
|| #default,
|
|| #default,
|
||||||
@ -325,7 +348,7 @@ fn create_set_field_method(field: &Field) -> TokenStream {
|
|||||||
#[doc = #doc]
|
#[doc = #doc]
|
||||||
#vis fn #set_ident(#ident: #ty) -> ::typst::model::Style {
|
#vis fn #set_ident(#ident: #ty) -> ::typst::model::Style {
|
||||||
::typst::model::Style::Property(::typst::model::Property::new(
|
::typst::model::Style::Property(::typst::model::Property::new(
|
||||||
<Self as ::typst::model::Element>::func(),
|
<Self as ::typst::model::NativeElement>::elem(),
|
||||||
#name,
|
#name,
|
||||||
#ident,
|
#ident,
|
||||||
))
|
))
|
||||||
@ -335,49 +358,54 @@ fn create_set_field_method(field: &Field) -> TokenStream {
|
|||||||
|
|
||||||
/// Create the element's `Pack` implementation.
|
/// Create the element's `Pack` implementation.
|
||||||
fn create_pack_impl(element: &Elem) -> TokenStream {
|
fn create_pack_impl(element: &Elem) -> TokenStream {
|
||||||
let Elem { ident, name, display, keywords, category, docs, .. } = element;
|
let eval = quote! { ::typst::eval };
|
||||||
|
let model = quote! { ::typst::model };
|
||||||
|
|
||||||
|
let Elem { name, ident, title, scope, keywords, docs, .. } = element;
|
||||||
let vtable_func = create_vtable_func(element);
|
let vtable_func = create_vtable_func(element);
|
||||||
let infos = element
|
let params = element
|
||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|field| !field.internal && !field.synthesized)
|
.filter(|field| !field.internal && !field.synthesized)
|
||||||
.map(create_param_info);
|
.map(create_param_info);
|
||||||
let scope = create_scope_builder(element.scope.as_ref());
|
|
||||||
let keywords = quote_option(keywords);
|
let scope = if *scope {
|
||||||
|
quote! { <#ident as #eval::NativeScope>::scope() }
|
||||||
|
} else {
|
||||||
|
quote! { #eval::Scope::new() }
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = quote! {
|
||||||
|
#model::NativeElementData {
|
||||||
|
name: #name,
|
||||||
|
title: #title,
|
||||||
|
docs: #docs,
|
||||||
|
keywords: &[#(#keywords),*],
|
||||||
|
construct: <#ident as #model::Construct>::construct,
|
||||||
|
set: <#ident as #model::Set>::set,
|
||||||
|
vtable: #vtable_func,
|
||||||
|
scope: #eval::Lazy::new(|| #scope),
|
||||||
|
params: #eval::Lazy::new(|| ::std::vec![#(#params),*])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl ::typst::model::Element for #ident {
|
impl #model::NativeElement for #ident {
|
||||||
fn pack(self) -> ::typst::model::Content {
|
fn data() -> &'static #model::NativeElementData {
|
||||||
|
static DATA: #model::NativeElementData = #data;
|
||||||
|
&DATA
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pack(self) -> #model::Content {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unpack(content: &::typst::model::Content) -> ::std::option::Option<&Self> {
|
fn unpack(content: &#model::Content) -> ::std::option::Option<&Self> {
|
||||||
// Safety: Elements are #[repr(transparent)].
|
// Safety: Elements are #[repr(transparent)].
|
||||||
content.is::<Self>().then(|| unsafe {
|
content.is::<Self>().then(|| unsafe {
|
||||||
::std::mem::transmute(content)
|
::std::mem::transmute(content)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn func() -> ::typst::model::ElemFunc {
|
|
||||||
static NATIVE: ::typst::model::NativeElemFunc = ::typst::model::NativeElemFunc {
|
|
||||||
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,
|
|
||||||
keywords: #keywords,
|
|
||||||
docs: #docs,
|
|
||||||
params: ::std::vec![#(#infos),*],
|
|
||||||
returns: ::typst::eval::CastInfo::Union(::std::vec![
|
|
||||||
::typst::eval::CastInfo::Type("content")
|
|
||||||
]),
|
|
||||||
category: #category,
|
|
||||||
scope: #scope,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
(&NATIVE).into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,7 +413,7 @@ fn create_pack_impl(element: &Elem) -> TokenStream {
|
|||||||
/// Create the element's casting vtable.
|
/// Create the element's casting vtable.
|
||||||
fn create_vtable_func(element: &Elem) -> TokenStream {
|
fn create_vtable_func(element: &Elem) -> TokenStream {
|
||||||
let ident = &element.ident;
|
let ident = &element.ident;
|
||||||
let relevant = element.capable.iter().filter(|&ident| ident != "Construct");
|
let relevant = element.capabilities.iter().filter(|&ident| ident != "Construct");
|
||||||
let checks = relevant.map(|capability| {
|
let checks = relevant.map(|capability| {
|
||||||
quote! {
|
quote! {
|
||||||
if id == ::std::any::TypeId::of::<dyn #capability>() {
|
if id == ::std::any::TypeId::of::<dyn #capability>() {
|
||||||
@ -399,7 +427,7 @@ fn create_vtable_func(element: &Elem) -> TokenStream {
|
|||||||
quote! {
|
quote! {
|
||||||
|id| {
|
|id| {
|
||||||
let null = Self(::typst::model::Content::new(
|
let null = Self(::typst::model::Content::new(
|
||||||
<#ident as ::typst::model::Element>::func()
|
<#ident as ::typst::model::NativeElement>::elem()
|
||||||
));
|
));
|
||||||
#(#checks)*
|
#(#checks)*
|
||||||
None
|
None
|
||||||
@ -441,7 +469,7 @@ fn create_param_info(field: &Field) -> TokenStream {
|
|||||||
::typst::eval::ParamInfo {
|
::typst::eval::ParamInfo {
|
||||||
name: #name,
|
name: #name,
|
||||||
docs: #docs,
|
docs: #docs,
|
||||||
cast: <#ty as ::typst::eval::Reflect>::describe(),
|
input: <#ty as ::typst::eval::Reflect>::input(),
|
||||||
default: #default,
|
default: #default,
|
||||||
positional: #positional,
|
positional: #positional,
|
||||||
named: #named,
|
named: #named,
|
||||||
@ -488,7 +516,7 @@ fn create_construct_impl(element: &Elem) -> TokenStream {
|
|||||||
args: &mut ::typst::eval::Args,
|
args: &mut ::typst::eval::Args,
|
||||||
) -> ::typst::diag::SourceResult<::typst::model::Content> {
|
) -> ::typst::diag::SourceResult<::typst::model::Content> {
|
||||||
let mut element = Self(::typst::model::Content::new(
|
let mut element = Self(::typst::model::Content::new(
|
||||||
<Self as ::typst::model::Element>::func()
|
<Self as ::typst::model::NativeElement>::elem()
|
||||||
));
|
));
|
||||||
#(#handlers)*
|
#(#handlers)*
|
||||||
Ok(element.0)
|
Ok(element.0)
|
@ -1,206 +1,340 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use heck::ToKebabCase;
|
||||||
|
|
||||||
/// Expand the `#[func]` macro.
|
/// Expand the `#[func]` macro.
|
||||||
pub fn func(stream: TokenStream, item: &syn::ItemFn) -> Result<TokenStream> {
|
pub fn func(stream: TokenStream, item: &syn::ItemFn) -> Result<TokenStream> {
|
||||||
let func = prepare(stream, item)?;
|
let func = parse(stream, item)?;
|
||||||
Ok(create(&func, item))
|
Ok(create(&func, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Details about a function.
|
||||||
struct Func {
|
struct Func {
|
||||||
name: String,
|
name: String,
|
||||||
display: String,
|
title: String,
|
||||||
category: String,
|
scope: bool,
|
||||||
keywords: Option<String>,
|
constructor: bool,
|
||||||
|
keywords: Vec<String>,
|
||||||
|
parent: Option<syn::Type>,
|
||||||
docs: String,
|
docs: String,
|
||||||
vis: syn::Visibility,
|
vis: syn::Visibility,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
ident_func: Ident,
|
special: SpecialParams,
|
||||||
parent: Option<syn::Type>,
|
params: Vec<Param>,
|
||||||
|
returns: syn::Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Special parameters provided by the runtime.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct SpecialParams {
|
||||||
|
self_: Option<Param>,
|
||||||
vm: bool,
|
vm: bool,
|
||||||
vt: bool,
|
vt: bool,
|
||||||
args: bool,
|
args: bool,
|
||||||
span: bool,
|
span: bool,
|
||||||
params: Vec<Param>,
|
|
||||||
returns: syn::Type,
|
|
||||||
scope: Option<BlockWithReturn>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Details about a function parameter.
|
||||||
struct Param {
|
struct Param {
|
||||||
name: String,
|
binding: Binding,
|
||||||
docs: String,
|
|
||||||
external: bool,
|
|
||||||
named: bool,
|
|
||||||
variadic: bool,
|
|
||||||
default: Option<syn::Expr>,
|
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
ty: syn::Type,
|
ty: syn::Type,
|
||||||
|
name: String,
|
||||||
|
docs: String,
|
||||||
|
named: bool,
|
||||||
|
variadic: bool,
|
||||||
|
external: bool,
|
||||||
|
default: Option<syn::Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(stream: TokenStream, item: &syn::ItemFn) -> Result<Func> {
|
/// How a parameter is bound.
|
||||||
let sig = &item.sig;
|
enum Binding {
|
||||||
|
/// Normal parameter.
|
||||||
|
Owned,
|
||||||
|
/// `&self`.
|
||||||
|
Ref,
|
||||||
|
/// `&mut self`.
|
||||||
|
RefMut,
|
||||||
|
}
|
||||||
|
|
||||||
let Parent(parent) = syn::parse2(stream)?;
|
/// The `..` in `#[func(..)]`.
|
||||||
|
pub struct Meta {
|
||||||
|
pub scope: bool,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub constructor: bool,
|
||||||
|
pub keywords: Vec<String>,
|
||||||
|
pub parent: Option<syn::Type>,
|
||||||
|
}
|
||||||
|
|
||||||
let mut vm = false;
|
impl Parse for Meta {
|
||||||
let mut vt = false;
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let mut args = false;
|
Ok(Self {
|
||||||
let mut span = false;
|
scope: parse_flag::<kw::scope>(input)?,
|
||||||
|
name: parse_string::<kw::name>(input)?,
|
||||||
|
title: parse_string::<kw::title>(input)?,
|
||||||
|
constructor: parse_flag::<kw::constructor>(input)?,
|
||||||
|
keywords: parse_string_array::<kw::keywords>(input)?,
|
||||||
|
parent: parse_key_value::<kw::parent, _>(input)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse details about the function from the fn item.
|
||||||
|
fn parse(stream: TokenStream, item: &syn::ItemFn) -> Result<Func> {
|
||||||
|
let meta: Meta = syn::parse2(stream)?;
|
||||||
|
let (name, title) =
|
||||||
|
determine_name_and_title(meta.name, meta.title, &item.sig.ident, None)?;
|
||||||
|
|
||||||
|
let docs = documentation(&item.attrs);
|
||||||
|
|
||||||
|
let mut special = SpecialParams::default();
|
||||||
let mut params = vec![];
|
let mut params = vec![];
|
||||||
for input in &sig.inputs {
|
for input in &item.sig.inputs {
|
||||||
let syn::FnArg::Typed(typed) = input else {
|
parse_param(&mut special, &mut params, meta.parent.as_ref(), input)?;
|
||||||
println!("option a");
|
}
|
||||||
bail!(input, "self is not allowed here");
|
|
||||||
};
|
|
||||||
|
|
||||||
let syn::Pat::Ident(syn::PatIdent {
|
let returns = match &item.sig.output {
|
||||||
by_ref: None, mutability: None, ident, ..
|
syn::ReturnType::Default => parse_quote! { () },
|
||||||
}) = &*typed.pat
|
syn::ReturnType::Type(_, ty) => ty.as_ref().clone(),
|
||||||
else {
|
};
|
||||||
bail!(typed.pat, "expected identifier");
|
|
||||||
};
|
|
||||||
|
|
||||||
match ident.to_string().as_str() {
|
if meta.parent.is_some() && meta.scope {
|
||||||
"vm" => vm = true,
|
bail!(item, "scoped function cannot have a scope");
|
||||||
"vt" => vt = true,
|
}
|
||||||
"args" => args = true,
|
|
||||||
"span" => span = true,
|
|
||||||
_ => {
|
|
||||||
let mut attrs = typed.attrs.clone();
|
|
||||||
params.push(Param {
|
|
||||||
name: kebab_case(ident),
|
|
||||||
docs: documentation(&attrs),
|
|
||||||
external: has_attr(&mut attrs, "external"),
|
|
||||||
named: has_attr(&mut attrs, "named"),
|
|
||||||
variadic: has_attr(&mut attrs, "variadic"),
|
|
||||||
default: parse_attr(&mut attrs, "default")?.map(|expr| {
|
|
||||||
expr.unwrap_or_else(
|
|
||||||
|| parse_quote! { ::std::default::Default::default() },
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
ident: ident.clone(),
|
|
||||||
ty: (*typed.ty).clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
validate_attrs(&attrs)?;
|
Ok(Func {
|
||||||
}
|
name,
|
||||||
|
title,
|
||||||
|
scope: meta.scope,
|
||||||
|
constructor: meta.constructor,
|
||||||
|
keywords: meta.keywords,
|
||||||
|
parent: meta.parent,
|
||||||
|
docs,
|
||||||
|
vis: item.vis.clone(),
|
||||||
|
ident: item.sig.ident.clone(),
|
||||||
|
special,
|
||||||
|
params,
|
||||||
|
returns,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse details about a functino parameter.
|
||||||
|
fn parse_param(
|
||||||
|
special: &mut SpecialParams,
|
||||||
|
params: &mut Vec<Param>,
|
||||||
|
parent: Option<&syn::Type>,
|
||||||
|
input: &syn::FnArg,
|
||||||
|
) -> Result<()> {
|
||||||
|
let typed = match input {
|
||||||
|
syn::FnArg::Receiver(recv) => {
|
||||||
|
let mut binding = Binding::Owned;
|
||||||
|
if recv.reference.is_some() {
|
||||||
|
if recv.mutability.is_some() {
|
||||||
|
binding = Binding::RefMut
|
||||||
|
} else {
|
||||||
|
binding = Binding::Ref
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
special.self_ = Some(Param {
|
||||||
|
binding,
|
||||||
|
ident: syn::Ident::new("self_", recv.self_token.span),
|
||||||
|
ty: match parent {
|
||||||
|
Some(ty) => ty.clone(),
|
||||||
|
None => bail!(recv, "explicit parent type required"),
|
||||||
|
},
|
||||||
|
name: "self".into(),
|
||||||
|
docs: documentation(&recv.attrs),
|
||||||
|
named: false,
|
||||||
|
variadic: false,
|
||||||
|
external: false,
|
||||||
|
default: None,
|
||||||
|
});
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
syn::FnArg::Typed(typed) => typed,
|
||||||
|
};
|
||||||
|
|
||||||
|
let syn::Pat::Ident(syn::PatIdent { by_ref: None, mutability: None, ident, .. }) =
|
||||||
|
&*typed.pat
|
||||||
|
else {
|
||||||
|
bail!(typed.pat, "expected identifier");
|
||||||
|
};
|
||||||
|
|
||||||
|
match ident.to_string().as_str() {
|
||||||
|
"vm" => special.vm = true,
|
||||||
|
"vt" => special.vt = true,
|
||||||
|
"args" => special.args = true,
|
||||||
|
"span" => special.span = true,
|
||||||
|
_ => {
|
||||||
|
let mut attrs = typed.attrs.clone();
|
||||||
|
params.push(Param {
|
||||||
|
binding: Binding::Owned,
|
||||||
|
ident: ident.clone(),
|
||||||
|
ty: (*typed.ty).clone(),
|
||||||
|
name: ident.to_string().to_kebab_case(),
|
||||||
|
docs: documentation(&attrs),
|
||||||
|
named: has_attr(&mut attrs, "named"),
|
||||||
|
variadic: has_attr(&mut attrs, "variadic"),
|
||||||
|
external: has_attr(&mut attrs, "external"),
|
||||||
|
default: parse_attr(&mut attrs, "default")?.map(|expr| {
|
||||||
|
expr.unwrap_or_else(
|
||||||
|
|| parse_quote! { ::std::default::Default::default() },
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
validate_attrs(&attrs)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut attrs = item.attrs.clone();
|
Ok(())
|
||||||
let docs = documentation(&attrs);
|
|
||||||
let mut lines = docs.split('\n').collect();
|
|
||||||
let keywords = meta_line(&mut lines, "Keywords").ok().map(Into::into);
|
|
||||||
let category = meta_line(&mut lines, "Category")?.into();
|
|
||||||
let display = meta_line(&mut lines, "Display")?.into();
|
|
||||||
let docs = lines.join("\n").trim().into();
|
|
||||||
|
|
||||||
let func = Func {
|
|
||||||
name: sig.ident.to_string().trim_end_matches('_').replace('_', "-"),
|
|
||||||
display,
|
|
||||||
category,
|
|
||||||
keywords,
|
|
||||||
docs,
|
|
||||||
vis: item.vis.clone(),
|
|
||||||
ident: sig.ident.clone(),
|
|
||||||
ident_func: Ident::new(
|
|
||||||
&format!("{}_func", sig.ident.to_string().trim_end_matches('_')),
|
|
||||||
sig.ident.span(),
|
|
||||||
),
|
|
||||||
parent,
|
|
||||||
params,
|
|
||||||
returns: match &sig.output {
|
|
||||||
syn::ReturnType::Default => parse_quote! { () },
|
|
||||||
syn::ReturnType::Type(_, ty) => ty.as_ref().clone(),
|
|
||||||
},
|
|
||||||
scope: parse_attr(&mut attrs, "scope")?.flatten(),
|
|
||||||
vm,
|
|
||||||
vt,
|
|
||||||
args,
|
|
||||||
span,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(func)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produce the function's definition.
|
||||||
fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
fn create(func: &Func, item: &syn::ItemFn) -> TokenStream {
|
||||||
|
let eval = quote! { ::typst::eval };
|
||||||
|
|
||||||
|
let Func { docs, vis, ident, .. } = func;
|
||||||
|
let item = rewrite_fn_item(item);
|
||||||
|
let ty = create_func_ty(func);
|
||||||
|
let data = create_func_data(func);
|
||||||
|
|
||||||
|
let creator = if ty.is_some() {
|
||||||
|
quote! {
|
||||||
|
impl #eval::NativeFunc for #ident {
|
||||||
|
fn data() -> &'static #eval::NativeFuncData {
|
||||||
|
static DATA: #eval::NativeFuncData = #data;
|
||||||
|
&DATA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let ident_data = quote::format_ident!("{ident}_data");
|
||||||
|
quote! {
|
||||||
|
#[doc(hidden)]
|
||||||
|
#vis fn #ident_data() -> &'static #eval::NativeFuncData {
|
||||||
|
static DATA: #eval::NativeFuncData = #data;
|
||||||
|
&DATA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[doc = #docs]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#item
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#ty
|
||||||
|
#creator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create native function data for the function.
|
||||||
|
fn create_func_data(func: &Func) -> TokenStream {
|
||||||
|
let eval = quote! { ::typst::eval };
|
||||||
|
|
||||||
let Func {
|
let Func {
|
||||||
name,
|
|
||||||
display,
|
|
||||||
category,
|
|
||||||
docs,
|
|
||||||
vis,
|
|
||||||
ident,
|
ident,
|
||||||
ident_func,
|
name,
|
||||||
|
title,
|
||||||
|
docs,
|
||||||
|
keywords,
|
||||||
returns,
|
returns,
|
||||||
|
scope,
|
||||||
|
parent,
|
||||||
|
constructor,
|
||||||
..
|
..
|
||||||
} = func;
|
} = func;
|
||||||
|
|
||||||
let handlers = func
|
let scope = if *scope {
|
||||||
.params
|
quote! { <#ident as #eval::NativeScope>::scope() }
|
||||||
.iter()
|
} else {
|
||||||
.filter(|param| !param.external)
|
quote! { #eval::Scope::new() }
|
||||||
.map(create_param_parser);
|
};
|
||||||
|
|
||||||
let args = func
|
let closure = create_wrapper_closure(func);
|
||||||
.params
|
let params = func.special.self_.iter().chain(&func.params).map(create_param_info);
|
||||||
.iter()
|
|
||||||
.filter(|param| !param.external)
|
|
||||||
.map(|param| ¶m.ident);
|
|
||||||
|
|
||||||
let parent = func.parent.as_ref().map(|ty| quote! { #ty:: });
|
let name = if *constructor {
|
||||||
let vm_ = func.vm.then(|| quote! { vm, });
|
quote! { <#parent as #eval::NativeType>::NAME }
|
||||||
let vt_ = func.vt.then(|| quote! { &mut vm.vt, });
|
} else {
|
||||||
let args_ = func.args.then(|| quote! { args.take(), });
|
quote! { #name }
|
||||||
let span_ = func.span.then(|| quote! { args.span, });
|
};
|
||||||
let wrapper = quote! {
|
|
||||||
|vm, args| {
|
quote! {
|
||||||
let __typst_func = #parent #ident;
|
#eval::NativeFuncData {
|
||||||
#(#handlers)*
|
function: #closure,
|
||||||
let output = __typst_func(#(#args,)* #vm_ #vt_ #args_ #span_);
|
name: #name,
|
||||||
::typst::eval::IntoResult::into_result(output, args.span)
|
title: #title,
|
||||||
|
docs: #docs,
|
||||||
|
keywords: &[#(#keywords),*],
|
||||||
|
scope: #eval::Lazy::new(|| #scope),
|
||||||
|
params: #eval::Lazy::new(|| ::std::vec![#(#params),*]),
|
||||||
|
returns: #eval::Lazy::new(|| <#returns as #eval::Reflect>::output()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a type that shadows the function.
|
||||||
|
fn create_func_ty(func: &Func) -> Option<TokenStream> {
|
||||||
|
if func.parent.is_some() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Func { vis, ident, .. } = func;
|
||||||
|
Some(quote! {
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#vis enum #ident {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create the runtime-compatible wrapper closure that parses arguments.
|
||||||
|
fn create_wrapper_closure(func: &Func) -> TokenStream {
|
||||||
|
// These handlers parse the arguments.
|
||||||
|
let handlers = {
|
||||||
|
let func_handlers = func
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.filter(|param| !param.external)
|
||||||
|
.map(create_param_parser);
|
||||||
|
let self_handler = func.special.self_.as_ref().map(create_param_parser);
|
||||||
|
quote! {
|
||||||
|
#self_handler
|
||||||
|
#(#func_handlers)*
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut item = item.clone();
|
// This is the actual function call.
|
||||||
item.attrs.clear();
|
let call = {
|
||||||
|
let self_ = func
|
||||||
let inputs = item.sig.inputs.iter().cloned().filter_map(|mut input| {
|
.special
|
||||||
if let syn::FnArg::Typed(typed) = &mut input {
|
.self_
|
||||||
if typed.attrs.iter().any(|attr| attr.path().is_ident("external")) {
|
.as_ref()
|
||||||
return None;
|
.map(bind)
|
||||||
}
|
.map(|tokens| quote! { #tokens, });
|
||||||
typed.attrs.clear();
|
let vm_ = func.special.vm.then(|| quote! { vm, });
|
||||||
|
let vt_ = func.special.vt.then(|| quote! { &mut vm.vt, });
|
||||||
|
let args_ = func.special.args.then(|| quote! { args.take(), });
|
||||||
|
let span_ = func.special.span.then(|| quote! { args.span, });
|
||||||
|
let forwarded = func.params.iter().filter(|param| !param.external).map(bind);
|
||||||
|
quote! {
|
||||||
|
__typst_func(#self_ #vm_ #vt_ #args_ #span_ #(#forwarded,)*)
|
||||||
}
|
}
|
||||||
Some(input)
|
};
|
||||||
});
|
|
||||||
|
|
||||||
item.sig.inputs = parse_quote! { #(#inputs),* };
|
|
||||||
|
|
||||||
let keywords = quote_option(&func.keywords);
|
|
||||||
let params = func.params.iter().map(create_param_info);
|
|
||||||
let scope = create_scope_builder(func.scope.as_ref());
|
|
||||||
|
|
||||||
|
// This is the whole wrapped closure.
|
||||||
|
let ident = &func.ident;
|
||||||
|
let parent = func.parent.as_ref().map(|ty| quote! { #ty:: });
|
||||||
quote! {
|
quote! {
|
||||||
#[doc(hidden)]
|
|vm, args| {
|
||||||
#vis fn #ident_func() -> &'static ::typst::eval::NativeFunc {
|
let __typst_func = #parent #ident;
|
||||||
static FUNC: ::typst::eval::NativeFunc = ::typst::eval::NativeFunc {
|
#handlers
|
||||||
func: #wrapper,
|
let output = #call;
|
||||||
info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo {
|
::typst::eval::IntoResult::into_result(output, args.span)
|
||||||
name: #name,
|
|
||||||
display: #display,
|
|
||||||
keywords: #keywords,
|
|
||||||
category: #category,
|
|
||||||
docs: #docs,
|
|
||||||
params: ::std::vec![#(#params),*],
|
|
||||||
returns: <#returns as ::typst::eval::Reflect>::describe(),
|
|
||||||
scope: #scope,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
&FUNC
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = #docs]
|
|
||||||
#item
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +360,7 @@ fn create_param_info(param: &Param) -> TokenStream {
|
|||||||
::typst::eval::ParamInfo {
|
::typst::eval::ParamInfo {
|
||||||
name: #name,
|
name: #name,
|
||||||
docs: #docs,
|
docs: #docs,
|
||||||
cast: <#ty as ::typst::eval::Reflect>::describe(),
|
input: <#ty as ::typst::eval::Reflect>::input(),
|
||||||
default: #default,
|
default: #default,
|
||||||
positional: #positional,
|
positional: #positional,
|
||||||
named: #named,
|
named: #named,
|
||||||
@ -258,10 +392,29 @@ fn create_param_parser(param: &Param) -> TokenStream {
|
|||||||
quote! { let mut #ident: #ty = #value; }
|
quote! { let mut #ident: #ty = #value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Parent(Option<syn::Type>);
|
/// Apply the binding to a parameter.
|
||||||
|
fn bind(param: &Param) -> TokenStream {
|
||||||
impl Parse for Parent {
|
let ident = ¶m.ident;
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
match param.binding {
|
||||||
Ok(Self(if !input.is_empty() { Some(input.parse()?) } else { None }))
|
Binding::Owned => quote! { #ident },
|
||||||
|
Binding::Ref => quote! { &#ident },
|
||||||
|
Binding::RefMut => quote! { &mut #ident },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes attributes and so on from the native function.
|
||||||
|
fn rewrite_fn_item(item: &syn::ItemFn) -> syn::ItemFn {
|
||||||
|
let inputs = item.sig.inputs.iter().cloned().filter_map(|mut input| {
|
||||||
|
if let syn::FnArg::Typed(typed) = &mut input {
|
||||||
|
if typed.attrs.iter().any(|attr| attr.path().is_ident("external")) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
typed.attrs.clear();
|
||||||
|
}
|
||||||
|
Some(input)
|
||||||
|
});
|
||||||
|
let mut item = item.clone();
|
||||||
|
item.attrs.clear();
|
||||||
|
item.sig.inputs = parse_quote! { #(#inputs),* };
|
||||||
|
item
|
||||||
|
}
|
||||||
|
@ -4,10 +4,12 @@ extern crate proc_macro;
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod util;
|
mod util;
|
||||||
mod castable;
|
mod cast;
|
||||||
mod element;
|
mod elem;
|
||||||
mod func;
|
mod func;
|
||||||
|
mod scope;
|
||||||
mod symbols;
|
mod symbols;
|
||||||
|
mod ty;
|
||||||
|
|
||||||
use proc_macro::TokenStream as BoundaryStream;
|
use proc_macro::TokenStream as BoundaryStream;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
@ -19,7 +21,79 @@ use syn::{parse_quote, DeriveInput, Ident, Result, Token};
|
|||||||
|
|
||||||
use self::util::*;
|
use self::util::*;
|
||||||
|
|
||||||
/// Turns a function into a `NativeFunc`.
|
/// Makes a native Rust function usable as a Typst function.
|
||||||
|
///
|
||||||
|
/// This implements `NativeFunction` for a freshly generated type with the same
|
||||||
|
/// name as a function. (In Rust, functions and types live in separate
|
||||||
|
/// namespace, so both can coexist.)
|
||||||
|
///
|
||||||
|
/// If the function is in an impl block annotated with `#[scope]`, things work a
|
||||||
|
/// bit differently because the no type can be generated within the impl block.
|
||||||
|
/// In that case, a function named `{name}_data` that returns `&'static
|
||||||
|
/// NativeFuncData` is generated. You typically don't need to interact with this
|
||||||
|
/// function though because the `#[scope]` macro hooks everything up for you.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// /// Doubles an integer.
|
||||||
|
/// #[func]
|
||||||
|
/// fn double(x: i64) -> i64 {
|
||||||
|
/// 2 * x
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Properties
|
||||||
|
/// You can customize some properties of the resulting function:
|
||||||
|
/// - `scope`: Indicates that the function has an associated scope defined by
|
||||||
|
/// the `#[scope]` macro.
|
||||||
|
/// - `name`: The functions's normal name (e.g. `min`). Defaults to the Rust
|
||||||
|
/// name in kebab-case.
|
||||||
|
/// - `title`: The functions's title case name (e.g. `Minimum`). Defaults to the
|
||||||
|
/// normal name in title case.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// By default, function arguments are positional and required. You can use
|
||||||
|
/// various attributes to configure their parsing behaviour:
|
||||||
|
///
|
||||||
|
/// - `#[named]`: Makes the argument named and optional. The argument type must
|
||||||
|
/// either be `Option<_>` _or_ the `#[default]` attribute must be used. (If
|
||||||
|
/// it's both `Option<_>` and `#[default]`, then the argument can be specified
|
||||||
|
/// as `none` in Typst).
|
||||||
|
/// - `#[default]`: Specifies the default value of the argument as
|
||||||
|
/// `Default::default()`.
|
||||||
|
/// - `#[default(..)]`: Specifies the default value of the argument as `..`.
|
||||||
|
/// - `#[variadic]`: Parses a variable number of arguments. The argument type
|
||||||
|
/// must be `Vec<_>`.
|
||||||
|
/// - `#[external]`: The argument appears in documentation, but is otherwise
|
||||||
|
/// ignored. Can be useful if you want to do something manually for more
|
||||||
|
/// flexibility.
|
||||||
|
///
|
||||||
|
/// Defaults can be specified for positional and named arguments. This is in
|
||||||
|
/// contrast to user-defined functions which currently cannot have optional
|
||||||
|
/// positional arguments (except through argument sinks).
|
||||||
|
///
|
||||||
|
/// In the example below, we define a `min` function that could be called as
|
||||||
|
/// `min(1, 2, 3, default: 0)` in Typst.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// /// Determines the minimum of a sequence of values.
|
||||||
|
/// #[func(title = "Minimum")]
|
||||||
|
/// fn min(
|
||||||
|
/// /// The values to extract the minimum from.
|
||||||
|
/// #[variadic]
|
||||||
|
/// values: Vec<i64>,
|
||||||
|
/// /// A default value to return if there are no values.
|
||||||
|
/// #[named]
|
||||||
|
/// #[default(0)]
|
||||||
|
/// default: i64,
|
||||||
|
/// ) -> i64 {
|
||||||
|
/// self.values.iter().min().unwrap_or(default)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// As you can see, arguments can also have doc-comments, which will be rendered
|
||||||
|
/// in the documentation. The first line of documentation should be concise and
|
||||||
|
/// self-contained as it is the designated short description, which is used in
|
||||||
|
/// overviews in the documentation (and for autocompletion).
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn func(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
pub fn func(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||||
let item = syn::parse_macro_input!(item as syn::ItemFn);
|
let item = syn::parse_macro_input!(item as syn::ItemFn);
|
||||||
@ -28,33 +102,230 @@ pub fn func(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns a type into an `Element`.
|
/// Makes a native Rust type usable as a Typst type.
|
||||||
|
///
|
||||||
|
/// This implements `NativeType` for the given type.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// /// A sequence of codepoints.
|
||||||
|
/// #[ty(scope, title = "String")]
|
||||||
|
/// struct Str(EcoString);
|
||||||
|
///
|
||||||
|
/// #[scope]
|
||||||
|
/// impl Str {
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Properties
|
||||||
|
/// You can customize some properties of the resulting type:
|
||||||
|
/// - `scope`: Indicates that the type has an associated scope defined by the
|
||||||
|
/// `#[scope]` macro
|
||||||
|
/// - `name`: The type's normal name (e.g. `str`). Defaults to the Rust name in
|
||||||
|
/// kebab-case.
|
||||||
|
/// - `title`: The type's title case name (e.g. `String`). Defaults to the
|
||||||
|
/// normal name in title case.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn element(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
pub fn ty(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||||
let item = syn::parse_macro_input!(item as syn::ItemStruct);
|
let item = syn::parse_macro_input!(item as syn::Item);
|
||||||
element::element(stream.into(), &item)
|
ty::ty(stream.into(), item)
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements `Reflect`, `FromValue`, and `IntoValue` for an enum.
|
/// Makes a native Rust type usable as a Typst element.
|
||||||
#[proc_macro_derive(Cast, attributes(string))]
|
///
|
||||||
pub fn derive_cast(item: BoundaryStream) -> BoundaryStream {
|
/// This implements `NativeElement` for the given type.
|
||||||
let item = syn::parse_macro_input!(item as DeriveInput);
|
///
|
||||||
castable::derive_cast(&item)
|
/// ```
|
||||||
|
/// /// A section heading.
|
||||||
|
/// #[elem(Show, Count)]
|
||||||
|
/// struct HeadingElem {
|
||||||
|
/// /// The logical nesting depth of the heading, starting from one.
|
||||||
|
/// #[default(NonZeroUsize::ONE)]
|
||||||
|
/// level: NonZeroUsize,
|
||||||
|
///
|
||||||
|
/// /// The heading's title.
|
||||||
|
/// #[required]
|
||||||
|
/// body: Content,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Properties
|
||||||
|
/// You can customize some properties of the resulting type:
|
||||||
|
/// - `scope`: Indicates that the type has an associated scope defined by the
|
||||||
|
/// `#[scope]` macro
|
||||||
|
/// - `name`: The element's normal name (e.g. `str`). Defaults to the Rust name
|
||||||
|
/// in kebab-case.
|
||||||
|
/// - `title`: The type's title case name (e.g. `String`). Defaults to the long
|
||||||
|
/// name in title case.
|
||||||
|
/// - The remaining entries in the `elem` macros list are traits the element
|
||||||
|
/// is capable of. These can be dynamically accessed.
|
||||||
|
///
|
||||||
|
/// # Fields
|
||||||
|
/// By default, element fields are named and optional (and thus settable). You
|
||||||
|
/// can use various attributes to configure their parsing behaviour:
|
||||||
|
///
|
||||||
|
/// - `#[positional]`: Makes the argument positional (but still optional).
|
||||||
|
/// - `#[required]`: Makes the argument positional and required.
|
||||||
|
/// - `#[default(..)]`: Specifies the default value of the argument as `..`.
|
||||||
|
/// - `#[variadic]`: Parses a variable number of arguments. The field type must
|
||||||
|
/// be `Vec<_>`. The field will be exposed as an array.
|
||||||
|
/// - `#[parse({ .. })]`: A block of code that parses the field manually.
|
||||||
|
///
|
||||||
|
/// In addition that there are a number of attributes that configure other
|
||||||
|
/// aspects of the field than the parsing behaviour.
|
||||||
|
/// - `#[resolve]`: When accessing the field, it will be automatically
|
||||||
|
/// resolved through the `Resolve` trait. This, for instance, turns `Length`
|
||||||
|
/// into `Abs`. It's just convenient.
|
||||||
|
/// - `#[fold]`: When there are multiple set rules for the field, all values
|
||||||
|
/// are folded together into one. E.g. `set rect(stroke: 2pt)` and
|
||||||
|
/// `set rect(stroke: red)` are combined into the equivalent of
|
||||||
|
/// `set rect(stroke: 2pt + red)` instead of having `red` override `2pt`.
|
||||||
|
/// - `#[internal]`: The field does not appear in the documentation.
|
||||||
|
/// - `#[external]`: The field appears in the documentation, but is otherwise
|
||||||
|
/// ignored. Can be useful if you want to do something manually for more
|
||||||
|
/// flexibility.
|
||||||
|
/// - `#[synthesized]`: The field cannot be specified in a constructor or set
|
||||||
|
/// rule. Instead, it is added to an element before its show rule runs
|
||||||
|
/// through the `Synthesize` trait.
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn elem(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||||
|
let item = syn::parse_macro_input!(item as syn::ItemStruct);
|
||||||
|
elem::elem(stream.into(), item)
|
||||||
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides an associated scope to a native function, type, or element.
|
||||||
|
///
|
||||||
|
/// This implements `NativeScope` for the function's shadow type, the type, or
|
||||||
|
/// the element.
|
||||||
|
///
|
||||||
|
/// The implementation block can contain four kinds of items:
|
||||||
|
/// - constants, which will be defined through `scope.define`
|
||||||
|
/// - functions, which will be defined through `scope.define_func`
|
||||||
|
/// - types, which will be defined through `scope.define_type`
|
||||||
|
/// - elements, which will be defined through `scope.define_elem`
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// #[func(scope)]
|
||||||
|
/// fn name() { .. }
|
||||||
|
///
|
||||||
|
/// #[scope]
|
||||||
|
/// impl name {
|
||||||
|
/// /// A simple constant.
|
||||||
|
/// const VAL: u32 = 0;
|
||||||
|
///
|
||||||
|
/// /// A function.
|
||||||
|
/// #[func]
|
||||||
|
/// fn foo() -> EcoString {
|
||||||
|
/// "foo!".into()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// A type.
|
||||||
|
/// type Brr;
|
||||||
|
///
|
||||||
|
/// /// An element.
|
||||||
|
/// #[elem]
|
||||||
|
/// type NiceElem;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[ty]
|
||||||
|
/// struct Brr;
|
||||||
|
///
|
||||||
|
/// #[elem]
|
||||||
|
/// struct NiceElem {}
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn scope(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||||
|
let item = syn::parse_macro_input!(item as syn::Item);
|
||||||
|
scope::scope(stream.into(), item)
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements `Reflect`, `FromValue`, and `IntoValue` for a type.
|
/// Implements `Reflect`, `FromValue`, and `IntoValue` for a type.
|
||||||
|
///
|
||||||
|
/// - `Reflect` makes Typst's runtime aware of the type's characteristics.
|
||||||
|
/// It's important for autocompletion, error messages, etc.
|
||||||
|
/// - `FromValue` defines how to cast from a value into this type.
|
||||||
|
/// - `IntoValue` defines how to cast fromthis type into a value.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// /// An integer between 0 and 13.
|
||||||
|
/// struct CoolInt(u8);
|
||||||
|
///
|
||||||
|
/// cast! {
|
||||||
|
/// CoolInt,
|
||||||
|
///
|
||||||
|
/// // Defines how to turn a `CoolInt` into a value.
|
||||||
|
/// self => self.0.into_value(),
|
||||||
|
///
|
||||||
|
/// // Defines "match arms" of types that can be cast into a `CoolInt`.
|
||||||
|
/// // These types needn't be value primitives, they can themselves use
|
||||||
|
/// // `cast!`.
|
||||||
|
/// v: bool => Self(v as u8),
|
||||||
|
/// v: i64 => if matches!(v, 0..=13) {
|
||||||
|
/// Self(v as u8)
|
||||||
|
/// } else {
|
||||||
|
/// bail!("integer is not nice :/")
|
||||||
|
/// },
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn cast(stream: BoundaryStream) -> BoundaryStream {
|
pub fn cast(stream: BoundaryStream) -> BoundaryStream {
|
||||||
castable::cast(stream.into())
|
cast::cast(stream.into())
|
||||||
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements `Reflect`, `FromValue`, and `IntoValue` for an enum.
|
||||||
|
///
|
||||||
|
/// The enum will become castable from kebab-case strings. The doc-comments will
|
||||||
|
/// become user-facing documentation for each variant. The `#[string]` attribute
|
||||||
|
/// can be used to override the string corresponding to a variant.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// /// A stringy enum of options.
|
||||||
|
/// #[derive(Cast)]
|
||||||
|
/// enum Niceness {
|
||||||
|
/// /// Clearly nice (parses from `"nice"`).
|
||||||
|
/// Nice,
|
||||||
|
/// /// Not so nice (parses from `"not-nice"`).
|
||||||
|
/// NotNice,
|
||||||
|
/// /// Very much not nice (parses from `"❌"`).
|
||||||
|
/// #[string("❌")]
|
||||||
|
/// Unnice,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_derive(Cast, attributes(string))]
|
||||||
|
pub fn derive_cast(item: BoundaryStream) -> BoundaryStream {
|
||||||
|
let item = syn::parse_macro_input!(item as DeriveInput);
|
||||||
|
cast::derive_cast(item)
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines a list of `Symbol`s.
|
/// Defines a list of `Symbol`s.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// const EMOJI: &[(&str, Symbol)] = symbols! {
|
||||||
|
/// // A plain symbol without modifiers.
|
||||||
|
/// abacus: '🧮',
|
||||||
|
///
|
||||||
|
/// // A symbol with a modifierless default and one modifier.
|
||||||
|
/// alien: ['👽', monster: '👾'],
|
||||||
|
///
|
||||||
|
/// // A symbol where each variant has a modifier. The first one will be
|
||||||
|
/// // the default.
|
||||||
|
/// clock: [one: '🕐', two: '🕑', ...],
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// _Note:_ While this could use `macro_rules!` instead of a proc-macro, it was
|
||||||
|
/// horribly slow in rust-analyzer. The underlying cause might be
|
||||||
|
/// [this issue](https://github.com/rust-lang/rust-analyzer/issues/11108).
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn symbols(stream: BoundaryStream) -> BoundaryStream {
|
pub fn symbols(stream: BoundaryStream) -> BoundaryStream {
|
||||||
symbols::symbols(stream.into())
|
symbols::symbols(stream.into())
|
||||||
|
153
crates/typst-macros/src/scope.rs
Normal file
153
crates/typst-macros/src/scope.rs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
use heck::ToKebabCase;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Expand the `#[scope]` macro.
|
||||||
|
pub fn scope(_: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
||||||
|
let syn::Item::Impl(mut item) = item else {
|
||||||
|
bail!(item, "expected module or impl item");
|
||||||
|
};
|
||||||
|
|
||||||
|
let eval = quote! { ::typst::eval };
|
||||||
|
let self_ty = &item.self_ty;
|
||||||
|
|
||||||
|
let mut definitions = vec![];
|
||||||
|
let mut constructor = quote! { None };
|
||||||
|
for child in &mut item.items {
|
||||||
|
let def = match child {
|
||||||
|
syn::ImplItem::Const(item) => handle_const(self_ty, item)?,
|
||||||
|
syn::ImplItem::Fn(item) => match handle_fn(self_ty, item)? {
|
||||||
|
FnKind::Member(tokens) => tokens,
|
||||||
|
FnKind::Constructor(tokens) => {
|
||||||
|
constructor = tokens;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
syn::ImplItem::Verbatim(item) => handle_type_or_elem(item)?,
|
||||||
|
_ => bail!(child, "unexpected item in scope"),
|
||||||
|
};
|
||||||
|
definitions.push(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.items.retain(|item| !matches!(item, syn::ImplItem::Verbatim(_)));
|
||||||
|
|
||||||
|
let mut base = quote! { #item };
|
||||||
|
if let syn::Type::Path(syn::TypePath { path, .. }) = self_ty.as_ref() {
|
||||||
|
if let Some(ident) = path.get_ident() {
|
||||||
|
if is_primitive(ident) {
|
||||||
|
base = rewrite_primitive_base(&item, ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#base
|
||||||
|
|
||||||
|
impl #eval::NativeScope for #self_ty {
|
||||||
|
fn constructor() -> ::std::option::Option<&'static #eval::NativeFuncData> {
|
||||||
|
#constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scope() -> #eval::Scope {
|
||||||
|
let mut scope = #eval::Scope::deduplicating();
|
||||||
|
#(#definitions;)*
|
||||||
|
scope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process a const item and returns its definition.
|
||||||
|
fn handle_const(self_ty: &syn::Type, item: &syn::ImplItemConst) -> Result<TokenStream> {
|
||||||
|
let ident = &item.ident;
|
||||||
|
let name = ident.to_string().to_kebab_case();
|
||||||
|
Ok(quote! { scope.define(#name, #self_ty::#ident) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process a type item.
|
||||||
|
fn handle_type_or_elem(item: &TokenStream) -> Result<TokenStream> {
|
||||||
|
let item: BareType = syn::parse2(item.clone())?;
|
||||||
|
let ident = &item.ident;
|
||||||
|
let define = if item.attrs.iter().any(|attr| attr.path().is_ident("elem")) {
|
||||||
|
quote! { define_elem }
|
||||||
|
} else {
|
||||||
|
quote! { define_type }
|
||||||
|
};
|
||||||
|
Ok(quote! { scope.#define::<#ident>() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process a function, return its definition, and register it as a constructor
|
||||||
|
/// if applicable.
|
||||||
|
fn handle_fn(self_ty: &syn::Type, item: &mut syn::ImplItemFn) -> Result<FnKind> {
|
||||||
|
let Some(attr) = item.attrs.iter_mut().find(|attr| attr.meta.path().is_ident("func"))
|
||||||
|
else {
|
||||||
|
bail!(item, "scope function is missing #[func] attribute");
|
||||||
|
};
|
||||||
|
|
||||||
|
let ident_data = quote::format_ident!("{}_data", item.sig.ident);
|
||||||
|
|
||||||
|
match &mut attr.meta {
|
||||||
|
syn::Meta::Path(_) => {
|
||||||
|
*attr = parse_quote! { #[func(parent = #self_ty)] };
|
||||||
|
}
|
||||||
|
syn::Meta::List(list) => {
|
||||||
|
let tokens = &list.tokens;
|
||||||
|
let meta: super::func::Meta = syn::parse2(tokens.clone())?;
|
||||||
|
list.tokens = quote! { #tokens, parent = #self_ty };
|
||||||
|
if meta.constructor {
|
||||||
|
return Ok(FnKind::Constructor(quote! { Some(#self_ty::#ident_data()) }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syn::Meta::NameValue(_) => bail!(attr.meta, "invalid func attribute"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FnKind::Member(quote! { scope.define_func_with_data(#self_ty::#ident_data()) }))
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FnKind {
|
||||||
|
Constructor(TokenStream),
|
||||||
|
Member(TokenStream),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the identifier describes a primitive type.
|
||||||
|
fn is_primitive(ident: &syn::Ident) -> bool {
|
||||||
|
ident == "bool" || ident == "i64" || ident == "f64"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rewrite an impl block for a primitive into a trait + trait impl.
|
||||||
|
fn rewrite_primitive_base(item: &syn::ItemImpl, ident: &syn::Ident) -> TokenStream {
|
||||||
|
let mut sigs = vec![];
|
||||||
|
let mut items = vec![];
|
||||||
|
for sub in &item.items {
|
||||||
|
let syn::ImplItem::Fn(mut func) = sub.clone() else { continue };
|
||||||
|
func.vis = syn::Visibility::Inherited;
|
||||||
|
items.push(func.clone());
|
||||||
|
|
||||||
|
let mut sig = func.sig;
|
||||||
|
let inputs = sig.inputs.iter().cloned().map(|mut input| {
|
||||||
|
if let syn::FnArg::Typed(typed) = &mut input {
|
||||||
|
typed.attrs.clear();
|
||||||
|
}
|
||||||
|
input
|
||||||
|
});
|
||||||
|
sig.inputs = parse_quote! { #(#inputs),* };
|
||||||
|
|
||||||
|
let ident_data = quote::format_ident!("{}_data", sig.ident);
|
||||||
|
sigs.push(quote! { #sig; });
|
||||||
|
sigs.push(quote! {
|
||||||
|
fn #ident_data() -> &'static ::typst::eval::NativeFuncData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let ident_ext = quote::format_ident!("{ident}Ext");
|
||||||
|
let self_ty = &item.self_ty;
|
||||||
|
quote! {
|
||||||
|
trait #ident_ext {
|
||||||
|
#(#sigs)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #ident_ext for #self_ty {
|
||||||
|
#(#items)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ pub fn symbols(stream: TokenStream) -> Result<TokenStream> {
|
|||||||
let pairs = list.iter().map(|symbol| {
|
let pairs = list.iter().map(|symbol| {
|
||||||
let name = symbol.name.to_string();
|
let name = symbol.name.to_string();
|
||||||
let kind = match &symbol.kind {
|
let kind = match &symbol.kind {
|
||||||
Kind::Single(c) => quote! { typst::eval::Symbol::new(#c), },
|
Kind::Single(c) => quote! { typst::eval::Symbol::single(#c), },
|
||||||
Kind::Multiple(variants) => {
|
Kind::Multiple(variants) => {
|
||||||
let variants = variants.iter().map(|variant| {
|
let variants = variants.iter().map(|variant| {
|
||||||
let name = &variant.name;
|
let name = &variant.name;
|
||||||
|
113
crates/typst-macros/src/ty.rs
Normal file
113
crates/typst-macros/src/ty.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use syn::Attribute;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Expand the `#[ty]` macro.
|
||||||
|
pub fn ty(stream: TokenStream, item: syn::Item) -> Result<TokenStream> {
|
||||||
|
let meta: Meta = syn::parse2(stream)?;
|
||||||
|
let bare: BareType;
|
||||||
|
let (ident, attrs, keep) = match &item {
|
||||||
|
syn::Item::Struct(item) => (&item.ident, &item.attrs, true),
|
||||||
|
syn::Item::Type(item) => (&item.ident, &item.attrs, true),
|
||||||
|
syn::Item::Enum(item) => (&item.ident, &item.attrs, true),
|
||||||
|
syn::Item::Verbatim(item) => {
|
||||||
|
bare = syn::parse2(item.clone())?;
|
||||||
|
(&bare.ident, &bare.attrs, false)
|
||||||
|
}
|
||||||
|
_ => bail!(item, "invalid type item"),
|
||||||
|
};
|
||||||
|
let ty = parse(meta, ident.clone(), attrs)?;
|
||||||
|
Ok(create(&ty, keep.then_some(&item)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds all relevant parsed data about a type.
|
||||||
|
struct Type {
|
||||||
|
ident: Ident,
|
||||||
|
name: String,
|
||||||
|
long: String,
|
||||||
|
scope: bool,
|
||||||
|
title: String,
|
||||||
|
docs: String,
|
||||||
|
keywords: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `..` in `#[ty(..)]`.
|
||||||
|
struct Meta {
|
||||||
|
scope: bool,
|
||||||
|
name: Option<String>,
|
||||||
|
title: Option<String>,
|
||||||
|
keywords: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Meta {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
scope: parse_flag::<kw::scope>(input)?,
|
||||||
|
name: parse_string::<kw::name>(input)?,
|
||||||
|
title: parse_string::<kw::title>(input)?,
|
||||||
|
keywords: parse_string_array::<kw::keywords>(input)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse details about the type from its definition.
|
||||||
|
fn parse(meta: Meta, ident: Ident, attrs: &[Attribute]) -> Result<Type> {
|
||||||
|
let docs = documentation(attrs);
|
||||||
|
let (name, title) = determine_name_and_title(meta.name, meta.title, &ident, None)?;
|
||||||
|
let long = title.to_lowercase();
|
||||||
|
Ok(Type {
|
||||||
|
ident,
|
||||||
|
name,
|
||||||
|
long,
|
||||||
|
scope: meta.scope,
|
||||||
|
keywords: meta.keywords,
|
||||||
|
title,
|
||||||
|
docs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produce the output of the macro.
|
||||||
|
fn create(ty: &Type, item: Option<&syn::Item>) -> TokenStream {
|
||||||
|
let eval = quote! { ::typst::eval };
|
||||||
|
|
||||||
|
let Type {
|
||||||
|
ident, name, long, title, docs, keywords, scope, ..
|
||||||
|
} = ty;
|
||||||
|
|
||||||
|
let constructor = if *scope {
|
||||||
|
quote! { <#ident as #eval::NativeScope>::constructor() }
|
||||||
|
} else {
|
||||||
|
quote! { None }
|
||||||
|
};
|
||||||
|
|
||||||
|
let scope = if *scope {
|
||||||
|
quote! { <#ident as #eval::NativeScope>::scope() }
|
||||||
|
} else {
|
||||||
|
quote! { #eval::Scope::new() }
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = quote! {
|
||||||
|
#eval::NativeTypeData {
|
||||||
|
name: #name,
|
||||||
|
long_name: #long,
|
||||||
|
title: #title,
|
||||||
|
docs: #docs,
|
||||||
|
keywords: &[#(#keywords),*],
|
||||||
|
constructor: #eval::Lazy::new(|| #constructor),
|
||||||
|
scope: #eval::Lazy::new(|| #scope),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#item
|
||||||
|
|
||||||
|
impl #eval::NativeType for #ident {
|
||||||
|
const NAME: &'static str = #name;
|
||||||
|
|
||||||
|
fn data() -> &'static #eval::NativeTypeData {
|
||||||
|
static DATA: #eval::NativeTypeData = #data;
|
||||||
|
&DATA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
use heck::ToKebabCase;
|
use heck::{ToKebabCase, ToTitleCase};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
|
use syn::token::Token;
|
||||||
|
use syn::Attribute;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -19,25 +21,27 @@ macro_rules! bail {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For parsing attributes of the form:
|
/// Extract documentation comments from an attribute list.
|
||||||
/// #[attr(
|
pub fn documentation(attrs: &[syn::Attribute]) -> String {
|
||||||
/// statement;
|
let mut doc = String::new();
|
||||||
/// statement;
|
|
||||||
/// returned_expression
|
|
||||||
/// )]
|
|
||||||
pub struct BlockWithReturn {
|
|
||||||
pub prefix: Vec<syn::Stmt>,
|
|
||||||
pub expr: syn::Stmt,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for BlockWithReturn {
|
// Parse doc comments.
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
for attr in attrs {
|
||||||
let mut stmts = syn::Block::parse_within(input)?;
|
if let syn::Meta::NameValue(meta) = &attr.meta {
|
||||||
let Some(expr) = stmts.pop() else {
|
if meta.path.is_ident("doc") {
|
||||||
return Err(input.error("expected at least one expression"));
|
if let syn::Expr::Lit(lit) = &meta.value {
|
||||||
};
|
if let syn::Lit::Str(string) = &lit.lit {
|
||||||
Ok(Self { prefix: stmts, expr })
|
let full = string.value();
|
||||||
|
let line = full.strip_prefix(' ').unwrap_or(&full);
|
||||||
|
doc.push_str(line);
|
||||||
|
doc.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doc.trim().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether an attribute list has a specified attribute.
|
/// Whether an attribute list has a specified attribute.
|
||||||
@ -83,58 +87,6 @@ pub fn validate_attrs(attrs: &[syn::Attribute]) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert an identifier to a kebab-case string.
|
|
||||||
pub fn kebab_case(name: &Ident) -> String {
|
|
||||||
name.to_string().to_kebab_case()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 syn::Meta::NameValue(meta) = &attr.meta {
|
|
||||||
if meta.path.is_ident("doc") {
|
|
||||||
if let syn::Expr::Lit(lit) = &meta.value {
|
|
||||||
if let syn::Lit::Str(string) = &lit.lit {
|
|
||||||
let full = string.value();
|
|
||||||
let line = full.strip_prefix(' ').unwrap_or(&full);
|
|
||||||
doc.push_str(line);
|
|
||||||
doc.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.trim().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract a line of metadata from documentation.
|
|
||||||
pub fn meta_line<'a>(lines: &mut Vec<&'a str>, key: &str) -> Result<&'a str> {
|
|
||||||
match lines.last().and_then(|line| line.strip_prefix(&format!("{key}:"))) {
|
|
||||||
Some(value) => {
|
|
||||||
lines.pop();
|
|
||||||
Ok(value.trim())
|
|
||||||
}
|
|
||||||
None => bail!(callsite, "missing metadata key: {key}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a block responsible for building a `Scope`.
|
|
||||||
pub fn create_scope_builder(scope_block: Option<&BlockWithReturn>) -> TokenStream {
|
|
||||||
if let Some(BlockWithReturn { prefix, expr }) = scope_block {
|
|
||||||
quote! { {
|
|
||||||
let mut scope = ::typst::eval::Scope::deduplicating();
|
|
||||||
#(#prefix);*
|
|
||||||
#expr
|
|
||||||
} }
|
|
||||||
} else {
|
|
||||||
quote! { ::typst::eval::Scope::new() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Quotes an option literally.
|
/// Quotes an option literally.
|
||||||
pub fn quote_option<T: ToTokens>(option: &Option<T>) -> TokenStream {
|
pub fn quote_option<T: ToTokens>(option: &Option<T>) -> TokenStream {
|
||||||
if let Some(value) = option {
|
if let Some(value) = option {
|
||||||
@ -143,3 +95,156 @@ pub fn quote_option<T: ToTokens>(option: &Option<T>) -> TokenStream {
|
|||||||
quote! { None }
|
quote! { None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a metadata key-value pair, separated by `=`.
|
||||||
|
pub fn parse_key_value<K: Token + Default + Parse, V: Parse>(
|
||||||
|
input: ParseStream,
|
||||||
|
) -> Result<Option<V>> {
|
||||||
|
if !input.peek(|_| K::default()) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _: K = input.parse()?;
|
||||||
|
let _: Token![=] = input.parse()?;
|
||||||
|
let value: V = input.parse::<V>()?;
|
||||||
|
eat_comma(input);
|
||||||
|
Ok(Some(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a metadata key-array pair, separated by `=`.
|
||||||
|
pub fn parse_key_value_array<K: Token + Default + Parse, V: Parse>(
|
||||||
|
input: ParseStream,
|
||||||
|
) -> Result<Vec<V>> {
|
||||||
|
Ok(parse_key_value::<K, Array<V>>(input)?.map_or(vec![], |array| array.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a metadata key-string pair, separated by `=`.
|
||||||
|
pub fn parse_string<K: Token + Default + Parse>(
|
||||||
|
input: ParseStream,
|
||||||
|
) -> Result<Option<String>> {
|
||||||
|
Ok(parse_key_value::<K, syn::LitStr>(input)?.map(|s| s.value()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a metadata key-string pair, separated by `=`.
|
||||||
|
pub fn parse_string_array<K: Token + Default + Parse>(
|
||||||
|
input: ParseStream,
|
||||||
|
) -> Result<Vec<String>> {
|
||||||
|
Ok(parse_key_value_array::<K, syn::LitStr>(input)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|lit| lit.value())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a metadata flag that can be present or not.
|
||||||
|
pub fn parse_flag<K: Token + Default + Parse>(input: ParseStream) -> Result<bool> {
|
||||||
|
if input.peek(|_| K::default()) {
|
||||||
|
let _: K = input.parse()?;
|
||||||
|
eat_comma(input);
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a comma if there is one.
|
||||||
|
pub fn eat_comma(input: ParseStream) {
|
||||||
|
if input.peek(Token![,]) {
|
||||||
|
let _: Token![,] = input.parse().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the normal and title case name of a function, type, or element.
|
||||||
|
pub fn determine_name_and_title(
|
||||||
|
specified_name: Option<String>,
|
||||||
|
specified_title: Option<String>,
|
||||||
|
ident: &syn::Ident,
|
||||||
|
trim: Option<fn(&str) -> &str>,
|
||||||
|
) -> Result<(String, String)> {
|
||||||
|
let name = {
|
||||||
|
let trim = trim.unwrap_or(|s| s);
|
||||||
|
let default = trim(&ident.to_string()).to_kebab_case();
|
||||||
|
if specified_name.as_ref() == Some(&default) {
|
||||||
|
bail!(ident, "name was specified unncessarily");
|
||||||
|
}
|
||||||
|
specified_name.unwrap_or(default)
|
||||||
|
};
|
||||||
|
|
||||||
|
let title = {
|
||||||
|
let default = name.to_title_case();
|
||||||
|
if specified_title.as_ref() == Some(&default) {
|
||||||
|
bail!(ident, "title was specified unncessarily");
|
||||||
|
}
|
||||||
|
specified_title.unwrap_or(default)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((name, title))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A generic parseable array.
|
||||||
|
struct Array<T>(Vec<T>);
|
||||||
|
|
||||||
|
impl<T: Parse> Parse for Array<T> {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let content;
|
||||||
|
syn::bracketed!(content in input);
|
||||||
|
|
||||||
|
let mut elems = Vec::new();
|
||||||
|
while !content.is_empty() {
|
||||||
|
let first: T = content.parse()?;
|
||||||
|
elems.push(first);
|
||||||
|
if !content.is_empty() {
|
||||||
|
let _: Token![,] = content.parse()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self(elems))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For parsing attributes of the form:
|
||||||
|
/// #[attr(
|
||||||
|
/// statement;
|
||||||
|
/// statement;
|
||||||
|
/// returned_expression
|
||||||
|
/// )]
|
||||||
|
pub struct BlockWithReturn {
|
||||||
|
pub prefix: Vec<syn::Stmt>,
|
||||||
|
pub expr: syn::Stmt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for BlockWithReturn {
|
||||||
|
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 one expression"));
|
||||||
|
};
|
||||||
|
Ok(Self { prefix: stmts, expr })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod kw {
|
||||||
|
syn::custom_keyword!(name);
|
||||||
|
syn::custom_keyword!(title);
|
||||||
|
syn::custom_keyword!(scope);
|
||||||
|
syn::custom_keyword!(constructor);
|
||||||
|
syn::custom_keyword!(keywords);
|
||||||
|
syn::custom_keyword!(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a bare `type Name;` item.
|
||||||
|
pub struct BareType {
|
||||||
|
pub attrs: Vec<Attribute>,
|
||||||
|
pub type_token: Token![type],
|
||||||
|
pub ident: Ident,
|
||||||
|
pub semi_token: Token![;],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for BareType {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
Ok(BareType {
|
||||||
|
attrs: input.call(Attribute::parse_outer)?,
|
||||||
|
type_token: input.parse()?,
|
||||||
|
ident: input.parse()?,
|
||||||
|
semi_token: input.parse()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user