Skip to main content

typst_library/foundations/
args.rs

1use std::fmt::{self, Debug, Formatter};
2use std::ops::Add;
3use std::slice;
4
5use comemo::Tracked;
6use ecow::{EcoString, EcoVec, eco_format, eco_vec};
7use typst_syntax::{Span, Spanned};
8
9use crate::diag::{At, SourceDiagnostic, SourceResult, StrResult, bail, error};
10use crate::engine::Engine;
11use crate::foundations::{
12    Array, Context, Dict, FromValue, Func, IntoValue, Repr, Str, Value, cast, func, repr,
13    scope, ty,
14};
15
16/// Captured arguments to a function.
17///
18/// Arguments are either _positional_ or _named,_ and can be accessed through
19/// the @arguments.pos[`pos`], @arguments.named[`named`], and
20/// @arguments.at[`at`] methods.
21///
22/// Additionally, named arguments can be accessed with @arguments.at[field
23/// syntax] similar to @dictionary[dictionaries].
24///
25/// = Argument Sinks <argument-sinks>
26/// Like built-in functions, custom functions can also take a variable number of
27/// arguments. You can specify an _argument sink_ which collects all excess
28/// arguments as `..sink`. The resulting `sink` value is of the `arguments`
29/// type. It exposes methods to access the positional and named arguments.
30///
31/// ```example
32/// #let format(title, ..authors) = {
33///   let by = authors
34///     .pos()
35///     .join(", ", last: " and ")
36///
37///   [*#title* \ _Written by #by;_]
38/// }
39///
40/// #format("ArtosFlow", "Jane", "Joe")
41/// ```
42///
43/// = Spreading <spreading>
44/// Inversely to an argument sink, you can _spread_ arguments, arrays and
45/// dictionaries into a function call with the `..spread` operator:
46///
47/// ```example
48/// #let array = (2, 3, 5)
49/// #calc.min(..array)
50/// #let dict = (fill: blue)
51/// #text(..dict)[Hello]
52/// ```
53#[ty(scope, cast, name = "arguments")]
54#[derive(Clone, Hash)]
55pub struct Args {
56    /// The callsite span for the function. This is not the span of the argument
57    /// list itself, but of the whole function call.
58    pub span: Span,
59    /// The positional and named arguments.
60    pub items: EcoVec<Arg>,
61}
62
63impl Args {
64    /// Create positional arguments from a span and values.
65    pub fn new<T: IntoValue>(span: Span, values: impl IntoIterator<Item = T>) -> Self {
66        let items = values
67            .into_iter()
68            .map(|value| Arg {
69                span,
70                name: None,
71                value: Spanned::new(value.into_value(), span),
72            })
73            .collect();
74        Self { span, items }
75    }
76
77    /// Attach a span to these arguments if they don't already have one.
78    pub fn spanned(mut self, span: Span) -> Self {
79        if self.span.is_detached() {
80            self.span = span;
81        }
82        self
83    }
84
85    /// Returns the number of remaining positional arguments.
86    pub fn remaining(&self) -> usize {
87        self.items.iter().filter(|slot| slot.name.is_none()).count()
88    }
89
90    /// Insert a positional argument at a specific index.
91    pub fn insert(&mut self, index: usize, span: Span, value: Value) {
92        self.items.insert(
93            index,
94            Arg {
95                span: self.span,
96                name: None,
97                value: Spanned::new(value, span),
98            },
99        )
100    }
101
102    /// Push a positional argument.
103    pub fn push(&mut self, span: Span, value: Value) {
104        self.items.push(Arg {
105            span: self.span,
106            name: None,
107            value: Spanned::new(value, span),
108        })
109    }
110
111    /// Consume and cast the first positional argument if there is one.
112    pub fn eat<T>(&mut self) -> SourceResult<Option<T>>
113    where
114        T: FromValue<Spanned<Value>>,
115    {
116        for (i, slot) in self.items.iter().enumerate() {
117            if slot.name.is_none() {
118                let value = self.items.remove(i).value;
119                let span = value.span;
120                return T::from_value(value).at(span).map(Some);
121            }
122        }
123        Ok(None)
124    }
125
126    /// Consume n positional arguments if possible.
127    pub fn consume(&mut self, n: usize) -> SourceResult<Vec<Arg>> {
128        let mut list = vec![];
129
130        let mut i = 0;
131        while i < self.items.len() && list.len() < n {
132            if self.items[i].name.is_none() {
133                list.push(self.items.remove(i));
134            } else {
135                i += 1;
136            }
137        }
138
139        if list.len() < n {
140            bail!(self.span, "not enough arguments");
141        }
142
143        Ok(list)
144    }
145
146    /// Consume and cast the first positional argument.
147    ///
148    /// Returns a `missing argument: {what}` error if no positional argument is
149    /// left.
150    pub fn expect<T>(&mut self, what: &str) -> SourceResult<T>
151    where
152        T: FromValue<Spanned<Value>>,
153    {
154        match self.eat()? {
155            Some(v) => Ok(v),
156            None => bail!(self.missing_argument(what)),
157        }
158    }
159
160    /// The error message for missing arguments.
161    fn missing_argument(&self, what: &str) -> SourceDiagnostic {
162        for item in &self.items {
163            let Some(name) = item.name.as_deref() else { continue };
164            if name == what {
165                return error!(
166                    item.span,
167                    "the argument `{what}` is positional";
168                    hint: "try removing `{name}:`";
169                );
170            }
171        }
172
173        error!(self.span, "missing argument: {what}")
174    }
175
176    /// Find and consume the first castable positional argument.
177    pub fn find<T>(&mut self) -> SourceResult<Option<T>>
178    where
179        T: FromValue<Spanned<Value>>,
180    {
181        for (i, slot) in self.items.iter().enumerate() {
182            if slot.name.is_none() && T::castable(&slot.value.v) {
183                let value = self.items.remove(i).value;
184                let span = value.span;
185                return T::from_value(value).at(span).map(Some);
186            }
187        }
188        Ok(None)
189    }
190
191    /// Find and consume all castable positional arguments.
192    pub fn all<T>(&mut self) -> SourceResult<Vec<T>>
193    where
194        T: FromValue<Spanned<Value>>,
195    {
196        let mut list = vec![];
197        let mut errors = eco_vec![];
198        self.items.retain(|item| {
199            if item.name.is_some() {
200                return true;
201            }
202            let span = item.value.span;
203            let spanned = Spanned::new(std::mem::take(&mut item.value.v), span);
204            match T::from_value(spanned).at(span) {
205                Ok(val) => list.push(val),
206                Err(diags) => errors.extend(diags),
207            }
208            false
209        });
210        if !errors.is_empty() {
211            return Err(errors);
212        }
213        Ok(list)
214    }
215
216    /// Cast and remove the value for the given named argument, returning an
217    /// error if the conversion fails.
218    pub fn named<T>(&mut self, name: &str) -> SourceResult<Option<T>>
219    where
220        T: FromValue<Spanned<Value>>,
221    {
222        // We don't quit once we have a match because when multiple matches
223        // exist, we want to remove all of them and use the last one.
224        let mut i = 0;
225        let mut found = None;
226        while i < self.items.len() {
227            if self.items[i].name.as_deref() == Some(name) {
228                let value = self.items.remove(i).value;
229                let span = value.span;
230                found = Some(T::from_value(value).at(span)?);
231            } else {
232                i += 1;
233            }
234        }
235        Ok(found)
236    }
237
238    /// Same as named, but with fallback to find.
239    pub fn named_or_find<T>(&mut self, name: &str) -> SourceResult<Option<T>>
240    where
241        T: FromValue<Spanned<Value>>,
242    {
243        match self.named(name)? {
244            Some(value) => Ok(Some(value)),
245            None => self.find(),
246        }
247    }
248
249    /// Take out all arguments into a new instance.
250    pub fn take(&mut self) -> Self {
251        Self {
252            span: self.span,
253            items: std::mem::take(&mut self.items),
254        }
255    }
256
257    /// Return an "unexpected argument" error if there is any remaining
258    /// argument.
259    pub fn finish(self) -> SourceResult<()> {
260        if let Some(arg) = self.items.first() {
261            match &arg.name {
262                Some(name) => bail!(arg.span, "unexpected argument: {name}"),
263                _ => bail!(arg.span, "unexpected argument"),
264            }
265        }
266        Ok(())
267    }
268}
269
270/// A key that can be used to get an argument: either the index of a positional
271/// argument, or the name of a named argument.
272#[derive(Debug, Clone, Eq, PartialEq)]
273pub enum ArgumentKey {
274    Index(i64),
275    Name(Str),
276}
277
278cast! {
279    ArgumentKey,
280    v: i64 => Self::Index(v),
281    v: Str => Self::Name(v),
282}
283
284impl Args {
285    /// Tests whether there is no positional nor named argument.
286    pub fn is_empty(&self) -> bool {
287        self.items.is_empty()
288    }
289
290    fn get(&self, key: &ArgumentKey) -> Option<&Value> {
291        let item = match key {
292            &ArgumentKey::Index(index) => {
293                let mut iter = self.items.iter().filter(|item| item.name.is_none());
294                if index < 0 {
295                    let index = (-(index + 1)).try_into().ok()?;
296                    iter.nth_back(index)
297                } else {
298                    let index = index.try_into().ok()?;
299                    iter.nth(index)
300                }
301            }
302            // Accept the last argument with the right name.
303            ArgumentKey::Name(name) => {
304                self.items.iter().rfind(|item| item.name.as_ref() == Some(name))
305            }
306        };
307        item.map(|item| &item.value.v)
308    }
309
310    /// Access a named argument as a field.
311    pub fn field(&self, field: &str) -> StrResult<&Value> {
312        self.items
313            .iter()
314            .rfind(|item| item.name.as_ref().map(|name| name.as_str()) == Some(field))
315            .ok_or_else(|| eco_format!("no named argument {}", field.repr()))
316            .map(|item| &item.value.v)
317    }
318}
319
320#[scope]
321impl Args {
322    /// Construct spreadable arguments in place.
323    ///
324    /// This function behaves like `{let args(..sink) = sink}`.
325    ///
326    /// ```example
327    /// #let args = arguments(stroke: red, inset: 1em, [Body])
328    /// #box(..args)
329    /// ```
330    #[func(constructor)]
331    pub fn construct(
332        args: &mut Args,
333        /// The arguments to construct.
334        #[external]
335        #[variadic]
336        arguments: Vec<Value>,
337    ) -> Args {
338        args.take()
339    }
340
341    /// The number of arguments, positional or named.
342    #[func(title = "Length")]
343    pub fn len(&self) -> usize {
344        self.items.len()
345    }
346
347    /// Returns the positional argument at the specified index, or the named
348    /// argument with the specified name.
349    ///
350    /// If the key is an @int[integer], this is equivalent to first calling
351    /// @arguments.pos[`pos`] and then @array.at. If it is a @str[string], this
352    /// is equivalent to first calling @arguments.named[`named`] and then
353    /// @dictionary.at.
354    ///
355    /// Named arguments can also be accessed with field syntax (e.g.
356    /// `{arguments(key: 42).key}`) if no default is needed. Unlike
357    /// @dictionary[dictionaries], fields on arguments cannot be modified.
358    #[func]
359    pub fn at(
360        &self,
361        /// The index or name of the argument to get.
362        key: ArgumentKey,
363        /// A default value to return if the key is invalid.
364        #[named]
365        default: Option<Value>,
366    ) -> StrResult<Value> {
367        self.get(&key)
368            .cloned()
369            .or(default)
370            .ok_or_else(|| missing_key_no_default(key))
371    }
372
373    /// Returns the captured positional arguments as an array.
374    #[func(name = "pos", title = "Positional")]
375    pub fn to_pos(&self) -> Array {
376        self.items
377            .iter()
378            .filter(|item| item.name.is_none())
379            .map(|item| item.value.v.clone())
380            .collect()
381    }
382
383    /// Returns the captured named arguments as a dictionary.
384    #[func(name = "named")]
385    pub fn to_named(&self) -> Dict {
386        self.items
387            .iter()
388            .filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone())))
389            .collect()
390    }
391
392    /// Produces a new `arguments` with only the arguments for which the value
393    /// passes the test.
394    ///
395    /// ```example
396    /// #{
397    ///   arguments(-1, a: 0, b: 1, 2)
398    ///     .filter(v => v > 0)
399    /// }
400    /// ```
401    #[func]
402    pub fn filter(
403        self,
404        engine: &mut Engine,
405        context: Tracked<Context>,
406        /// The function to apply to each value. Must return a boolean.
407        test: Func,
408    ) -> SourceResult<Args> {
409        let mut run_test = |v: &Value| {
410            test.call(engine, context, [v.clone()])?
411                .cast::<bool>()
412                .at(test.span())
413        };
414        self.into_iter()
415            .filter_map(|arg| {
416                run_test(&arg.value.v).map(|b| b.then_some(arg)).transpose()
417            })
418            .collect()
419    }
420
421    /// Produces a new `arguments` by transforming each argument value with the
422    /// passed function.
423    ///
424    /// ```example
425    /// #{
426    ///   arguments(0, a: 1, 2)
427    ///     .map(v => v + 1)
428    /// }
429    /// ```
430    #[func]
431    pub fn map(
432        self,
433        engine: &mut Engine,
434        context: Tracked<Context>,
435        /// The function to apply to each value.
436        mapper: Func,
437    ) -> SourceResult<Args> {
438        self.into_iter()
439            .map(|arg| {
440                let mapped_value = mapper.call(engine, context, [arg.value.v])?;
441                Ok(Arg {
442                    span: arg.span,
443                    name: arg.name,
444                    value: Spanned::detached(mapped_value),
445                })
446            })
447            .collect()
448    }
449}
450
451impl Debug for Args {
452    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
453        f.debug_list().entries(&self.items).finish()
454    }
455}
456
457impl Repr for Args {
458    fn repr(&self) -> EcoString {
459        let pieces = self.items.iter().map(Arg::repr).collect::<Vec<_>>();
460        eco_format!("arguments{}", repr::pretty_array_like(&pieces, false))
461    }
462}
463
464impl PartialEq for Args {
465    fn eq(&self, other: &Self) -> bool {
466        self.to_pos() == other.to_pos() && self.to_named() == other.to_named()
467    }
468}
469
470impl Add for Args {
471    type Output = Self;
472
473    fn add(mut self, rhs: Self) -> Self::Output {
474        self.items.retain(|item| {
475            !item.name.as_ref().is_some_and(|name| {
476                rhs.items.iter().any(|a| a.name.as_ref() == Some(name))
477            })
478        });
479        self.items.extend(rhs.items);
480        self.span = Span::detached();
481        self
482    }
483}
484
485impl FromIterator<Arg> for Args {
486    fn from_iter<T: IntoIterator<Item = Arg>>(iter: T) -> Self {
487        Self {
488            span: Span::detached(),
489            items: iter.into_iter().collect(),
490        }
491    }
492}
493
494impl IntoIterator for Args {
495    type Item = Arg;
496    type IntoIter = <EcoVec<Arg> as IntoIterator>::IntoIter;
497
498    fn into_iter(self) -> Self::IntoIter {
499        self.items.into_iter()
500    }
501}
502
503impl<'a> IntoIterator for &'a Args {
504    type Item = &'a Arg;
505    type IntoIter = slice::Iter<'a, Arg>;
506
507    fn into_iter(self) -> Self::IntoIter {
508        self.items.iter()
509    }
510}
511
512/// An argument to a function call: `12` or `draw: false`.
513#[derive(Clone, Hash)]
514pub struct Arg {
515    /// The span of the whole argument.
516    pub span: Span,
517    /// The name of the argument (`None` for positional arguments).
518    pub name: Option<Str>,
519    /// The value of the argument.
520    pub value: Spanned<Value>,
521}
522
523impl Debug for Arg {
524    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
525        if let Some(name) = &self.name {
526            name.fmt(f)?;
527            f.write_str(": ")?;
528            self.value.v.fmt(f)
529        } else {
530            self.value.v.fmt(f)
531        }
532    }
533}
534
535impl Repr for Arg {
536    fn repr(&self) -> EcoString {
537        if let Some(name) = &self.name {
538            eco_format!("{}: {}", name, self.value.v.repr())
539        } else {
540            self.value.v.repr()
541        }
542    }
543}
544
545impl PartialEq for Arg {
546    fn eq(&self, other: &Self) -> bool {
547        self.name == other.name && self.value.v == other.value.v
548    }
549}
550
551/// Things that can be used as arguments.
552pub trait IntoArgs {
553    /// Convert into arguments, attaching the `fallback` span in case `Self`
554    /// doesn't have a span.
555    fn into_args(self, fallback: Span) -> Args;
556}
557
558impl IntoArgs for Args {
559    fn into_args(self, fallback: Span) -> Args {
560        self.spanned(fallback)
561    }
562}
563
564impl<I, T> IntoArgs for I
565where
566    I: IntoIterator<Item = T>,
567    T: IntoValue,
568{
569    fn into_args(self, fallback: Span) -> Args {
570        Args::new(fallback, self)
571    }
572}
573
574/// The missing key access error message when no default was given.
575#[cold]
576fn missing_key_no_default(key: ArgumentKey) -> EcoString {
577    match key {
578        ArgumentKey::Index(i) => eco_format!(
579            "no positional argument at index {i} and no default value was specified",
580        ),
581        ArgumentKey::Name(name) => eco_format!(
582            "no named argument {} and no default value was specified",
583            name.repr()
584        ),
585    }
586}