diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-07-02 19:59:52 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-07-02 20:07:43 +0200 |
| commit | ebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch) | |
| tree | 2bbc24ddb4124c4bb14dec0e536129d4de37b056 /macros/src | |
| parent | 3ab19185093d7709f824b95b979060ce125389d8 (diff) | |
Move everything into `crates/` directory
Diffstat (limited to 'macros/src')
| -rw-r--r-- | macros/src/castable.rs | 331 | ||||
| -rw-r--r-- | macros/src/element.rs | 554 | ||||
| -rw-r--r-- | macros/src/func.rs | 268 | ||||
| -rw-r--r-- | macros/src/lib.rs | 63 | ||||
| -rw-r--r-- | macros/src/symbols.rs | 78 | ||||
| -rw-r--r-- | macros/src/util.rs | 145 |
6 files changed, 0 insertions, 1439 deletions
diff --git a/macros/src/castable.rs b/macros/src/castable.rs deleted file mode 100644 index 05c1b4d1..00000000 --- a/macros/src/castable.rs +++ /dev/null @@ -1,331 +0,0 @@ -use super::*; - -/// Expand the `#[derive(Cast)]` macro. -pub fn derive_cast(item: &DeriveInput) -> Result<TokenStream> { - let ty = &item.ident; - - let syn::Data::Enum(data) = &item.data else { - bail!(item, "only enums are supported"); - }; - - let mut variants = vec![]; - for variant in &data.variants { - if let Some((_, expr)) = &variant.discriminant { - bail!(expr, "explicit discriminant is not allowed"); - } - - let string = if let Some(attr) = - variant.attrs.iter().find(|attr| attr.path().is_ident("string")) - { - attr.parse_args::<syn::LitStr>()?.value() - } else { - kebab_case(&variant.ident) - }; - - variants.push(Variant { - ident: variant.ident.clone(), - string, - docs: documentation(&variant.attrs), - }); - } - - let strs_to_variants = variants.iter().map(|Variant { ident, string, docs }| { - quote! { - #[doc = #docs] - #string => Self::#ident - } - }); - - let variants_to_strs = variants.iter().map(|Variant { ident, string, .. }| { - quote! { - #ty::#ident => #string - } - }); - - Ok(quote! { - ::typst::eval::cast! { - #ty, - self => ::typst::eval::IntoValue::into_value(match self { - #(#variants_to_strs),* - }), - #(#strs_to_variants),* - } - }) -} - -/// An enum variant in a `derive(Cast)`. -struct Variant { - ident: Ident, - string: String, - docs: String, -} - -/// Expand the `cast!` macro. -pub fn cast(stream: TokenStream) -> Result<TokenStream> { - let input: CastInput = syn::parse2(stream)?; - let ty = &input.ty; - let eval = quote! { ::typst::eval }; - - let castable_body = create_castable_body(&input); - let describe_body = create_describe_body(&input); - let into_value_body = create_into_value_body(&input); - let from_value_body = create_from_value_body(&input); - - let reflect = (!input.from_value.is_empty() || input.name.is_some()).then(|| { - quote! { - impl #eval::Reflect for #ty { - fn describe() -> #eval::CastInfo { - #describe_body - } - - fn castable(value: &#eval::Value) -> bool { - #castable_body - } - } - } - }); - - let into_value = (input.into_value.is_some() || input.name.is_some()).then(|| { - quote! { - impl #eval::IntoValue for #ty { - fn into_value(self) -> #eval::Value { - #into_value_body - } - } - } - }); - - let from_value = (!input.from_value.is_empty() || input.name.is_some()).then(|| { - quote! { - impl #eval::FromValue for #ty { - fn from_value(value: #eval::Value) -> ::typst::diag::StrResult<Self> { - #from_value_body - } - } - } - }); - - let ty = input.name.as_ref().map(|name| { - quote! { - impl #eval::Type for #ty { - const TYPE_NAME: &'static str = #name; - } - } - }); - - Ok(quote! { - #reflect - #into_value - #from_value - #ty - }) -} - -/// The input to `cast!`. -struct CastInput { - ty: syn::Type, - name: Option<syn::LitStr>, - into_value: Option<syn::Expr>, - from_value: Punctuated<Cast, Token![,]>, -} - -impl Parse for CastInput { - fn parse(input: ParseStream) -> Result<Self> { - let ty; - let mut name = None; - if input.peek(syn::Token![type]) { - let _: syn::Token![type] = input.parse()?; - ty = input.parse()?; - let _: syn::Token![:] = input.parse()?; - name = Some(input.parse()?); - } else { - ty = input.parse()?; - } - - let _: syn::Token![,] = input.parse()?; - - let mut into_value = None; - if input.peek(syn::Token![self]) { - let _: syn::Token![self] = input.parse()?; - let _: syn::Token![=>] = input.parse()?; - into_value = Some(input.parse()?); - let _: syn::Token![,] = input.parse()?; - } - - let from_value = Punctuated::parse_terminated(input)?; - Ok(Self { ty, name, into_value, from_value }) - } -} - -impl Parse for Cast { - fn parse(input: ParseStream) -> Result<Self> { - let attrs = input.call(syn::Attribute::parse_outer)?; - let pattern = input.parse()?; - let _: syn::Token![=>] = input.parse()?; - let expr = input.parse()?; - Ok(Self { attrs, pattern, expr }) - } -} - -impl Parse for Pattern { - fn parse(input: ParseStream) -> Result<Self> { - if input.peek(syn::LitStr) { - Ok(Pattern::Str(input.parse()?)) - } else { - let pat = syn::Pat::parse_single(input)?; - let _: syn::Token![:] = input.parse()?; - let ty = input.parse()?; - Ok(Pattern::Ty(pat, ty)) - } - } -} - -/// A single cast, e.g. `v: i64 => Self::Int(v)`. -struct Cast { - attrs: Vec<syn::Attribute>, - pattern: Pattern, - expr: syn::Expr, -} - -/// A pattern in a cast, e.g.`"ascender"` or `v: i64`. -enum Pattern { - Str(syn::LitStr), - Ty(syn::Pat, syn::Type), -} - -fn create_castable_body(input: &CastInput) -> TokenStream { - let mut strings = vec![]; - let mut casts = vec![]; - - for cast in &input.from_value { - match &cast.pattern { - Pattern::Str(lit) => { - strings.push(quote! { #lit => return true }); - } - Pattern::Ty(_, ty) => { - casts.push(quote! { - if <#ty as ::typst::eval::Reflect>::castable(value) { - return true; - } - }); - } - } - } - - let dynamic_check = input.name.is_some().then(|| { - quote! { - if let ::typst::eval::Value::Dyn(dynamic) = &value { - if dynamic.is::<Self>() { - return true; - } - } - } - }); - - let str_check = (!strings.is_empty()).then(|| { - quote! { - if let ::typst::eval::Value::Str(string) = &value { - match string.as_str() { - #(#strings,)* - _ => {} - } - } - } - }); - - quote! { - #dynamic_check - #str_check - #(#casts)* - false - } -} - -fn create_describe_body(input: &CastInput) -> TokenStream { - let mut infos = vec![]; - - for cast in &input.from_value { - let docs = documentation(&cast.attrs); - infos.push(match &cast.pattern { - Pattern::Str(lit) => { - quote! { - ::typst::eval::CastInfo::Value( - ::typst::eval::IntoValue::into_value(#lit), - #docs, - ) - } - } - Pattern::Ty(_, ty) => { - quote! { <#ty as ::typst::eval::Reflect>::describe() } - } - }); - } - - if let Some(name) = &input.name { - infos.push(quote! { - ::typst::eval::CastInfo::Type(#name) - }); - } - - quote! { - #(#infos)+* - } -} - -fn create_into_value_body(input: &CastInput) -> TokenStream { - if let Some(expr) = &input.into_value { - quote! { #expr } - } else { - quote! { ::typst::eval::Value::dynamic(self) } - } -} - -fn create_from_value_body(input: &CastInput) -> TokenStream { - let mut string_arms = vec![]; - let mut cast_checks = vec![]; - - for cast in &input.from_value { - let expr = &cast.expr; - match &cast.pattern { - Pattern::Str(lit) => { - string_arms.push(quote! { #lit => return Ok(#expr) }); - } - Pattern::Ty(binding, ty) => { - cast_checks.push(quote! { - if <#ty as ::typst::eval::Reflect>::castable(&value) { - let #binding = <#ty as ::typst::eval::FromValue>::from_value(value)?; - return Ok(#expr); - } - }); - } - } - } - - let dynamic_check = input.name.is_some().then(|| { - quote! { - if let ::typst::eval::Value::Dyn(dynamic) = &value { - if let Some(concrete) = dynamic.downcast::<Self>() { - return Ok(concrete.clone()); - } - } - } - }); - - let str_check = (!string_arms.is_empty()).then(|| { - quote! { - if let ::typst::eval::Value::Str(string) = &value { - match string.as_str() { - #(#string_arms,)* - _ => {} - } - } - } - }); - - quote! { - #dynamic_check - #str_check - #(#cast_checks)* - Err(<Self as ::typst::eval::Reflect>::error(&value)) - } -} diff --git a/macros/src/element.rs b/macros/src/element.rs deleted file mode 100644 index 6ce91fcb..00000000 --- a/macros/src/element.rs +++ /dev/null @@ -1,554 +0,0 @@ -use super::*; - -/// Expand the `#[element]` macro. -pub fn element(stream: TokenStream, body: &syn::ItemStruct) -> Result<TokenStream> { - let element = prepare(stream, body)?; - Ok(create(&element)) -} - -struct Elem { - name: String, - display: String, - category: String, - keywords: Option<String>, - docs: String, - vis: syn::Visibility, - ident: Ident, - capable: Vec<Ident>, - fields: Vec<Field>, - scope: Option<BlockWithReturn>, -} - -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_in: Ident, - with_ident: Ident, - push_ident: Ident, - set_ident: Ident, - ty: syn::Type, - output: syn::Type, -} - -impl Field { - fn inherent(&self) -> bool { - self.required || self.variadic - } - - fn settable(&self) -> bool { - !self.inherent() - } -} - -/// Preprocess the element's definition. -fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Elem> { - let syn::Fields::Named(named) = &body.fields else { - bail!(body, "expected named fields"); - }; - - let mut fields = vec![]; - for field in &named.named { - let Some(ident) = field.ident.clone() else { - bail!(field, "expected named field"); - }; - - let mut attrs = field.attrs.clone(); - let variadic = has_attr(&mut attrs, "variadic"); - let required = has_attr(&mut attrs, "required") || variadic; - let positional = has_attr(&mut attrs, "positional") || required; - - if ident == "label" { - bail!(ident, "invalid field name"); - } - - let mut field = Field { - name: kebab_case(&ident), - docs: documentation(&attrs), - internal: has_attr(&mut attrs, "internal"), - external: has_attr(&mut attrs, "external"), - positional, - required, - variadic, - synthesized: has_attr(&mut attrs, "synthesized"), - fold: has_attr(&mut attrs, "fold"), - resolve: has_attr(&mut attrs, "resolve"), - parse: parse_attr(&mut attrs, "parse")?.flatten(), - default: parse_attr(&mut attrs, "default")? - .flatten() - .unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }), - vis: field.vis.clone(), - ident: ident.clone(), - ident_in: Ident::new(&format!("{}_in", ident), ident.span()), - with_ident: Ident::new(&format!("with_{}", ident), ident.span()), - push_ident: Ident::new(&format!("push_{}", ident), ident.span()), - set_ident: Ident::new(&format!("set_{}", ident), ident.span()), - ty: field.ty.clone(), - output: field.ty.clone(), - }; - - if field.required && (field.fold || field.resolve) { - bail!(ident, "required fields cannot be folded or resolved"); - } - - if field.required && !field.positional { - bail!(ident, "only positional fields can be required"); - } - - if field.resolve { - let output = &field.output; - field.output = parse_quote! { <#output as ::typst::model::Resolve>::Output }; - } - if field.fold { - let output = &field.output; - field.output = parse_quote! { <#output as ::typst::model::Fold>::Output }; - } - - validate_attrs(&attrs)?; - fields.push(field); - } - - let capable = Punctuated::<Ident, Token![,]>::parse_terminated - .parse2(stream)? - .into_iter() - .collect(); - - let 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, - vis: body.vis.clone(), - ident: body.ident.clone(), - capable, - fields, - scope: parse_attr(&mut attrs, "scope")?.flatten(), - }; - - validate_attrs(&attrs)?; - Ok(element) -} - -/// Produce the element's definition. -fn create(element: &Elem) -> TokenStream { - let Elem { vis, ident, docs, .. } = element; - let all = element.fields.iter().filter(|field| !field.external); - let settable = all.clone().filter(|field| !field.synthesized && field.settable()); - - // Inherent methods and functions. - let new = create_new_func(element); - let field_methods = all.clone().map(create_field_method); - let field_in_methods = settable.clone().map(create_field_in_method); - let with_field_methods = all.clone().map(create_with_field_method); - let push_field_methods = all.map(create_push_field_method); - let field_style_methods = settable.map(create_set_field_method); - - // Trait implementations. - let element_impl = create_pack_impl(element); - let construct_impl = element - .capable - .iter() - .all(|capability| capability != "Construct") - .then(|| create_construct_impl(element)); - let set_impl = create_set_impl(element); - let locatable_impl = element - .capable - .iter() - .any(|capability| capability == "Locatable") - .then(|| quote! { impl ::typst::model::Locatable for #ident {} }); - - quote! { - #[doc = #docs] - #[derive(Debug, Clone, Hash)] - #[repr(transparent)] - #vis struct #ident(pub ::typst::model::Content); - - impl #ident { - #new - #(#field_methods)* - #(#field_in_methods)* - #(#with_field_methods)* - #(#push_field_methods)* - #(#field_style_methods)* - - /// The element's span. - pub fn span(&self) -> ::typst::syntax::Span { - self.0.span() - } - - /// Set the element's span. - pub fn spanned(self, span: ::typst::syntax::Span) -> Self { - Self(self.0.spanned(span)) - } - } - - #element_impl - #construct_impl - #set_impl - #locatable_impl - - impl ::typst::eval::IntoValue for #ident { - fn into_value(self) -> ::typst::eval::Value { - ::typst::eval::Value::Content(self.0) - } - } - } -} - -/// Create the `new` function for the element. -fn create_new_func(element: &Elem) -> TokenStream { - let relevant = element - .fields - .iter() - .filter(|field| !field.external && !field.synthesized && field.inherent()); - let params = relevant.clone().map(|Field { ident, ty, .. }| { - quote! { #ident: #ty } - }); - let builder_calls = relevant.map(|Field { ident, with_ident, .. }| { - quote! { .#with_ident(#ident) } - }); - quote! { - /// Create a new element. - pub fn new(#(#params),*) -> Self { - Self(::typst::model::Content::new( - <Self as ::typst::model::Element>::func() - )) - #(#builder_calls)* - } - } -} - -/// Create an accessor methods for a field. -fn create_field_method(field: &Field) -> TokenStream { - let Field { vis, docs, ident, name, output, .. } = field; - if field.inherent() || field.synthesized { - quote! { - #[doc = #docs] - #[track_caller] - #vis fn #ident(&self) -> #output { - self.0.expect_field(#name) - } - } - } else { - let access = create_style_chain_access(field, quote! { self.0.field(#name) }); - quote! { - #[doc = #docs] - #vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output { - #access - } - } - } -} - -/// Create a style chain access method for a field. -fn create_field_in_method(field: &Field) -> TokenStream { - let Field { vis, ident_in, name, output, .. } = field; - let doc = format!("Access the `{}` field in the given style chain.", name); - let access = create_style_chain_access(field, quote! { None }); - quote! { - #[doc = #doc] - #vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output { - #access - } - } -} - -/// Create a style chain access method for a field. -fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStream { - let Field { name, ty, default, .. } = field; - let getter = match (field.fold, field.resolve) { - (false, false) => quote! { get }, - (false, true) => quote! { get_resolve }, - (true, false) => quote! { get_fold }, - (true, true) => quote! { get_resolve_fold }, - }; - - quote! { - styles.#getter::<#ty>( - <Self as ::typst::model::Element>::func(), - #name, - #inherent, - || #default, - ) - } -} - -/// Create a builder pattern method for a field. -fn create_with_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, with_ident, name, ty, .. } = field; - let doc = format!("Set the [`{}`](Self::{}) field.", name, ident); - quote! { - #[doc = #doc] - #vis fn #with_ident(mut self, #ident: #ty) -> Self { - Self(self.0.with_field(#name, #ident)) - } - } -} - -/// Create a set-style method for a field. -fn create_push_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, push_ident, name, ty, .. } = field; - let doc = format!("Push the [`{}`](Self::{}) field.", name, ident); - quote! { - #[doc = #doc] - #vis fn #push_ident(&mut self, #ident: #ty) { - self.0.push_field(#name, #ident); - } - } -} - -/// Create a setter method for a field. -fn create_set_field_method(field: &Field) -> TokenStream { - let Field { vis, ident, set_ident, name, ty, .. } = field; - let doc = format!("Create a style property for the `{}` field.", name); - quote! { - #[doc = #doc] - #vis fn #set_ident(#ident: #ty) -> ::typst::model::Style { - ::typst::model::Style::Property(::typst::model::Property::new( - <Self as ::typst::model::Element>::func(), - #name, - #ident, - )) - } - } -} - -/// Create the element's `Pack` implementation. -fn create_pack_impl(element: &Elem) -> TokenStream { - let Elem { ident, name, display, keywords, category, docs, .. } = element; - let vtable_func = create_vtable_func(element); - let infos = element - .fields - .iter() - .filter(|field| !field.internal && !field.synthesized) - .map(create_param_info); - let scope = create_scope_builder(element.scope.as_ref()); - let keywords = quote_option(keywords); - quote! { - impl ::typst::model::Element for #ident { - fn pack(self) -> ::typst::model::Content { - self.0 - } - - fn unpack(content: &::typst::model::Content) -> ::std::option::Option<&Self> { - // Safety: Elements are #[repr(transparent)]. - content.is::<Self>().then(|| unsafe { - ::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() - } - } - } -} - -/// Create the element's casting vtable. -fn create_vtable_func(element: &Elem) -> TokenStream { - let ident = &element.ident; - let relevant = element.capable.iter().filter(|&ident| ident != "Construct"); - let checks = relevant.map(|capability| { - quote! { - if id == ::std::any::TypeId::of::<dyn #capability>() { - return Some(unsafe { - ::typst::util::fat::vtable(&null as &dyn #capability) - }); - } - } - }); - - quote! { - |id| { - let null = Self(::typst::model::Content::new( - <#ident as ::typst::model::Element>::func() - )); - #(#checks)* - None - } - } -} - -/// Create a parameter info for a field. -fn create_param_info(field: &Field) -> TokenStream { - let Field { - name, - docs, - positional, - variadic, - required, - default, - fold, - ty, - output, - .. - } = field; - let named = !positional; - let settable = field.settable(); - let default_ty = if *fold { &output } else { &ty }; - let default = quote_option(&settable.then(|| { - quote! { - || { - let typed: #default_ty = #default; - ::typst::eval::IntoValue::into_value(typed) - } - } - })); - let ty = if *variadic { - quote! { <#ty as ::typst::eval::Variadics>::Inner } - } else { - quote! { #ty } - }; - quote! { - ::typst::eval::ParamInfo { - name: #name, - docs: #docs, - cast: <#ty as ::typst::eval::Reflect>::describe(), - default: #default, - positional: #positional, - named: #named, - variadic: #variadic, - required: #required, - settable: #settable, - } - } -} - -/// Create the element's `Construct` implementation. -fn create_construct_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - let handlers = element - .fields - .iter() - .filter(|field| { - !field.external - && !field.synthesized - && (!field.internal || field.parse.is_some()) - }) - .map(|field| { - let push_ident = &field.push_ident; - let (prefix, value) = create_field_parser(field); - if field.settable() { - quote! { - #prefix - if let Some(value) = #value { - element.#push_ident(value); - } - } - } else { - quote! { - #prefix - element.#push_ident(#value); - } - } - }); - - quote! { - impl ::typst::model::Construct for #ident { - fn construct( - vm: &mut ::typst::eval::Vm, - args: &mut ::typst::eval::Args, - ) -> ::typst::diag::SourceResult<::typst::model::Content> { - let mut element = Self(::typst::model::Content::new( - <Self as ::typst::model::Element>::func() - )); - #(#handlers)* - Ok(element.0) - } - } - } -} - -/// Create the element's `Set` implementation. -fn create_set_impl(element: &Elem) -> TokenStream { - let ident = &element.ident; - let handlers = element - .fields - .iter() - .filter(|field| { - !field.external - && !field.synthesized - && field.settable() - && (!field.internal || field.parse.is_some()) - }) - .map(|field| { - let set_ident = &field.set_ident; - let (prefix, value) = create_field_parser(field); - quote! { - #prefix - if let Some(value) = #value { - styles.set(Self::#set_ident(value)); - } - } - }); - - quote! { - impl ::typst::model::Set for #ident { - fn set( - args: &mut ::typst::eval::Args, - ) -> ::typst::diag::SourceResult<::typst::model::Styles> { - let mut styles = ::typst::model::Styles::new(); - #(#handlers)* - Ok(styles) - } - } - } -} - -/// Create argument parsing code for a field. -fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) { - if let Some(BlockWithReturn { prefix, expr }) = &field.parse { - return (quote! { #(#prefix);* }, quote! { #expr }); - } - - let name = &field.name; - let value = if field.variadic { - quote! { args.all()? } - } else if field.required { - quote! { args.expect(#name)? } - } else if field.positional { - quote! { args.find()? } - } else { - quote! { args.named(#name)? } - }; - - (quote! {}, value) -} diff --git a/macros/src/func.rs b/macros/src/func.rs deleted file mode 100644 index 4a68e846..00000000 --- a/macros/src/func.rs +++ /dev/null @@ -1,268 +0,0 @@ -use super::*; - -/// Expand the `#[func]` macro. -pub fn func(stream: TokenStream, item: &syn::ItemFn) -> Result<TokenStream> { - let func = prepare(stream, item)?; - Ok(create(&func, item)) -} - -struct Func { - name: String, - display: String, - category: String, - keywords: Option<String>, - docs: String, - vis: syn::Visibility, - ident: Ident, - ident_func: Ident, - parent: Option<syn::Type>, - vm: bool, - vt: bool, - args: bool, - span: bool, - params: Vec<Param>, - returns: syn::Type, - scope: Option<BlockWithReturn>, -} - -struct Param { - name: String, - docs: String, - external: bool, - named: bool, - variadic: bool, - default: Option<syn::Expr>, - ident: Ident, - ty: syn::Type, -} - -fn prepare(stream: TokenStream, item: &syn::ItemFn) -> Result<Func> { - let sig = &item.sig; - - let Parent(parent) = syn::parse2(stream)?; - - let mut vm = false; - let mut vt = false; - let mut args = false; - let mut span = false; - let mut params = vec![]; - for input in &sig.inputs { - let syn::FnArg::Typed(typed) = input else { - bail!(input, "self is not allowed here"); - }; - - 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" => vm = true, - "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)?; - } - } - } - - let mut attrs = item.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 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) -} - -fn create(func: &Func, item: &syn::ItemFn) -> TokenStream { - let Func { - name, - display, - category, - docs, - vis, - ident, - ident_func, - returns, - .. - } = func; - - let handlers = func - .params - .iter() - .filter(|param| !param.external) - .map(create_param_parser); - - let args = func - .params - .iter() - .filter(|param| !param.external) - .map(|param| ¶m.ident); - - let parent = func.parent.as_ref().map(|ty| quote! { #ty:: }); - let vm_ = func.vm.then(|| quote! { vm, }); - let vt_ = func.vt.then(|| quote! { &mut vm.vt, }); - let args_ = func.args.then(|| quote! { args.take(), }); - let span_ = func.span.then(|| quote! { args.span, }); - let wrapper = quote! { - |vm, args| { - let __typst_func = #parent #ident; - #(#handlers)* - let output = __typst_func(#(#args,)* #vm_ #vt_ #args_ #span_); - ::typst::eval::IntoResult::into_result(output, args.span) - } - }; - - let mut item = item.clone(); - item.attrs.clear(); - - 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) - }); - - 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()); - - quote! { - #[doc(hidden)] - #vis fn #ident_func() -> &'static ::typst::eval::NativeFunc { - static FUNC: ::typst::eval::NativeFunc = ::typst::eval::NativeFunc { - func: #wrapper, - info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo { - 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 - } -} - -/// Create a parameter info for a field. -fn create_param_info(param: &Param) -> TokenStream { - let Param { name, docs, named, variadic, ty, default, .. } = param; - let positional = !named; - let required = default.is_none(); - let default = quote_option(&default.as_ref().map(|_default| { - quote! { - || { - let typed: #ty = #default; - ::typst::eval::IntoValue::into_value(typed) - } - } - })); - let ty = if *variadic { - quote! { <#ty as ::typst::eval::Variadics>::Inner } - } else { - quote! { #ty } - }; - quote! { - ::typst::eval::ParamInfo { - name: #name, - docs: #docs, - cast: <#ty as ::typst::eval::Reflect>::describe(), - default: #default, - positional: #positional, - named: #named, - variadic: #variadic, - required: #required, - settable: false, - } - } -} - -/// Create argument parsing code for a parameter. -fn create_param_parser(param: &Param) -> TokenStream { - let Param { name, ident, ty, .. } = param; - - let mut value = if param.variadic { - quote! { args.all()? } - } else if param.named { - quote! { args.named(#name)? } - } else if param.default.is_some() { - quote! { args.eat()? } - } else { - quote! { args.expect(#name)? } - }; - - if let Some(default) = ¶m.default { - value = quote! { #value.unwrap_or_else(|| #default) } - } - - quote! { let mut #ident: #ty = #value; } -} - -struct Parent(Option<syn::Type>); - -impl Parse for Parent { - fn parse(input: ParseStream) -> Result<Self> { - Ok(Self(if !input.is_empty() { Some(input.parse()?) } else { None })) - } -} diff --git a/macros/src/lib.rs b/macros/src/lib.rs deleted file mode 100644 index 49840ef2..00000000 --- a/macros/src/lib.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Procedural macros for Typst. - -extern crate proc_macro; - -#[macro_use] -mod util; -mod castable; -mod element; -mod func; -mod symbols; - -use proc_macro::TokenStream as BoundaryStream; -use proc_macro2::TokenStream; -use quote::quote; -use syn::ext::IdentExt; -use syn::parse::{Parse, ParseStream, Parser}; -use syn::punctuated::Punctuated; -use syn::{parse_quote, DeriveInput, Ident, Result, Token}; - -use self::util::*; - -/// Turns a function into a `NativeFunc`. -#[proc_macro_attribute] -pub fn func(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { - let item = syn::parse_macro_input!(item as syn::ItemFn); - func::func(stream.into(), &item) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - -/// Turns a type into an `Element`. -#[proc_macro_attribute] -pub fn element(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { - let item = syn::parse_macro_input!(item as syn::ItemStruct); - element::element(stream.into(), &item) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - -/// Implements `Reflect`, `FromValue`, and `IntoValue` for an enum. -#[proc_macro_derive(Cast, attributes(string))] -pub fn derive_cast(item: BoundaryStream) -> BoundaryStream { - let item = syn::parse_macro_input!(item as DeriveInput); - castable::derive_cast(&item) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - -/// Implements `Reflect`, `FromValue`, and `IntoValue` for a type. -#[proc_macro] -pub fn cast(stream: BoundaryStream) -> BoundaryStream { - castable::cast(stream.into()) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - -/// Defines a list of `Symbol`s. -#[proc_macro] -pub fn symbols(stream: BoundaryStream) -> BoundaryStream { - symbols::symbols(stream.into()) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} diff --git a/macros/src/symbols.rs b/macros/src/symbols.rs deleted file mode 100644 index cdb7f5d7..00000000 --- a/macros/src/symbols.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::*; - -/// Expand the `symbols!` macro. -pub fn symbols(stream: TokenStream) -> Result<TokenStream> { - let list: Punctuated<Symbol, Token![,]> = - Punctuated::parse_terminated.parse2(stream)?; - let pairs = list.iter().map(|symbol| { - let name = symbol.name.to_string(); - let kind = match &symbol.kind { - Kind::Single(c) => quote! { typst::eval::Symbol::new(#c), }, - Kind::Multiple(variants) => { - let variants = variants.iter().map(|variant| { - let name = &variant.name; - let c = &variant.c; - quote! { (#name, #c) } - }); - quote! { - typst::eval::Symbol::list(&[#(#variants),*]) - } - } - }; - quote! { (#name, #kind) } - }); - Ok(quote! { &[#(#pairs),*] }) -} - -struct Symbol { - name: syn::Ident, - kind: Kind, -} - -enum Kind { - Single(syn::LitChar), - Multiple(Punctuated<Variant, Token![,]>), -} - -struct Variant { - name: String, - c: syn::LitChar, -} - -impl Parse for Symbol { - fn parse(input: ParseStream) -> Result<Self> { - let name = input.call(Ident::parse_any)?; - input.parse::<Token![:]>()?; - let kind = input.parse()?; - Ok(Self { name, kind }) - } -} - -impl Parse for Kind { - fn parse(input: ParseStream) -> Result<Self> { - if input.peek(syn::LitChar) { - Ok(Self::Single(input.parse()?)) - } else { - let content; - syn::bracketed!(content in input); - Ok(Self::Multiple(Punctuated::parse_terminated(&content)?)) - } - } -} - -impl Parse for Variant { - fn parse(input: ParseStream) -> Result<Self> { - let mut name = String::new(); - if input.peek(syn::Ident::peek_any) { - name.push_str(&input.call(Ident::parse_any)?.to_string()); - while input.peek(Token![.]) { - input.parse::<Token![.]>()?; - name.push('.'); - name.push_str(&input.call(Ident::parse_any)?.to_string()); - } - input.parse::<Token![:]>()?; - } - let c = input.parse()?; - Ok(Self { name, c }) - } -} diff --git a/macros/src/util.rs b/macros/src/util.rs deleted file mode 100644 index 389fed06..00000000 --- a/macros/src/util.rs +++ /dev/null @@ -1,145 +0,0 @@ -use heck::ToKebabCase; -use quote::ToTokens; - -use super::*; - -/// Return an error at the given item. -macro_rules! bail { - (callsite, $($tts:tt)*) => { - return Err(syn::Error::new( - proc_macro2::Span::call_site(), - format!("typst: {}", format!($($tts)*)) - )) - }; - ($item:expr, $($tts:tt)*) => { - return Err(syn::Error::new_spanned( - &$item, - format!("typst: {}", format!($($tts)*)) - )) - }; -} - -/// 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 }) - } -} - -/// Whether an attribute list has a specified attribute. -pub fn has_attr(attrs: &mut Vec<syn::Attribute>, target: &str) -> bool { - take_attr(attrs, target).is_some() -} - -/// Whether an attribute list has a specified attribute. -pub fn parse_attr<T: Parse>( - attrs: &mut Vec<syn::Attribute>, - target: &str, -) -> Result<Option<Option<T>>> { - take_attr(attrs, target) - .map(|attr| { - Ok(match attr.meta { - syn::Meta::Path(_) => None, - syn::Meta::List(list) => Some(list.parse_args()?), - syn::Meta::NameValue(meta) => bail!(meta, "not valid here"), - }) - }) - .transpose() -} - -/// Whether an attribute list has a specified attribute. -pub fn take_attr( - attrs: &mut Vec<syn::Attribute>, - target: &str, -) -> Option<syn::Attribute> { - attrs - .iter() - .position(|attr| attr.path().is_ident(target)) - .map(|i| attrs.remove(i)) -} - -/// Ensure that no unrecognized attributes remain. -pub fn validate_attrs(attrs: &[syn::Attribute]) -> Result<()> { - for attr in attrs { - if !attr.path().is_ident("doc") && !attr.path().is_ident("derive") { - let ident = attr.path().get_ident().unwrap(); - bail!(ident, "unrecognized attribute: {ident}"); - } - } - 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. -pub fn quote_option<T: ToTokens>(option: &Option<T>) -> TokenStream { - if let Some(value) = option { - quote! { Some(#value) } - } else { - quote! { None } - } -} |
