summaryrefslogtreecommitdiff
path: root/src/eval/dict.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-01 16:30:58 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-01 16:33:28 +0100
commit6ab7760822ccd24b4ef126d4737d41f1be15fe19 (patch)
tree49905f91d292ceefe4f9878ead43f117c4b1fec0 /src/eval/dict.rs
parentab841188e3d2687ee8f436336e6fde337985a83e (diff)
Split up `model` module
Diffstat (limited to 'src/eval/dict.rs')
-rw-r--r--src/eval/dict.rs209
1 files changed, 209 insertions, 0 deletions
diff --git a/src/eval/dict.rs b/src/eval/dict.rs
new file mode 100644
index 00000000..6c1934c9
--- /dev/null
+++ b/src/eval/dict.rs
@@ -0,0 +1,209 @@
+use std::collections::BTreeMap;
+use std::fmt::{self, Debug, Formatter, Write};
+use std::ops::{Add, AddAssign};
+use std::sync::Arc;
+
+use ecow::{eco_format, EcoString};
+
+use super::{array, Array, Str, Value};
+use crate::diag::StrResult;
+use crate::syntax::is_ident;
+use crate::util::ArcExt;
+
+/// Create a new [`Dict`] from key-value pairs.
+#[macro_export]
+#[doc(hidden)]
+macro_rules! __dict {
+ ($($key:expr => $value:expr),* $(,)?) => {{
+ #[allow(unused_mut)]
+ let mut map = std::collections::BTreeMap::new();
+ $(map.insert($key.into(), $value.into());)*
+ $crate::eval::Dict::from_map(map)
+ }};
+}
+
+#[doc(inline)]
+pub use crate::__dict as dict;
+
+/// A reference-counted dictionary with value semantics.
+#[derive(Default, Clone, PartialEq, Hash)]
+pub struct Dict(Arc<BTreeMap<Str, Value>>);
+
+impl Dict {
+ /// Create a new, empty dictionary.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Create a new dictionary from a mapping of strings to values.
+ pub fn from_map(map: BTreeMap<Str, Value>) -> Self {
+ Self(Arc::new(map))
+ }
+
+ /// Whether the dictionary is empty.
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ /// The number of pairs in the dictionary.
+ pub fn len(&self) -> i64 {
+ self.0.len() as i64
+ }
+
+ /// Borrow the value the given `key` maps to.
+ pub fn at(&self, key: &str) -> StrResult<&Value> {
+ self.0.get(key).ok_or_else(|| missing_key(key))
+ }
+
+ /// Mutably borrow the value the given `key` maps to.
+ pub fn at_mut(&mut self, key: &str) -> StrResult<&mut Value> {
+ Arc::make_mut(&mut self.0)
+ .get_mut(key)
+ .ok_or_else(|| missing_key(key))
+ }
+
+ /// Remove the value if the dictionary contains the given key.
+ pub fn take(&mut self, key: &str) -> StrResult<Value> {
+ Arc::make_mut(&mut self.0)
+ .remove(key)
+ .ok_or_else(|| eco_format!("missing key: {:?}", Str::from(key)))
+ }
+
+ /// Whether the dictionary contains a specific key.
+ pub fn contains(&self, key: &str) -> bool {
+ self.0.contains_key(key)
+ }
+
+ /// Insert a mapping from the given `key` to the given `value`.
+ pub fn insert(&mut self, key: Str, value: Value) {
+ Arc::make_mut(&mut self.0).insert(key, value);
+ }
+
+ /// Remove a mapping by `key` and return the value.
+ pub fn remove(&mut self, key: &str) -> StrResult<Value> {
+ match Arc::make_mut(&mut self.0).remove(key) {
+ Some(value) => Ok(value),
+ None => Err(missing_key(key)),
+ }
+ }
+
+ /// Clear the dictionary.
+ pub fn clear(&mut self) {
+ if Arc::strong_count(&self.0) == 1 {
+ Arc::make_mut(&mut self.0).clear();
+ } else {
+ *self = Self::new();
+ }
+ }
+
+ /// Return the keys of the dictionary as an array.
+ pub fn keys(&self) -> Array {
+ self.0.keys().cloned().map(Value::Str).collect()
+ }
+
+ /// Return the values of the dictionary as an array.
+ pub fn values(&self) -> Array {
+ self.0.values().cloned().collect()
+ }
+
+ /// Return the values of the dictionary as an array of pairs (arrays of
+ /// length two).
+ pub fn pairs(&self) -> Array {
+ self.0
+ .iter()
+ .map(|(k, v)| Value::Array(array![k.clone(), v.clone()]))
+ .collect()
+ }
+
+ /// Iterate over pairs of references to the contained keys and values.
+ pub fn iter(&self) -> std::collections::btree_map::Iter<Str, Value> {
+ self.0.iter()
+ }
+
+ /// Return an "unexpected key" error if there is any remaining pair.
+ pub fn finish(&self, expected: &[&str]) -> StrResult<()> {
+ if let Some((key, _)) = self.iter().next() {
+ let parts: Vec<_> = expected.iter().map(|s| eco_format!("\"{s}\"")).collect();
+ let mut msg = format!("unexpected key {key:?}, valid keys are ");
+ crate::diag::comma_list(&mut msg, &parts, "and");
+ return Err(msg.into());
+ }
+ Ok(())
+ }
+}
+
+/// The missing key access error message.
+#[cold]
+fn missing_key(key: &str) -> EcoString {
+ eco_format!("dictionary does not contain key {:?}", Str::from(key))
+}
+
+impl Debug for Dict {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.write_char('(')?;
+ if self.is_empty() {
+ f.write_char(':')?;
+ }
+ for (i, (key, value)) in self.iter().enumerate() {
+ if is_ident(key) {
+ f.write_str(key)?;
+ } else {
+ write!(f, "{key:?}")?;
+ }
+ f.write_str(": ")?;
+ value.fmt(f)?;
+ if i + 1 < self.0.len() {
+ f.write_str(", ")?;
+ }
+ }
+ f.write_char(')')
+ }
+}
+
+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 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 = std::collections::btree_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 = std::collections::btree_map::Iter<'a, Str, Value>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter()
+ }
+}