use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::ops::{Add, AddAssign};
use std::sync::Arc;
use ecow::{EcoString, eco_format};
use indexmap::IndexMap;
use rustc_hash::FxBuildHasher;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use typst_syntax::is_ident;
use typst_utils::ArcExt;
use crate::diag::{Hint, HintedStrResult, StrResult};
use crate::foundations::{
Array, Module, Repr, Str, Value, array, cast, func, repr, scope, ty,
};
#[macro_export]
#[doc(hidden)]
macro_rules! __dict {
($($key:expr => $value:expr),* $(,)?) => {{
#[allow(unused_mut)]
let mut map = $crate::foundations::IndexMap::default();
$(map.insert($key.into(), $crate::foundations::IntoValue::into_value($value));)*
$crate::foundations::Dict::from(map)
}};
}
#[doc(inline)]
pub use crate::__dict as dict;
#[ty(scope, cast, name = "dictionary")]
#[derive(Default, Clone, PartialEq)]
pub struct Dict(Arc<IndexMap<Str, Value, FxBuildHasher>>);
impl Dict {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get(&self, key: &str) -> StrResult<&Value> {
self.0.get(key).ok_or_else(|| missing_key(key))
}
pub fn at_mut(&mut self, key: &str) -> HintedStrResult<&mut Value> {
Arc::make_mut(&mut self.0)
.get_mut(key)
.ok_or_else(|| missing_key(key))
.hint("use `insert` to add or update values")
}
pub fn take(&mut self, key: &str) -> StrResult<Value> {
Arc::make_mut(&mut self.0)
.shift_remove(key)
.ok_or_else(|| missing_key(key))
}
pub fn contains(&self, key: &str) -> bool {
self.0.contains_key(key)
}
pub fn clear(&mut self) {
if Arc::strong_count(&self.0) == 1 {
Arc::make_mut(&mut self.0).clear();
} else {
*self = Self::new();
}
}
pub fn iter(&self) -> indexmap::map::Iter<'_, Str, Value> {
self.0.iter()
}
pub fn finish(&self, expected: &[&str]) -> StrResult<()> {
let mut iter = self.iter().peekable();
if iter.peek().is_none() {
return Ok(());
}
let unexpected: Vec<&str> = iter.map(|kv| kv.0.as_str()).collect();
Err(Self::unexpected_keys(unexpected, Some(expected)))
}
pub fn unexpected_keys(
unexpected: Vec<&str>,
hint_expected: Option<&[&str]>,
) -> EcoString {
let format_as_list = |arr: &[&str]| {
repr::separated_list(
&arr.iter().map(|s| eco_format!("\"{s}\"")).collect::<Vec<_>>(),
"and",
)
};
let mut msg = String::from(match unexpected.len() {
1 => "unexpected key ",
_ => "unexpected keys ",
});
msg.push_str(&format_as_list(&unexpected[..]));
if let Some(expected) = hint_expected {
msg.push_str(", valid keys are ");
msg.push_str(&format_as_list(expected));
}
msg.into()
}
}
#[scope]
impl Dict {
#[func(constructor)]
pub fn construct(
value: ToDict,
) -> Dict {
value.0
}
#[func(title = "Length")]
pub fn len(&self) -> usize {
self.0.len()
}
#[func]
pub fn at(
&self,
key: Str,
#[named]
default: Option<Value>,
) -> StrResult<Value> {
self.0
.get(&key)
.cloned()
.or(default)
.ok_or_else(|| missing_key_no_default(&key))
}
#[func]
pub fn insert(
&mut self,
key: Str,
value: Value,
) {
Arc::make_mut(&mut self.0).insert(key, value);
}
#[func]
pub fn remove(
&mut self,
key: Str,
#[named]
default: Option<Value>,
) -> StrResult<Value> {
Arc::make_mut(&mut self.0)
.shift_remove(&key)
.or(default)
.ok_or_else(|| missing_key(&key))
}
#[func]
pub fn keys(&self) -> Array {
self.0.keys().cloned().map(Value::Str).collect()
}
#[func]
pub fn values(&self) -> Array {
self.0.values().cloned().collect()
}
#[func]
pub fn pairs(&self) -> Array {
self.0
.iter()
.map(|(k, v)| Value::Array(array![k.clone(), v.clone()]))
.collect()
}
}
pub struct ToDict(Dict);
cast! {
ToDict,
v: Module => Self(v
.scope()
.iter()
.map(|(k, b)| (Str::from(k.clone()), b.read().clone()))
.collect()
),
}
impl Debug for Dict {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_map().entries(self.0.iter()).finish()
}
}
impl Repr for Dict {
fn repr(&self) -> EcoString {
if self.is_empty() {
return "(:)".into();
}
let max = 40;
let mut pieces: Vec<_> = self
.iter()
.take(max)
.map(|(key, value)| {
if is_ident(key) {
eco_format!("{key}: {}", value.repr())
} else {
eco_format!("{}: {}", key.repr(), value.repr())
}
})
.collect();
if self.len() > max {
pieces.push(eco_format!(".. ({} pairs omitted)", self.len() - max));
}
repr::pretty_array_like(&pieces, false).into()
}
}
impl Add for Dict {
type Output = Self;
fn add(mut self, rhs: Dict) -> Self::Output {
self += rhs;
self
}
}
impl AddAssign for Dict {
fn add_assign(&mut self, rhs: Dict) {
match Arc::try_unwrap(rhs.0) {
Ok(map) => self.extend(map),
Err(rc) => self.extend(rc.iter().map(|(k, v)| (k.clone(), v.clone()))),
}
}
}
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 Serialize for Dict {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Dict {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(IndexMap::<Str, Value, FxBuildHasher>::deserialize(deserializer)?.into())
}
}
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);
}
}
impl FromIterator<(Str, Value)> for Dict {
fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self {
Self(Arc::new(iter.into_iter().collect()))
}
}
impl IntoIterator for Dict {
type Item = (Str, Value);
type IntoIter = indexmap::map::IntoIter<Str, Value>;
fn into_iter(self) -> Self::IntoIter {
Arc::take(self.0).into_iter()
}
}
impl<'a> IntoIterator for &'a Dict {
type Item = (&'a Str, &'a Value);
type IntoIter = indexmap::map::Iter<'a, Str, Value>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl From<IndexMap<Str, Value, FxBuildHasher>> for Dict {
fn from(map: IndexMap<Str, Value, FxBuildHasher>) -> Self {
Self(Arc::new(map))
}
}
#[cold]
fn missing_key(key: &str) -> EcoString {
eco_format!("dictionary does not contain key {}", key.repr())
}
#[cold]
fn missing_key_no_default(key: &str) -> EcoString {
eco_format!(
"dictionary does not contain key {} \
and no default value was specified",
key.repr()
)
}