Let dictionaries respect insertion order

This commit is contained in:
Laurenz 2023-04-11 21:54:47 +02:00
parent 6dcb65e3a3
commit 12be8fe070
7 changed files with 33 additions and 23 deletions

View File

@ -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

View File

@ -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()

View File

@ -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 {

View File

@ -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)");
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -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)

View File

@ -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.