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 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#[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#[ty(scope, cast, name = "dictionary")]
77#[derive(Default, Clone, PartialEq)]
78pub struct Dict(Arc<IndexMap<Str, Value, FxBuildHasher>>);
79
80impl Dict {
81 pub fn new() -> Self {
83 Self::default()
84 }
85
86 pub fn is_empty(&self) -> bool {
88 self.0.is_empty()
89 }
90
91 pub fn get(&self, key: &str) -> StrResult<&Value> {
93 self.0.get(key).ok_or_else(|| missing_key(key))
94 }
95
96 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 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 pub fn contains(&self, key: &str) -> bool {
113 self.0.contains_key(key)
114 }
115
116 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 pub fn iter(&self) -> indexmap::map::Iter<'_, Str, Value> {
127 self.0.iter()
128 }
129
130 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 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 #[func(constructor)]
182 pub fn construct(
183 value: ToDict,
185 ) -> Dict {
186 value.0
187 }
188
189 #[func(title = "Length")]
191 pub fn len(&self) -> usize {
192 self.0.len()
193 }
194
195 #[func]
201 pub fn at(
202 &self,
203 key: Str,
205 #[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 #[func]
222 pub fn insert(
223 &mut self,
224 key: Str,
226 value: Value,
228 ) {
229 Arc::make_mut(&mut self.0).insert(key, value);
230 }
231
232 #[func]
234 pub fn remove(
235 &mut self,
236 key: Str,
238 #[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 #[func]
250 pub fn keys(&self) -> Array {
251 self.0.keys().cloned().map(Value::Str).collect()
252 }
253
254 #[func]
256 pub fn values(&self) -> Array {
257 self.0.values().cloned().collect()
258 }
259
260 #[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
271pub 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#[cold]
400fn missing_key(key: &str) -> EcoString {
401 eco_format!("dictionary does not contain key {}", key.repr())
402}
403
404#[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}