diff options
Diffstat (limited to 'crates/typst-library/src/foundations/func.rs')
| -rw-r--r-- | crates/typst-library/src/foundations/func.rs | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/crates/typst-library/src/foundations/func.rs b/crates/typst-library/src/foundations/func.rs new file mode 100644 index 00000000..1b40714b --- /dev/null +++ b/crates/typst-library/src/foundations/func.rs @@ -0,0 +1,541 @@ +#[doc(inline)] +pub use typst_macros::func; + +use std::fmt::{self, Debug, Formatter}; +use std::sync::Arc; + +use comemo::{Tracked, TrackedMut}; +use ecow::{eco_format, EcoString}; +use once_cell::sync::Lazy; +use typst_syntax::{ast, Span, SyntaxNode}; +use typst_utils::{singleton, LazyHash, Static}; + +use crate::diag::{bail, SourceResult, StrResult}; +use crate::engine::Engine; +use crate::foundations::{ + cast, repr, scope, ty, Args, CastInfo, Content, Context, Element, IntoArgs, Scope, + Selector, Type, Value, +}; + +/// A mapping from argument values to a return value. +/// +/// You can call a function by writing a comma-separated list of function +/// _arguments_ enclosed in parentheses directly after the function name. +/// Additionally, you can pass any number of trailing content blocks arguments +/// to a function _after_ the normal argument list. If the normal argument list +/// would become empty, it can be omitted. Typst supports positional and named +/// arguments. The former are identified by position and type, while the latter +/// are written as `name: value`. +/// +/// Within math mode, function calls have special behaviour. See the +/// [math documentation]($category/math) for more details. +/// +/// # Example +/// ```example +/// // Call a function. +/// #list([A], [B]) +/// +/// // Named arguments and trailing +/// // content blocks. +/// #enum(start: 2)[A][B] +/// +/// // Version without parentheses. +/// #list[A][B] +/// ``` +/// +/// Functions are a fundamental building block of Typst. Typst provides +/// functions for a variety of typesetting tasks. Moreover, the markup you write +/// is backed by functions and all styling happens through functions. This +/// reference lists all available functions and how you can use them. Please +/// also refer to the documentation about [set]($styling/#set-rules) and +/// [show]($styling/#show-rules) rules to learn about additional ways you can +/// work with functions in Typst. +/// +/// # Element functions +/// Some functions are associated with _elements_ like [headings]($heading) or +/// [tables]($table). When called, these create an element of their respective +/// kind. In contrast to normal functions, they can further be used in [set +/// rules]($styling/#set-rules), [show rules]($styling/#show-rules), and +/// [selectors]($selector). +/// +/// # Function scopes +/// Functions can hold related definitions in their own scope, similar to a +/// [module]($scripting/#modules). Examples of this are +/// [`assert.eq`]($assert.eq) or [`list.item`]($list.item). However, this +/// feature is currently only available for built-in functions. +/// +/// # Defining functions +/// You can define your own function with a [let binding]($scripting/#bindings) +/// that has a parameter list after the binding's name. The parameter list can +/// contain mandatory positional parameters, named parameters with default +/// values and [argument sinks]($arguments). +/// +/// The right-hand side of a function binding is the function body, which can be +/// a block or any other expression. It defines the function's return value and +/// can depend on the parameters. If the function body is a [code +/// block]($scripting/#blocks), the return value is the result of joining the +/// values of each expression in the block. +/// +/// Within a function body, the `return` keyword can be used to exit early and +/// optionally specify a return value. If no explicit return value is given, the +/// body evaluates to the result of joining all expressions preceding the +/// `return`. +/// +/// Functions that don't return any meaningful value return [`none`] instead. +/// The return type of such functions is not explicitly specified in the +/// documentation. (An example of this is [`array.push`]). +/// +/// ```example +/// #let alert(body, fill: red) = { +/// set text(white) +/// set align(center) +/// rect( +/// fill: fill, +/// inset: 8pt, +/// radius: 4pt, +/// [*Warning:\ #body*], +/// ) +/// } +/// +/// #alert[ +/// Danger is imminent! +/// ] +/// +/// #alert(fill: blue)[ +/// KEEP OFF TRACKS +/// ] +/// ``` +/// +/// # Importing functions +/// Functions can be imported from one file ([`module`]($scripting/#modules)) into +/// another using `{import}`. For example, assume that we have defined the `alert` +/// function from the previous example in a file called `foo.typ`. We can import +/// it into another file by writing `{import "foo.typ": alert}`. +/// +/// # Unnamed functions { #unnamed } +/// You can also created an unnamed function without creating a binding by +/// specifying a parameter list followed by `=>` and the function body. If your +/// function has just one parameter, the parentheses around the parameter list +/// are optional. Unnamed functions are mainly useful for show rules, but also +/// for settable properties that take functions like the page function's +/// [`footer`]($page.footer) property. +/// +/// ```example +/// #show "once?": it => [#it #it] +/// once? +/// ``` +/// +/// # Note on function purity +/// In Typst, all functions are _pure._ This means that for the same +/// arguments, they always return the same result. They cannot "remember" things to +/// produce another value when they are called a second time. +/// +/// The only exception are built-in methods like +/// [`array.push(value)`]($array.push). These can modify the values they are +/// called on. +#[ty(scope, cast, name = "function")] +#[derive(Clone, Hash)] +#[allow(clippy::derived_hash_with_manual_eq)] +pub struct Func { + /// The internal representation. + repr: Repr, + /// The span with which errors are reported when this function is called. + span: Span, +} + +/// The different kinds of function representations. +#[derive(Clone, PartialEq, Hash)] +enum Repr { + /// A native Rust function. + Native(Static<NativeFuncData>), + /// A function for an element. + Element(Element), + /// A user-defined closure. + Closure(Arc<LazyHash<Closure>>), + /// A nested function with pre-applied arguments. + With(Arc<(Func, Args)>), +} + +impl Func { + /// The function's name (e.g. `min`). + /// + /// Returns `None` if this is an anonymous closure. + pub fn name(&self) -> Option<&str> { + match &self.repr { + Repr::Native(native) => Some(native.name), + Repr::Element(elem) => Some(elem.name()), + Repr::Closure(closure) => closure.name(), + Repr::With(with) => with.0.name(), + } + } + + /// The function's title case name, for use in documentation (e.g. `Minimum`). + /// + /// Returns `None` if this is a closure. + pub fn title(&self) -> Option<&'static str> { + match &self.repr { + Repr::Native(native) => Some(native.title), + Repr::Element(elem) => Some(elem.title()), + Repr::Closure(_) => None, + Repr::With(with) => with.0.title(), + } + } + + /// Documentation for the function (as Markdown). + pub fn docs(&self) -> Option<&'static str> { + match &self.repr { + Repr::Native(native) => Some(native.docs), + Repr::Element(elem) => Some(elem.docs()), + Repr::Closure(_) => None, + Repr::With(with) => with.0.docs(), + } + } + + /// Whether the function is known to be contextual. + pub fn contextual(&self) -> Option<bool> { + match &self.repr { + Repr::Native(native) => Some(native.contextual), + _ => None, + } + } + + /// Get details about this function's parameters if available. + pub fn params(&self) -> Option<&'static [ParamInfo]> { + match &self.repr { + Repr::Native(native) => Some(&native.0.params), + Repr::Element(elem) => Some(elem.params()), + Repr::Closure(_) => None, + Repr::With(with) => with.0.params(), + } + } + + /// Get the parameter info for a parameter with the given name if it exist. + pub fn param(&self, name: &str) -> Option<&'static ParamInfo> { + self.params()?.iter().find(|param| param.name == name) + } + + /// Get details about the function's return type. + pub fn returns(&self) -> Option<&'static CastInfo> { + match &self.repr { + Repr::Native(native) => Some(&native.0.returns), + Repr::Element(_) => { + Some(singleton!(CastInfo, CastInfo::Type(Type::of::<Content>()))) + } + Repr::Closure(_) => None, + Repr::With(with) => with.0.returns(), + } + } + + /// Search keywords for the function. + pub fn keywords(&self) -> &'static [&'static str] { + match &self.repr { + Repr::Native(native) => native.keywords, + Repr::Element(elem) => elem.keywords(), + Repr::Closure(_) => &[], + Repr::With(with) => with.0.keywords(), + } + } + + /// The function's associated scope of sub-definition. + pub fn scope(&self) -> Option<&'static Scope> { + match &self.repr { + Repr::Native(native) => Some(&native.0.scope), + Repr::Element(elem) => Some(elem.scope()), + Repr::Closure(_) => None, + Repr::With(with) => with.0.scope(), + } + } + + /// Get a field from this function's scope, if possible. + pub fn field(&self, field: &str) -> StrResult<&'static Value> { + let scope = + self.scope().ok_or("cannot access fields on user-defined functions")?; + match scope.get(field) { + Some(field) => Ok(field), + None => match self.name() { + Some(name) => bail!("function `{name}` does not contain field `{field}`"), + None => bail!("function does not contain field `{field}`"), + }, + } + } + + /// Extract the element function, if it is one. + pub fn element(&self) -> Option<Element> { + match self.repr { + Repr::Element(func) => Some(func), + _ => None, + } + } + + /// Call the function with the given context and arguments. + pub fn call<A: IntoArgs>( + &self, + engine: &mut Engine, + context: Tracked<Context>, + args: A, + ) -> SourceResult<Value> { + self.call_impl(engine, context, args.into_args(self.span)) + } + + /// Non-generic implementation of `call`. + #[typst_macros::time(name = "func call", span = self.span())] + fn call_impl( + &self, + engine: &mut Engine, + context: Tracked<Context>, + mut args: Args, + ) -> SourceResult<Value> { + match &self.repr { + Repr::Native(native) => { + let value = (native.function)(engine, context, &mut args)?; + args.finish()?; + Ok(value) + } + Repr::Element(func) => { + let value = func.construct(engine, &mut args)?; + args.finish()?; + Ok(Value::Content(value)) + } + Repr::Closure(closure) => (engine.routines.eval_closure)( + self, + closure, + engine.routines, + engine.world, + engine.introspector, + engine.traced, + TrackedMut::reborrow_mut(&mut engine.sink), + engine.route.track(), + context, + args, + ), + Repr::With(with) => { + args.items = with.1.items.iter().cloned().chain(args.items).collect(); + with.0.call(engine, context, args) + } + } + } + + /// The function's span. + pub fn span(&self) -> Span { + self.span + } + + /// Attach a span to this function if it doesn't already have one. + pub fn spanned(mut self, span: Span) -> Self { + if self.span.is_detached() { + self.span = span; + } + self + } +} + +#[scope] +impl Func { + /// Returns a new function that has the given arguments pre-applied. + #[func] + pub fn with( + self, + /// The real arguments (the other argument is just for the docs). + /// The docs argument cannot be called `args`. + args: &mut Args, + /// The arguments to apply to the function. + #[external] + #[variadic] + arguments: Vec<Value>, + ) -> Func { + let span = self.span; + Self { + repr: Repr::With(Arc::new((self, args.take()))), + span, + } + } + + /// Returns a selector that filters for elements belonging to this function + /// whose fields have the values of the given arguments. + /// + /// ```example + /// #show heading.where(level: 2): set text(blue) + /// = Section + /// == Subsection + /// === Sub-subsection + /// ``` + #[func] + pub fn where_( + self, + /// The real arguments (the other argument is just for the docs). + /// The docs argument cannot be called `args`. + args: &mut Args, + /// The fields to filter for. + #[variadic] + #[external] + fields: Vec<Value>, + ) -> StrResult<Selector> { + let fields = args.to_named(); + args.items.retain(|arg| arg.name.is_none()); + + let element = self + .element() + .ok_or("`where()` can only be called on element functions")?; + + let fields = fields + .into_iter() + .map(|(key, value)| { + element.field_id(&key).map(|id| (id, value)).ok_or_else(|| { + eco_format!( + "element `{}` does not have field `{}`", + element.name(), + key + ) + }) + }) + .collect::<StrResult<smallvec::SmallVec<_>>>()?; + + Ok(element.where_(fields)) + } +} + +impl Debug for Func { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Func({})", self.name().unwrap_or("..")) + } +} + +impl repr::Repr for Func { + fn repr(&self) -> EcoString { + match self.name() { + Some(name) => name.into(), + None => "(..) => ..".into(), + } + } +} + +impl PartialEq for Func { + fn eq(&self, other: &Self) -> bool { + self.repr == other.repr + } +} + +impl PartialEq<&NativeFuncData> for Func { + fn eq(&self, other: &&NativeFuncData) -> bool { + match &self.repr { + Repr::Native(native) => native.function == other.function, + _ => false, + } + } +} + +impl From<Repr> for Func { + fn from(repr: Repr) -> Self { + Self { repr, span: Span::detached() } + } +} + +impl From<Element> for Func { + fn from(func: Element) -> Self { + Repr::Element(func).into() + } +} + +/// A Typst function that is defined by a native Rust type that shadows a +/// native Rust function. +pub trait NativeFunc { + /// Get the function for the native Rust type. + fn func() -> Func { + Func::from(Self::data()) + } + + /// Get the function data for the native Rust type. + fn data() -> &'static NativeFuncData; +} + +/// Defines a native function. +#[derive(Debug)] +pub struct NativeFuncData { + /// Invokes the function from Typst. + pub function: fn(&mut Engine, Tracked<Context>, &mut Args) -> SourceResult<Value>, + /// The function's normal name (e.g. `align`), as exposed to Typst. + pub name: &'static str, + /// The function's title case name (e.g. `Align`). + pub title: &'static str, + /// The documentation for this function as a string. + pub docs: &'static str, + /// A list of alternate search terms for this function. + pub keywords: &'static [&'static str], + /// Whether this function makes use of context. + pub contextual: bool, + pub scope: Lazy<Scope>, + /// A list of parameter information for each parameter. + pub params: Lazy<Vec<ParamInfo>>, + /// Information about the return value of this function. + pub returns: Lazy<CastInfo>, +} + +impl From<&'static NativeFuncData> for Func { + fn from(data: &'static NativeFuncData) -> Self { + Repr::Native(Static(data)).into() + } +} + +cast! { + &'static NativeFuncData, + self => Func::from(self).into_value(), +} + +/// Describes a function parameter. +#[derive(Debug, Clone)] +pub struct ParamInfo { + /// The parameter's name. + pub name: &'static str, + /// Documentation for the parameter. + pub docs: &'static str, + /// Describe what values this parameter accepts. + pub input: CastInfo, + /// Creates an instance of the parameter's default value. + pub default: Option<fn() -> Value>, + /// Is the parameter positional? + pub positional: bool, + /// Is the parameter named? + /// + /// Can be true even if `positional` is true if the parameter can be given + /// in both variants. + pub named: bool, + /// Can the parameter be given any number of times? + pub variadic: bool, + /// Is the parameter required? + pub required: bool, + /// Is the parameter settable with a set rule? + pub settable: bool, +} + +/// A user-defined closure. +#[derive(Debug, Hash)] +pub struct Closure { + /// The closure's syntax node. Must be either castable to `ast::Closure` or + /// `ast::Expr`. In the latter case, this is a synthesized closure without + /// any parameters (used by `context` expressions). + pub node: SyntaxNode, + /// Default values of named parameters. + pub defaults: Vec<Value>, + /// Captured values from outer scopes. + pub captured: Scope, + /// The number of positional parameters in the closure. + pub num_pos_params: usize, +} + +impl Closure { + /// The name of the closure. + pub fn name(&self) -> Option<&str> { + self.node.cast::<ast::Closure>()?.name().map(|ident| ident.as_str()) + } +} + +impl From<Closure> for Func { + fn from(closure: Closure) -> Self { + Repr::Closure(Arc::new(LazyHash::new(closure))).into() + } +} + +cast! { + Closure, + self => Value::Func(self.into()), +} |
