diff --git a/crates/typst/src/foundations/array.rs b/crates/typst/src/foundations/array.rs index 4c05e3751..7d76cf5bb 100644 --- a/crates/typst/src/foundations/array.rs +++ b/crates/typst/src/foundations/array.rs @@ -8,12 +8,12 @@ use ecow::{eco_format, EcoString, EcoVec}; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -use crate::diag::{At, SourceResult, StrResult}; +use crate::diag::{bail, At, SourceResult, StrResult}; use crate::engine::Engine; use crate::eval::ops; use crate::foundations::{ - cast, func, repr, scope, ty, Args, Bytes, CastInfo, Context, FromValue, Func, - IntoValue, Reflect, Repr, Value, Version, + cast, func, repr, scope, ty, Args, Bytes, CastInfo, Context, Dict, FromValue, Func, + IntoValue, Reflect, Repr, Str, Value, Version, }; use crate::syntax::Span; @@ -854,6 +854,34 @@ impl Array { Ok(Self(out)) } + + /// Converts an array of pairs into a dictionary. + /// The first value of each pair is the key, the second the value. + /// + /// If the same key occurs multiple times, the last value is selected. + /// + /// ```example + /// (("apples", 2), ("peaches", 3), ("apples", 5)).to-dict() + /// ``` + #[func] + pub fn to_dict(self) -> StrResult { + self.into_iter() + .map(|value| { + let value_ty = value.ty(); + let pair = value.cast::().map_err(|_| { + eco_format!("expected (str, any) pairs, found {}", value_ty) + })?; + if let [key, value] = pair.as_slice() { + let key = key.clone().cast::().map_err(|_| { + eco_format!("expected key of type str, found {}", value.ty()) + })?; + Ok((key, value.clone())) + } else { + bail!("expected pairs of length 2, found length {}", pair.len()); + } + }) + .collect() + } } /// A value that can be cast to bytes. diff --git a/tests/typ/compiler/array.typ b/tests/typ/compiler/array.typ index 4a1948ff7..597f242c6 100644 --- a/tests/typ/compiler/array.typ +++ b/tests/typ/compiler/array.typ @@ -307,12 +307,34 @@ #test(("Jane", "John", "Eric", "John").dedup(), ("Jane", "John", "Eric")) --- -// Test the `dedup` with the `key` argument. +// Test the `dedup` method with the `key` argument. #test((1, 2, 3, 4, 5, 6).dedup(key: x => calc.rem(x, 2)), (1, 2)) #test((1, 2, 3, 4, 5, 6).dedup(key: x => calc.rem(x, 3)), (1, 2, 3)) #test(("Hello", "World", "Hi", "There").dedup(key: x => x.len()), ("Hello", "Hi")) #test(("Hello", "World", "Hi", "There").dedup(key: x => x.at(0)), ("Hello", "World", "There")) +--- +// Test the `to-dict` method. +#test(().to-dict(), (:)) +#test((("a", 1), ("b", 2), ("c", 3)).to-dict(), (a: 1, b: 2, c: 3)) +#test((("a", 1), ("b", 2), ("c", 3), ("b", 4)).to-dict(), (a: 1, b: 4, c: 3)) + +--- +// Error: 2-16 expected (str, any) pairs, found integer +#(1,).to-dict() + +--- +// Error: 2-19 expected pairs of length 2, found length 1 +#((1,),).to-dict() + +--- +// Error: 2-26 expected pairs of length 2, found length 3 +#(("key",1,2),).to-dict() + +--- +// Error: 2-21 expected key of type str, found integer +#((1, 2),).to-dict() + --- // Error: 9-26 unexpected argument: val #().zip(val: "applicable")