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