typst_library/foundations/
args.rs

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