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