diff --git a/docs/src/reference/types.md b/docs/src/reference/types.md index 184da1377..7183bac4e 100644 --- a/docs/src/reference/types.md +++ b/docs/src/reference/types.md @@ -674,7 +674,9 @@ Return a new array with the same items, but sorted. A map from string keys to values. You can construct a dictionary by enclosing comma-separated `key: value` pairs -in parentheses. The values do not have to be of the same type. +in parentheses. The values do not have to be of the same type. Since empty +parentheses already yield an empty array, you have to use the special `(:)` +syntax to create an empty dictionary. A dictionary is conceptually similar to an array, but it is indexed by strings instead of integers. You can access and create dictionary entries with the @@ -685,12 +687,8 @@ the value. Dictionaries can be added with the `+` operator and To check whether a key is present in the dictionary, use the `in` keyword. You can iterate over the pairs in a dictionary using a -[for loop]($scripting/#loops). -Dictionaries are always ordered by key. - -Since empty parentheses already yield an empty array, you have to use the -special `(:)` syntax to create an empty dictionary. - +[for loop]($scripting/#loops). This will iterate in the order the pairs were +inserted / declared. ## Example ```example @@ -735,12 +733,12 @@ If the dictionary already contains this key, the value is updated. The value of the pair that should be inserted. ### keys() -Returns the keys of the dictionary as an array in sorted order. +Returns the keys of the dictionary as an array in insertion order. - returns: array ### values() -Returns the values of the dictionary as an array in key-order. +Returns the values of the dictionary as an array in insertion order. - returns: array diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 4333a55eb..b137f03c7 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -1,5 +1,5 @@ -use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; +use std::hash::{Hash, Hasher}; use std::ops::{Add, AddAssign}; use std::sync::Arc; @@ -16,7 +16,7 @@ use crate::util::{pretty_array_like, separated_list, ArcExt}; macro_rules! __dict { ($($key:expr => $value:expr),* $(,)?) => {{ #[allow(unused_mut)] - let mut map = std::collections::BTreeMap::new(); + let mut map = $crate::eval::IndexMap::new(); $(map.insert($key.into(), $value.into());)* $crate::eval::Dict::from_map(map) }}; @@ -25,9 +25,12 @@ macro_rules! __dict { #[doc(inline)] pub use crate::__dict as dict; +#[doc(inline)] +pub use indexmap::IndexMap; + /// A reference-counted dictionary with value semantics. -#[derive(Default, Clone, PartialEq, Hash)] -pub struct Dict(Arc>); +#[derive(Default, Clone, PartialEq)] +pub struct Dict(Arc>); impl Dict { /// Create a new, empty dictionary. @@ -36,7 +39,7 @@ impl Dict { } /// Create a new dictionary from a mapping of strings to values. - pub fn from_map(map: BTreeMap) -> Self { + pub fn from_map(map: IndexMap) -> Self { Self(Arc::new(map)) } @@ -116,7 +119,7 @@ impl Dict { } /// Iterate over pairs of references to the contained keys and values. - pub fn iter(&self) -> std::collections::btree_map::Iter { + pub fn iter(&self) -> indexmap::map::Iter { self.0.iter() } @@ -171,6 +174,15 @@ impl AddAssign for Dict { } } +impl Hash for Dict { + fn hash(&self, state: &mut H) { + state.write_usize(self.0.len()); + for item in self { + item.hash(state); + } + } +} + impl Extend<(Str, Value)> for Dict { fn extend>(&mut self, iter: T) { Arc::make_mut(&mut self.0).extend(iter); @@ -185,7 +197,7 @@ impl FromIterator<(Str, Value)> for Dict { impl IntoIterator for Dict { type Item = (Str, Value); - type IntoIter = std::collections::btree_map::IntoIter; + type IntoIter = indexmap::map::IntoIter; fn into_iter(self) -> Self::IntoIter { Arc::take(self.0).into_iter() @@ -194,7 +206,7 @@ impl IntoIterator for Dict { impl<'a> IntoIterator for &'a Dict { type Item = (&'a Str, &'a Value); - type IntoIter = std::collections::btree_map::Iter<'a, Str, Value>; + type IntoIter = indexmap::map::Iter<'a, Str, Value>; fn into_iter(self) -> Self::IntoIter { self.iter() diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 55d2a7341..5a4504816 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -37,7 +37,7 @@ pub use self::value::*; pub(crate) use self::methods::methods_on; -use std::collections::{BTreeMap, HashSet}; +use std::collections::HashSet; use std::mem; use std::path::{Path, PathBuf}; @@ -870,7 +870,7 @@ impl Eval for ast::Dict { type Output = Dict; fn eval(&self, vm: &mut Vm) -> SourceResult { - let mut map = BTreeMap::new(); + let mut map = indexmap::IndexMap::new(); for item in self.items() { match item { diff --git a/src/eval/value.rs b/src/eval/value.rs index b8a51c701..3fd9ed422 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -444,6 +444,6 @@ mod tests { test(array![1, 2], "(1, 2)"); test(dict![], "(:)"); test(dict!["one" => 1], "(one: 1)"); - test(dict!["two" => false, "one" => 1], "(one: 1, two: false)"); + test(dict!["two" => false, "one" => 1], "(two: false, one: 1)"); } } diff --git a/tests/ref/compiler/for.png b/tests/ref/compiler/for.png index 48fde203a..5608248f8 100644 Binary files a/tests/ref/compiler/for.png and b/tests/ref/compiler/for.png differ diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ index c8dd086fe..fb0a59a36 100644 --- a/tests/typ/compiler/dict.typ +++ b/tests/typ/compiler/dict.typ @@ -48,8 +48,8 @@ #let dict = (a: 3, c: 2, b: 1) #test("c" in dict, true) #test(dict.len(), 3) -#test(dict.values(), (3, 1, 2)) -#test(dict.pairs().map(p => p.first() + str(p.last())).join(), "a3b1c2") +#test(dict.values(), (3, 2, 1)) +#test(dict.pairs().map(p => p.first() + str(p.last())).join(), "a3c2b1") #dict.remove("c") #test("c" in dict, false) diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ index 4a3aefb41..f525215ff 100644 --- a/tests/typ/compiler/for.typ +++ b/tests/typ/compiler/for.typ @@ -7,7 +7,7 @@ // Empty array. #for x in () [Nope] -// Dictionary is not traversed in insertion order. +// Dictionary is traversed in insertion order. // Should output `Age: 2. Name: Typst.`. #for (k, v) in (Name: "Typst", Age: 2) [ #k: #v.