summaryrefslogtreecommitdiff
path: root/crates/typst-macros/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-macros/src/lib.rs')
-rw-r--r--crates/typst-macros/src/lib.rs295
1 files changed, 283 insertions, 12 deletions
diff --git a/crates/typst-macros/src/lib.rs b/crates/typst-macros/src/lib.rs
index 49840ef2..52f3e237 100644
--- a/crates/typst-macros/src/lib.rs
+++ b/crates/typst-macros/src/lib.rs
@@ -4,10 +4,12 @@ extern crate proc_macro;
#[macro_use]
mod util;
-mod castable;
-mod element;
+mod cast;
+mod elem;
mod func;
+mod scope;
mod symbols;
+mod ty;
use proc_macro::TokenStream as BoundaryStream;
use proc_macro2::TokenStream;
@@ -19,7 +21,79 @@ use syn::{parse_quote, DeriveInput, Ident, Result, Token};
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]
pub fn func(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
let item = syn::parse_macro_input!(item as syn::ItemFn);
@@ -28,33 +102,230 @@ pub fn func(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
.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]
-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::Item);
+ ty::ty(stream.into(), item)
+ .unwrap_or_else(|err| err.to_compile_error())
+ .into()
+}
+
+/// Makes a native Rust type usable as a Typst element.
+///
+/// This implements `NativeElement` for the given type.
+///
+/// ```
+/// /// 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);
- element::element(stream.into(), &item)
+ elem::elem(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)
+/// 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())
.into()
}
/// 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]
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())
.into()
}
/// 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]
pub fn symbols(stream: BoundaryStream) -> BoundaryStream {
symbols::symbols(stream.into())