Skip to main content

typst_library/foundations/
dict.rs

1use std::fmt::{Debug, Formatter};
2use std::hash::{Hash, Hasher};
3use std::ops::{Add, AddAssign};
4use std::sync::Arc;
5
6use comemo::Tracked;
7use ecow::{EcoString, eco_format};
8use indexmap::IndexMap;
9use rustc_hash::FxBuildHasher;
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use typst_syntax::is_ident;
12
13use crate::diag::{At, Hint, HintedStrResult, SourceResult, StrResult};
14use crate::engine::Engine;
15use crate::foundations::{
16    Array, Context, Func, Module, Repr, Str, Value, array, cast, func, repr, scope, ty,
17};
18
19/// Create a new [`Dict`] from key-value pairs.
20#[macro_export]
21#[doc(hidden)]
22macro_rules! __dict {
23    ($($key:expr => $value:expr),* $(,)?) => {{
24        #[allow(unused_mut)]
25        let mut map = $crate::foundations::IndexMap::default();
26        $(map.insert($key.into(), $crate::foundations::IntoValue::into_value($value));)*
27        $crate::foundations::Dict::from(map)
28    }};
29}
30
31#[doc(inline)]
32pub use crate::__dict as dict;
33
34/// A map from string keys to values.
35///
36/// You can construct a dictionary by enclosing comma-separated `key: value`
37/// pairs in parentheses. The values do not have to be of the same type. Since
38/// empty parentheses already yield an empty array, you have to use the special
39/// `(:)` syntax to create an empty dictionary.
40///
41/// A dictionary is conceptually similar to an @array[array], but it is indexed
42/// by strings instead of integers. You can access and create dictionary entries
43/// with the `.at()` method. If you know the key statically, you can
44/// alternatively use @reference:scripting:fields[field access notation]
45/// (`.key`) to access the value. To check whether a key is present in the
46/// dictionary, use the `in` keyword.
47///
48/// You can iterate over the pairs in a dictionary using a
49/// @reference:scripting:loops[for loop]. This will iterate in the order the
50/// pairs were inserted / declared initially.
51///
52/// Dictionaries can be added with the `+` operator and
53/// @reference:scripting:blocks[joined together]. They can also be
54/// @arguments:spreading[spread] into a function call or another dictionary
55/// #footnote[When spreading into a dictionary, if all items between the
56/// parentheses are spread, you have to begin the container with `(:`, as in
57/// `(: ..dict, ..other_dict)`. Otherwise the container is inferred to be an
58/// array and an error is raised.]
59/// with the `..spread` operator. In each case, if a key appears multiple times,
60/// the last value will override the others.
61///
62/// = Example <example>
63/// ```example
64/// #let dict = (
65///   name: "Typst",
66///   born: 2019,
67/// )
68///
69/// #dict.name \
70/// #(dict.launch = 20)
71/// #dict.len() \
72/// #dict.keys() \
73/// #dict.values() \
74/// #dict.at("born") \
75/// #dict.insert("city", "Berlin")
76/// #("name" in dict)
77/// ```
78#[ty(scope, cast, name = "dictionary")]
79#[derive(Default, Clone, PartialEq)]
80pub struct Dict(Arc<IndexMap<Str, Value, FxBuildHasher>>);
81
82impl Dict {
83    /// Create a new, empty dictionary.
84    pub fn new() -> Self {
85        Self::default()
86    }
87
88    /// Whether the dictionary is empty.
89    pub fn is_empty(&self) -> bool {
90        self.0.is_empty()
91    }
92
93    /// Borrow the value at the given key.
94    pub fn get(&self, key: &str) -> StrResult<&Value> {
95        self.0.get(key).ok_or_else(|| missing_key(key))
96    }
97
98    /// Mutably borrow the value the given `key` maps to.
99    pub fn at_mut(&mut self, key: &str) -> HintedStrResult<&mut Value> {
100        Arc::make_mut(&mut self.0)
101            .get_mut(key)
102            .ok_or_else(|| missing_key(key))
103            .hint("use `insert` to add or update values")
104    }
105
106    /// Remove the value if the dictionary contains the given key.
107    pub fn take(&mut self, key: &str) -> StrResult<Value> {
108        Arc::make_mut(&mut self.0)
109            .shift_remove(key)
110            .ok_or_else(|| missing_key(key))
111    }
112
113    /// Whether the dictionary contains a specific key.
114    pub fn contains(&self, key: &str) -> bool {
115        self.0.contains_key(key)
116    }
117
118    /// Clear the dictionary.
119    pub fn clear(&mut self) {
120        if Arc::strong_count(&self.0) == 1 {
121            Arc::make_mut(&mut self.0).clear();
122        } else {
123            *self = Self::new();
124        }
125    }
126
127    /// Iterate over pairs of references to the contained keys and values.
128    pub fn iter(&self) -> indexmap::map::Iter<'_, Str, Value> {
129        self.0.iter()
130    }
131
132    /// Check if there is any remaining pair, and if so return an
133    /// "unexpected key" error.
134    pub fn finish(&self, expected: &[&str]) -> StrResult<()> {
135        let mut iter = self.iter().peekable();
136        if iter.peek().is_none() {
137            return Ok(());
138        }
139        let unexpected: Vec<&str> = iter.map(|kv| kv.0.as_str()).collect();
140
141        Err(Self::unexpected_keys(unexpected, Some(expected)))
142    }
143
144    // Return an "unexpected key" error string.
145    pub fn unexpected_keys(
146        unexpected: Vec<&str>,
147        hint_expected: Option<&[&str]>,
148    ) -> EcoString {
149        let format_as_list = |arr: &[&str]| {
150            repr::separated_list(
151                &arr.iter().map(|s| eco_format!("\"{s}\"")).collect::<Vec<_>>(),
152                "and",
153            )
154        };
155
156        let mut msg = String::from(match unexpected.len() {
157            1 => "unexpected key ",
158            _ => "unexpected keys ",
159        });
160
161        msg.push_str(&format_as_list(&unexpected[..]));
162
163        if let Some(expected) = hint_expected {
164            msg.push_str(", valid keys are ");
165            msg.push_str(&format_as_list(expected));
166        }
167
168        msg.into()
169    }
170}
171
172#[scope]
173impl Dict {
174    /// Converts a value into a dictionary.
175    ///
176    /// Note that this function is only intended for conversion of a
177    /// dictionary-like value to a dictionary, not for creation of a dictionary
178    /// from individual pairs. Use the dictionary syntax `(key: value)` instead.
179    /// Also see @array.to-dict for converting arrays to dictionaries.
180    ///
181    /// ```example
182    /// #dictionary(sys).at("version")
183    /// ```
184    #[func(constructor)]
185    pub fn construct(
186        /// The value that should be converted to a dictionary.
187        value: ToDict,
188    ) -> Dict {
189        value.0
190    }
191
192    /// The number of pairs in the dictionary.
193    #[func(title = "Length")]
194    pub fn len(&self) -> usize {
195        self.0.len()
196    }
197
198    /// Returns the value associated with the specified key in the dictionary.
199    ///
200    /// May be used on the left-hand side of an assignment if the key is already
201    /// present in the dictionary. Returns the default value if the key is not
202    /// part of the dictionary or fails with an error if no default value was
203    /// specified.
204    ///
205    /// Values may also be accessed with field syntax (e.g. `{(key: 42).key}`)
206    /// if no default is needed.
207    #[func]
208    pub fn at(
209        &self,
210        /// The key at which to retrieve the item.
211        key: Str,
212        /// A default value to return if the key is not part of the dictionary.
213        #[named]
214        default: Option<Value>,
215    ) -> StrResult<Value> {
216        self.0
217            .get(&key)
218            .cloned()
219            .or(default)
220            .ok_or_else(|| missing_key_no_default(&key))
221    }
222
223    /// Inserts a new pair into the dictionary. If the dictionary already
224    /// contains this key, the value is updated.
225    ///
226    /// To insert multiple pairs at once, you can alternatively add another
227    /// dictionary with the `+=` operator.
228    #[func]
229    pub fn insert(
230        &mut self,
231        /// The key of the pair that should be inserted.
232        key: Str,
233        /// The value of the pair that should be inserted.
234        value: Value,
235    ) {
236        Arc::make_mut(&mut self.0).insert(key, value);
237    }
238
239    /// Removes a pair from the dictionary by key and return the value.
240    #[func]
241    pub fn remove(
242        &mut self,
243        /// The key of the pair to remove.
244        key: Str,
245        /// A default value to return if the key does not exist.
246        #[named]
247        default: Option<Value>,
248    ) -> StrResult<Value> {
249        Arc::make_mut(&mut self.0)
250            .shift_remove(&key)
251            .or(default)
252            .ok_or_else(|| missing_key(&key))
253    }
254
255    /// Returns the keys of the dictionary as an array in insertion order.
256    #[func]
257    pub fn keys(&self) -> Array {
258        self.0.keys().cloned().map(Value::Str).collect()
259    }
260
261    /// Returns the values of the dictionary as an array in insertion order.
262    #[func]
263    pub fn values(&self) -> Array {
264        self.0.values().cloned().collect()
265    }
266
267    /// Returns the keys and values of the dictionary as an array of pairs. Each
268    /// pair is represented as an array of length two.
269    #[func]
270    pub fn pairs(&self) -> Array {
271        self.0
272            .iter()
273            .map(|(k, v)| Value::Array(array![k.clone(), v.clone()]))
274            .collect()
275    }
276
277    /// Produces a new dictionary with only the pairs from the original one for
278    /// which the given function returns `{true}`.
279    ///
280    /// #example(
281    ///   title: "Basic usage",
282    ///   ```
283    ///   #{
284    ///     (a: 0, b: 1, c: 2)
285    ///       .filter(v => v > 0)
286    ///   }
287    ///   ```
288    /// )
289    ///
290    /// #example(
291    ///   title: "Filtering based on the key instead of the value",
292    ///   ```
293    ///   #{
294    ///     (a: 0, b: 1, c: 2)
295    ///       .pairs()
296    ///       .filter(((k, v)) => k != "a")
297    ///       .to-dict()
298    ///   }
299    ///   ```
300    /// )
301    #[func]
302    pub fn filter(
303        self,
304        engine: &mut Engine,
305        context: Tracked<Context>,
306        /// The function to apply to each value. Must return a boolean.
307        test: Func,
308    ) -> SourceResult<Dict> {
309        let mut run_test = |v: &Value| {
310            test.call(engine, context, [v.clone()])?
311                .cast::<bool>()
312                .at(test.span())
313        };
314        self.into_iter()
315            .filter_map(|(k, v)| run_test(&v).map(|b| b.then_some((k, v))).transpose())
316            .collect()
317    }
318
319    /// Produces a new dictionary where the keys are the same, but the values
320    /// are transformed with the given function.
321    ///
322    /// ```example
323    /// #(a: 0, b: 1, c: 2).map(v => v + 1)
324    /// ```
325    #[func]
326    pub fn map(
327        self,
328        engine: &mut Engine,
329        context: Tracked<Context>,
330        /// The function to apply to each value.
331        mapper: Func,
332    ) -> SourceResult<Dict> {
333        self.into_iter()
334            .map(|(k, v)| {
335                let mapped_value = mapper.call(engine, context, [v])?;
336                Ok((k, mapped_value))
337            })
338            .collect()
339    }
340}
341
342/// A value that can be cast to dictionary.
343pub struct ToDict(Dict);
344
345cast! {
346    ToDict,
347    v: Module => Self(v
348        .scope()
349        .iter()
350        .map(|(k, b)| (Str::from(k.clone()), b.read().clone()))
351        .collect()
352    ),
353}
354
355impl Debug for Dict {
356    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
357        f.debug_map().entries(self.0.iter()).finish()
358    }
359}
360
361impl Repr for Dict {
362    fn repr(&self) -> EcoString {
363        if self.is_empty() {
364            return "(:)".into();
365        }
366
367        let max = 40;
368        let mut pieces: Vec<_> = self
369            .iter()
370            .take(max)
371            .map(|(key, value)| {
372                if is_ident(key) {
373                    eco_format!("{key}: {}", value.repr())
374                } else {
375                    eco_format!("{}: {}", key.repr(), value.repr())
376                }
377            })
378            .collect();
379
380        if self.len() > max {
381            pieces.push(eco_format!(".. ({} pairs omitted)", self.len() - max));
382        }
383
384        repr::pretty_array_like(&pieces, false).into()
385    }
386}
387
388impl Add for Dict {
389    type Output = Self;
390
391    fn add(mut self, rhs: Dict) -> Self::Output {
392        self += rhs;
393        self
394    }
395}
396
397impl AddAssign for Dict {
398    fn add_assign(&mut self, rhs: Dict) {
399        match Arc::try_unwrap(rhs.0) {
400            Ok(map) => self.extend(map),
401            Err(rc) => self.extend(rc.iter().map(|(k, v)| (k.clone(), v.clone()))),
402        }
403    }
404}
405
406impl Hash for Dict {
407    fn hash<H: Hasher>(&self, state: &mut H) {
408        state.write_usize(self.0.len());
409        for item in self {
410            item.hash(state);
411        }
412    }
413}
414
415impl Serialize for Dict {
416    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
417    where
418        S: Serializer,
419    {
420        self.0.serialize(serializer)
421    }
422}
423
424impl<'de> Deserialize<'de> for Dict {
425    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
426    where
427        D: Deserializer<'de>,
428    {
429        Ok(IndexMap::<Str, Value, FxBuildHasher>::deserialize(deserializer)?.into())
430    }
431}
432
433impl Extend<(Str, Value)> for Dict {
434    fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) {
435        Arc::make_mut(&mut self.0).extend(iter);
436    }
437}
438
439impl FromIterator<(Str, Value)> for Dict {
440    fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self {
441        Self(Arc::new(iter.into_iter().collect()))
442    }
443}
444
445impl IntoIterator for Dict {
446    type Item = (Str, Value);
447    type IntoIter = indexmap::map::IntoIter<Str, Value>;
448
449    fn into_iter(self) -> Self::IntoIter {
450        Arc::unwrap_or_clone(self.0).into_iter()
451    }
452}
453
454impl<'a> IntoIterator for &'a Dict {
455    type Item = (&'a Str, &'a Value);
456    type IntoIter = indexmap::map::Iter<'a, Str, Value>;
457
458    fn into_iter(self) -> Self::IntoIter {
459        self.iter()
460    }
461}
462
463impl From<IndexMap<Str, Value, FxBuildHasher>> for Dict {
464    fn from(map: IndexMap<Str, Value, FxBuildHasher>) -> Self {
465        Self(Arc::new(map))
466    }
467}
468
469/// The missing key access error message.
470#[cold]
471fn missing_key(key: &str) -> EcoString {
472    eco_format!("dictionary does not contain key {}", key.repr())
473}
474
475/// The missing key access error message when no default was given.
476#[cold]
477fn missing_key_no_default(key: &str) -> EcoString {
478    eco_format!(
479        "dictionary does not contain key {} \
480         and no default value was specified",
481        key.repr()
482    )
483}