typst_library/foundations/
dict.rs1use 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#[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#[ty(scope, cast, name = "dictionary")]
79#[derive(Default, Clone, PartialEq)]
80pub struct Dict(Arc<IndexMap<Str, Value, FxBuildHasher>>);
81
82impl Dict {
83 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub fn is_empty(&self) -> bool {
90 self.0.is_empty()
91 }
92
93 pub fn get(&self, key: &str) -> StrResult<&Value> {
95 self.0.get(key).ok_or_else(|| missing_key(key))
96 }
97
98 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 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 pub fn contains(&self, key: &str) -> bool {
115 self.0.contains_key(key)
116 }
117
118 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 pub fn iter(&self) -> indexmap::map::Iter<'_, Str, Value> {
129 self.0.iter()
130 }
131
132 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 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 #[func(constructor)]
185 pub fn construct(
186 value: ToDict,
188 ) -> Dict {
189 value.0
190 }
191
192 #[func(title = "Length")]
194 pub fn len(&self) -> usize {
195 self.0.len()
196 }
197
198 #[func]
208 pub fn at(
209 &self,
210 key: Str,
212 #[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 #[func]
229 pub fn insert(
230 &mut self,
231 key: Str,
233 value: Value,
235 ) {
236 Arc::make_mut(&mut self.0).insert(key, value);
237 }
238
239 #[func]
241 pub fn remove(
242 &mut self,
243 key: Str,
245 #[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 #[func]
257 pub fn keys(&self) -> Array {
258 self.0.keys().cloned().map(Value::Str).collect()
259 }
260
261 #[func]
263 pub fn values(&self) -> Array {
264 self.0.values().cloned().collect()
265 }
266
267 #[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 #[func]
302 pub fn filter(
303 self,
304 engine: &mut Engine,
305 context: Tracked<Context>,
306 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 #[func]
326 pub fn map(
327 self,
328 engine: &mut Engine,
329 context: Tracked<Context>,
330 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
342pub 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#[cold]
471fn missing_key(key: &str) -> EcoString {
472 eco_format!("dictionary does not contain key {}", key.repr())
473}
474
475#[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}