summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-04-11 21:54:47 +0200
committerLaurenz <laurmaedje@gmail.com>2023-04-11 21:59:02 +0200
commit12be8fe070d6c3b0ef04c744ba300063f30791cf (patch)
tree8f018b638ebbc78130cc34ae684dc06367bd865f
parent6dcb65e3a30c59f4a99cd1a985075b1d0adc385f (diff)
Let dictionaries respect insertion order
-rw-r--r--docs/src/reference/types.md16
-rw-r--r--src/eval/dict.rs28
-rw-r--r--src/eval/mod.rs4
-rw-r--r--src/eval/value.rs2
-rw-r--r--tests/ref/compiler/for.pngbin3352 -> 3342 bytes
-rw-r--r--tests/typ/compiler/dict.typ4
-rw-r--r--tests/typ/compiler/for.typ2
7 files changed, 33 insertions, 23 deletions
diff --git a/docs/src/reference/types.md b/docs/src/reference/types.md
index 184da137..7183bac4 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 4333a55e..b137f03c 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<BTreeMap<Str, Value>>);
+#[derive(Default, Clone, PartialEq)]
+pub struct Dict(Arc<IndexMap<Str, Value>>);
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<Str, Value>) -> Self {
+ pub fn from_map(map: IndexMap<Str, Value>) -> 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<Str, Value> {
+ pub fn iter(&self) -> indexmap::map::Iter<Str, Value> {
self.0.iter()
}
@@ -171,6 +174,15 @@ impl AddAssign for Dict {
}
}
+impl Hash for Dict {
+ fn hash<H: Hasher>(&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<T: IntoIterator<Item = (Str, Value)>>(&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<Str, Value>;
+ type IntoIter = indexmap::map::IntoIter<Str, Value>;
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 55d2a734..5a450481 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<Self::Output> {
- 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 b8a51c70..3fd9ed42 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 48fde203..5608248f 100644
--- a/tests/ref/compiler/for.png
+++ b/tests/ref/compiler/for.png
Binary files differ
diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ
index c8dd086f..fb0a59a3 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 4a3aefb4..f525215f 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.