typst_library/foundations/func.rs
1#[doc(inline)]
2pub use typst_macros::func;
3
4use std::fmt::{self, Debug, Formatter};
5use std::sync::{Arc, LazyLock};
6
7use comemo::{Tracked, TrackedMut};
8use ecow::{eco_format, EcoString};
9use typst_syntax::{ast, Span, SyntaxNode};
10use typst_utils::{singleton, LazyHash, Static};
11
12use crate::diag::{bail, At, DeprecationSink, SourceResult, StrResult};
13use crate::engine::Engine;
14use crate::foundations::{
15 cast, repr, scope, ty, Args, Bytes, CastInfo, Content, Context, Element, IntoArgs,
16 PluginFunc, Scope, Selector, Type, Value,
17};
18
19/// A mapping from argument values to a return value.
20///
21/// You can call a function by writing a comma-separated list of function
22/// _arguments_ enclosed in parentheses directly after the function name.
23/// Additionally, you can pass any number of trailing content blocks arguments
24/// to a function _after_ the normal argument list. If the normal argument list
25/// would become empty, it can be omitted. Typst supports positional and named
26/// arguments. The former are identified by position and type, while the latter
27/// are written as `name: value`.
28///
29/// Within math mode, function calls have special behaviour. See the
30/// [math documentation]($category/math) for more details.
31///
32/// # Example
33/// ```example
34/// // Call a function.
35/// #list([A], [B])
36///
37/// // Named arguments and trailing
38/// // content blocks.
39/// #enum(start: 2)[A][B]
40///
41/// // Version without parentheses.
42/// #list[A][B]
43/// ```
44///
45/// Functions are a fundamental building block of Typst. Typst provides
46/// functions for a variety of typesetting tasks. Moreover, the markup you write
47/// is backed by functions and all styling happens through functions. This
48/// reference lists all available functions and how you can use them. Please
49/// also refer to the documentation about [set]($styling/#set-rules) and
50/// [show]($styling/#show-rules) rules to learn about additional ways you can
51/// work with functions in Typst.
52///
53/// # Element functions
54/// Some functions are associated with _elements_ like [headings]($heading) or
55/// [tables]($table). When called, these create an element of their respective
56/// kind. In contrast to normal functions, they can further be used in [set
57/// rules]($styling/#set-rules), [show rules]($styling/#show-rules), and
58/// [selectors]($selector).
59///
60/// # Function scopes
61/// Functions can hold related definitions in their own scope, similar to a
62/// [module]($scripting/#modules). Examples of this are
63/// [`assert.eq`]($assert.eq) or [`list.item`]($list.item). However, this
64/// feature is currently only available for built-in functions.
65///
66/// # Defining functions
67/// You can define your own function with a [let binding]($scripting/#bindings)
68/// that has a parameter list after the binding's name. The parameter list can
69/// contain mandatory positional parameters, named parameters with default
70/// values and [argument sinks]($arguments).
71///
72/// The right-hand side of a function binding is the function body, which can be
73/// a block or any other expression. It defines the function's return value and
74/// can depend on the parameters. If the function body is a [code
75/// block]($scripting/#blocks), the return value is the result of joining the
76/// values of each expression in the block.
77///
78/// Within a function body, the `return` keyword can be used to exit early and
79/// optionally specify a return value. If no explicit return value is given, the
80/// body evaluates to the result of joining all expressions preceding the
81/// `return`.
82///
83/// Functions that don't return any meaningful value return [`none`] instead.
84/// The return type of such functions is not explicitly specified in the
85/// documentation. (An example of this is [`array.push`]).
86///
87/// ```example
88/// #let alert(body, fill: red) = {
89/// set text(white)
90/// set align(center)
91/// rect(
92/// fill: fill,
93/// inset: 8pt,
94/// radius: 4pt,
95/// [*Warning:\ #body*],
96/// )
97/// }
98///
99/// #alert[
100/// Danger is imminent!
101/// ]
102///
103/// #alert(fill: blue)[
104/// KEEP OFF TRACKS
105/// ]
106/// ```
107///
108/// # Importing functions
109/// Functions can be imported from one file ([`module`]($scripting/#modules)) into
110/// another using `{import}`. For example, assume that we have defined the `alert`
111/// function from the previous example in a file called `foo.typ`. We can import
112/// it into another file by writing `{import "foo.typ": alert}`.
113///
114/// # Unnamed functions { #unnamed }
115/// You can also created an unnamed function without creating a binding by
116/// specifying a parameter list followed by `=>` and the function body. If your
117/// function has just one parameter, the parentheses around the parameter list
118/// are optional. Unnamed functions are mainly useful for show rules, but also
119/// for settable properties that take functions like the page function's
120/// [`footer`]($page.footer) property.
121///
122/// ```example
123/// #show "once?": it => [#it #it]
124/// once?
125/// ```
126///
127/// # Note on function purity
128/// In Typst, all functions are _pure._ This means that for the same
129/// arguments, they always return the same result. They cannot "remember" things to
130/// produce another value when they are called a second time.
131///
132/// The only exception are built-in methods like
133/// [`array.push(value)`]($array.push). These can modify the values they are
134/// called on.
135#[ty(scope, cast, name = "function")]
136#[derive(Clone, Hash)]
137#[allow(clippy::derived_hash_with_manual_eq)]
138pub struct Func {
139 /// The internal representation.
140 repr: Repr,
141 /// The span with which errors are reported when this function is called.
142 span: Span,
143}
144
145/// The different kinds of function representations.
146#[derive(Clone, PartialEq, Hash)]
147enum Repr {
148 /// A native Rust function.
149 Native(Static<NativeFuncData>),
150 /// A function for an element.
151 Element(Element),
152 /// A user-defined closure.
153 Closure(Arc<LazyHash<Closure>>),
154 /// A plugin WebAssembly function.
155 Plugin(Arc<PluginFunc>),
156 /// A nested function with pre-applied arguments.
157 With(Arc<(Func, Args)>),
158}
159
160impl Func {
161 /// The function's name (e.g. `min`).
162 ///
163 /// Returns `None` if this is an anonymous closure.
164 pub fn name(&self) -> Option<&str> {
165 match &self.repr {
166 Repr::Native(native) => Some(native.name),
167 Repr::Element(elem) => Some(elem.name()),
168 Repr::Closure(closure) => closure.name(),
169 Repr::Plugin(func) => Some(func.name()),
170 Repr::With(with) => with.0.name(),
171 }
172 }
173
174 /// The function's title case name, for use in documentation (e.g. `Minimum`).
175 ///
176 /// Returns `None` if this is a closure.
177 pub fn title(&self) -> Option<&'static str> {
178 match &self.repr {
179 Repr::Native(native) => Some(native.title),
180 Repr::Element(elem) => Some(elem.title()),
181 Repr::Closure(_) => None,
182 Repr::Plugin(_) => None,
183 Repr::With(with) => with.0.title(),
184 }
185 }
186
187 /// Documentation for the function (as Markdown).
188 pub fn docs(&self) -> Option<&'static str> {
189 match &self.repr {
190 Repr::Native(native) => Some(native.docs),
191 Repr::Element(elem) => Some(elem.docs()),
192 Repr::Closure(_) => None,
193 Repr::Plugin(_) => None,
194 Repr::With(with) => with.0.docs(),
195 }
196 }
197
198 /// Whether the function is known to be contextual.
199 pub fn contextual(&self) -> Option<bool> {
200 match &self.repr {
201 Repr::Native(native) => Some(native.contextual),
202 _ => None,
203 }
204 }
205
206 /// Get details about this function's parameters if available.
207 pub fn params(&self) -> Option<&'static [ParamInfo]> {
208 match &self.repr {
209 Repr::Native(native) => Some(&native.0.params),
210 Repr::Element(elem) => Some(elem.params()),
211 Repr::Closure(_) => None,
212 Repr::Plugin(_) => None,
213 Repr::With(with) => with.0.params(),
214 }
215 }
216
217 /// Get the parameter info for a parameter with the given name if it exist.
218 pub fn param(&self, name: &str) -> Option<&'static ParamInfo> {
219 self.params()?.iter().find(|param| param.name == name)
220 }
221
222 /// Get details about the function's return type.
223 pub fn returns(&self) -> Option<&'static CastInfo> {
224 match &self.repr {
225 Repr::Native(native) => Some(&native.0.returns),
226 Repr::Element(_) => {
227 Some(singleton!(CastInfo, CastInfo::Type(Type::of::<Content>())))
228 }
229 Repr::Closure(_) => None,
230 Repr::Plugin(_) => None,
231 Repr::With(with) => with.0.returns(),
232 }
233 }
234
235 /// Search keywords for the function.
236 pub fn keywords(&self) -> &'static [&'static str] {
237 match &self.repr {
238 Repr::Native(native) => native.keywords,
239 Repr::Element(elem) => elem.keywords(),
240 Repr::Closure(_) => &[],
241 Repr::Plugin(_) => &[],
242 Repr::With(with) => with.0.keywords(),
243 }
244 }
245
246 /// The function's associated scope of sub-definition.
247 pub fn scope(&self) -> Option<&'static Scope> {
248 match &self.repr {
249 Repr::Native(native) => Some(&native.0.scope),
250 Repr::Element(elem) => Some(elem.scope()),
251 Repr::Closure(_) => None,
252 Repr::Plugin(_) => None,
253 Repr::With(with) => with.0.scope(),
254 }
255 }
256
257 /// Get a field from this function's scope, if possible.
258 pub fn field(
259 &self,
260 field: &str,
261 sink: impl DeprecationSink,
262 ) -> StrResult<&'static Value> {
263 let scope =
264 self.scope().ok_or("cannot access fields on user-defined functions")?;
265 match scope.get(field) {
266 Some(binding) => Ok(binding.read_checked(sink)),
267 None => match self.name() {
268 Some(name) => bail!("function `{name}` does not contain field `{field}`"),
269 None => bail!("function does not contain field `{field}`"),
270 },
271 }
272 }
273
274 /// Extract the element function, if it is one.
275 pub fn element(&self) -> Option<Element> {
276 match self.repr {
277 Repr::Element(func) => Some(func),
278 _ => None,
279 }
280 }
281
282 /// Extract the plugin function, if it is one.
283 pub fn to_plugin(&self) -> Option<&PluginFunc> {
284 match &self.repr {
285 Repr::Plugin(func) => Some(func),
286 _ => None,
287 }
288 }
289
290 /// Call the function with the given context and arguments.
291 pub fn call<A: IntoArgs>(
292 &self,
293 engine: &mut Engine,
294 context: Tracked<Context>,
295 args: A,
296 ) -> SourceResult<Value> {
297 self.call_impl(engine, context, args.into_args(self.span))
298 }
299
300 /// Non-generic implementation of `call`.
301 #[typst_macros::time(name = "func call", span = self.span())]
302 fn call_impl(
303 &self,
304 engine: &mut Engine,
305 context: Tracked<Context>,
306 mut args: Args,
307 ) -> SourceResult<Value> {
308 match &self.repr {
309 Repr::Native(native) => {
310 let value = (native.function)(engine, context, &mut args)?;
311 args.finish()?;
312 Ok(value)
313 }
314 Repr::Element(func) => {
315 let value = func.construct(engine, &mut args)?;
316 args.finish()?;
317 Ok(Value::Content(value))
318 }
319 Repr::Closure(closure) => (engine.routines.eval_closure)(
320 self,
321 closure,
322 engine.routines,
323 engine.world,
324 engine.introspector,
325 engine.traced,
326 TrackedMut::reborrow_mut(&mut engine.sink),
327 engine.route.track(),
328 context,
329 args,
330 ),
331 Repr::Plugin(func) => {
332 let inputs = args.all::<Bytes>()?;
333 let output = func.call(inputs).at(args.span)?;
334 args.finish()?;
335 Ok(Value::Bytes(output))
336 }
337 Repr::With(with) => {
338 args.items = with.1.items.iter().cloned().chain(args.items).collect();
339 with.0.call(engine, context, args)
340 }
341 }
342 }
343
344 /// The function's span.
345 pub fn span(&self) -> Span {
346 self.span
347 }
348
349 /// Attach a span to this function if it doesn't already have one.
350 pub fn spanned(mut self, span: Span) -> Self {
351 if self.span.is_detached() {
352 self.span = span;
353 }
354 self
355 }
356}
357
358#[scope]
359impl Func {
360 /// Returns a new function that has the given arguments pre-applied.
361 #[func]
362 pub fn with(
363 self,
364 args: &mut Args,
365 /// The arguments to apply to the function.
366 #[external]
367 #[variadic]
368 arguments: Vec<Value>,
369 ) -> Func {
370 let span = self.span;
371 Self {
372 repr: Repr::With(Arc::new((self, args.take()))),
373 span,
374 }
375 }
376
377 /// Returns a selector that filters for elements belonging to this function
378 /// whose fields have the values of the given arguments.
379 ///
380 /// ```example
381 /// #show heading.where(level: 2): set text(blue)
382 /// = Section
383 /// == Subsection
384 /// === Sub-subsection
385 /// ```
386 #[func]
387 pub fn where_(
388 self,
389 args: &mut Args,
390 /// The fields to filter for.
391 #[variadic]
392 #[external]
393 fields: Vec<Value>,
394 ) -> StrResult<Selector> {
395 let fields = args.to_named();
396 args.items.retain(|arg| arg.name.is_none());
397
398 let element = self
399 .element()
400 .ok_or("`where()` can only be called on element functions")?;
401
402 let fields = fields
403 .into_iter()
404 .map(|(key, value)| {
405 element.field_id(&key).map(|id| (id, value)).ok_or_else(|| {
406 eco_format!(
407 "element `{}` does not have field `{}`",
408 element.name(),
409 key
410 )
411 })
412 })
413 .collect::<StrResult<smallvec::SmallVec<_>>>()?;
414
415 Ok(element.where_(fields))
416 }
417}
418
419impl Debug for Func {
420 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
421 write!(f, "Func({})", self.name().unwrap_or(".."))
422 }
423}
424
425impl repr::Repr for Func {
426 fn repr(&self) -> EcoString {
427 match self.name() {
428 Some(name) => name.into(),
429 None => "(..) => ..".into(),
430 }
431 }
432}
433
434impl PartialEq for Func {
435 fn eq(&self, other: &Self) -> bool {
436 self.repr == other.repr
437 }
438}
439
440impl PartialEq<&'static NativeFuncData> for Func {
441 fn eq(&self, other: &&'static NativeFuncData) -> bool {
442 match &self.repr {
443 Repr::Native(native) => *native == Static(*other),
444 _ => false,
445 }
446 }
447}
448
449impl From<Repr> for Func {
450 fn from(repr: Repr) -> Self {
451 Self { repr, span: Span::detached() }
452 }
453}
454
455impl From<&'static NativeFuncData> for Func {
456 fn from(data: &'static NativeFuncData) -> Self {
457 Repr::Native(Static(data)).into()
458 }
459}
460
461impl From<Element> for Func {
462 fn from(func: Element) -> Self {
463 Repr::Element(func).into()
464 }
465}
466
467impl From<Closure> for Func {
468 fn from(closure: Closure) -> Self {
469 Repr::Closure(Arc::new(LazyHash::new(closure))).into()
470 }
471}
472
473impl From<PluginFunc> for Func {
474 fn from(func: PluginFunc) -> Self {
475 Repr::Plugin(Arc::new(func)).into()
476 }
477}
478
479/// A Typst function that is defined by a native Rust type that shadows a
480/// native Rust function.
481pub trait NativeFunc {
482 /// Get the function for the native Rust type.
483 fn func() -> Func {
484 Func::from(Self::data())
485 }
486
487 /// Get the function data for the native Rust function.
488 fn data() -> &'static NativeFuncData;
489}
490
491/// Defines a native function.
492#[derive(Debug)]
493pub struct NativeFuncData {
494 /// Invokes the function from Typst.
495 pub function: fn(&mut Engine, Tracked<Context>, &mut Args) -> SourceResult<Value>,
496 /// The function's normal name (e.g. `align`), as exposed to Typst.
497 pub name: &'static str,
498 /// The function's title case name (e.g. `Align`).
499 pub title: &'static str,
500 /// The documentation for this function as a string.
501 pub docs: &'static str,
502 /// A list of alternate search terms for this function.
503 pub keywords: &'static [&'static str],
504 /// Whether this function makes use of context.
505 pub contextual: bool,
506 /// Definitions in the scope of the function.
507 pub scope: LazyLock<Scope>,
508 /// A list of parameter information for each parameter.
509 pub params: LazyLock<Vec<ParamInfo>>,
510 /// Information about the return value of this function.
511 pub returns: LazyLock<CastInfo>,
512}
513
514cast! {
515 &'static NativeFuncData,
516 self => Func::from(self).into_value(),
517}
518
519/// Describes a function parameter.
520#[derive(Debug, Clone)]
521pub struct ParamInfo {
522 /// The parameter's name.
523 pub name: &'static str,
524 /// Documentation for the parameter.
525 pub docs: &'static str,
526 /// Describe what values this parameter accepts.
527 pub input: CastInfo,
528 /// Creates an instance of the parameter's default value.
529 pub default: Option<fn() -> Value>,
530 /// Is the parameter positional?
531 pub positional: bool,
532 /// Is the parameter named?
533 ///
534 /// Can be true even if `positional` is true if the parameter can be given
535 /// in both variants.
536 pub named: bool,
537 /// Can the parameter be given any number of times?
538 pub variadic: bool,
539 /// Is the parameter required?
540 pub required: bool,
541 /// Is the parameter settable with a set rule?
542 pub settable: bool,
543}
544
545/// A user-defined closure.
546#[derive(Debug, Hash)]
547pub struct Closure {
548 /// The closure's syntax node. Must be either castable to `ast::Closure` or
549 /// `ast::Expr`. In the latter case, this is a synthesized closure without
550 /// any parameters (used by `context` expressions).
551 pub node: SyntaxNode,
552 /// Default values of named parameters.
553 pub defaults: Vec<Value>,
554 /// Captured values from outer scopes.
555 pub captured: Scope,
556 /// The number of positional parameters in the closure.
557 pub num_pos_params: usize,
558}
559
560impl Closure {
561 /// The name of the closure.
562 pub fn name(&self) -> Option<&str> {
563 self.node.cast::<ast::Closure>()?.name().map(|ident| ident.as_str())
564 }
565}
566
567cast! {
568 Closure,
569 self => Value::Func(self.into()),
570}