use crate::{Borrow, BorrowMut, Error, PtrMut, Result, prelude::*};
use indexmap::{Equivalent, IndexMap};
use rustc_hash::FxHasher;
use std::{
hash::{BuildHasherDefault, Hash},
ops::{Deref, DerefMut, RangeBounds},
};
pub type KotoHasher = FxHasher;
type ValueMapType = IndexMap<ValueKey, KValue, BuildHasherDefault<KotoHasher>>;
#[derive(Clone, Default)]
pub struct ValueMap(ValueMapType);
impl ValueMap {
pub fn with_capacity(capacity: usize) -> Self {
Self(ValueMapType::with_capacity_and_hasher(
capacity,
Default::default(),
))
}
pub fn make_data_slice(&self, range: impl RangeBounds<usize>) -> Option<Self> {
self.get_range(range).map(|entries| {
Self::from_iter(
entries
.iter()
.map(|(key, value)| (key.clone(), value.clone())),
)
})
}
}
impl Deref for ValueMap {
type Target = ValueMapType;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ValueMap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl FromIterator<(ValueKey, KValue)> for ValueMap {
fn from_iter<T: IntoIterator<Item = (ValueKey, KValue)>>(iter: T) -> ValueMap {
Self(ValueMapType::from_iter(iter))
}
}
#[derive(Clone, Default)]
pub struct KMap {
data: PtrMut<ValueMap>,
meta: Option<PtrMut<MetaMap>>,
}
impl KMap {
pub fn new() -> Self {
Self::default()
}
pub fn with_type(type_name: &str) -> Self {
let mut meta = MetaMap::default();
meta.insert(MetaKey::Type, type_name.into());
Self::with_contents(ValueMap::default(), Some(meta))
}
pub fn with_capacity(capacity: usize) -> Self {
Self::with_contents(ValueMap::with_capacity(capacity), None)
}
pub fn with_data(data: ValueMap) -> Self {
Self::with_contents(data, None)
}
pub fn with_contents(data: ValueMap, meta: Option<MetaMap>) -> Self {
Self {
data: data.into(),
meta: meta.map(PtrMut::from),
}
}
pub fn from_data_and_meta_maps(data: &Self, meta: &Self) -> Self {
Self {
data: data.data.clone(),
meta: meta.meta.clone(),
}
}
pub fn data(&self) -> Borrow<'_, ValueMap> {
self.data.borrow()
}
pub fn data_mut(&self) -> BorrowMut<'_, ValueMap> {
self.data.borrow_mut()
}
pub fn meta_map(&self) -> Option<&PtrMut<MetaMap>> {
self.meta.as_ref()
}
pub fn set_meta_map(&mut self, meta: Option<PtrMut<MetaMap>>) {
self.meta = meta;
}
pub fn contains_meta_key(&self, key: &MetaKey) -> bool {
self.meta
.as_ref()
.is_some_and(|meta| meta.borrow().contains_key(key))
}
pub fn get<K>(&self, key: &K) -> Option<KValue>
where
K: Hash + Equivalent<ValueKey> + ?Sized,
{
self.data.borrow().get(key).cloned()
}
pub fn get_meta_value(&self, key: &MetaKey) -> Option<KValue> {
self.meta
.as_ref()
.and_then(|meta| meta.borrow().get(key).cloned())
}
pub fn insert(&self, key: impl Into<ValueKey>, value: impl Into<KValue>) {
self.data_mut().insert(key.into(), value.into());
}
pub fn remove(&self, key: impl Into<ValueKey>) -> Option<KValue> {
self.data_mut().shift_remove(&key.into())
}
pub fn remove_path(&self, path: &str) -> Option<KValue> {
if let Some((node, rest)) = path.split_once(".") {
self.get(node)
.and_then(|child| match child {
KValue::Map(map) => Some(map),
_ => None,
})
.and_then(|nested| nested.remove_path(rest))
} else {
self.remove(path)
}
}
pub fn insert_meta(&mut self, key: MetaKey, value: KValue) {
self.meta
.get_or_insert_with(Default::default)
.borrow_mut()
.insert(key, value);
}
pub fn add_fn(&self, id: &str, f: impl KotoFunction) {
self.insert(id, KValue::NativeFunction(KNativeFunction::new(f)));
}
pub fn len(&self) -> usize {
self.data().len()
}
pub fn is_empty(&self) -> bool {
self.data().is_empty()
}
pub fn clear(&mut self) {
self.data_mut().clear();
self.meta = None;
}
pub fn is_same_instance(&self, other: &Self) -> bool {
PtrMut::ptr_eq(&self.data, &other.data)
}
pub fn meta_type(&self) -> Option<KString> {
use KValue::*;
match self.get_meta_value(&MetaKey::Type) {
Some(Str(s)) => Some(s),
Some(_) => Some("Error: expected string as result of @type".into()),
None => match self.get_meta_value(&MetaKey::Base) {
Some(Map(base)) => base.meta_type(),
_ => None,
},
}
}
pub fn display(&self, ctx: &mut DisplayContext) -> Result<()> {
if self.contains_meta_key(&UnaryOp::Display.into()) {
let mut vm = ctx
.vm()
.ok_or_else(|| Error::from("missing VM in map display op"))?
.spawn_shared_vm();
match vm.run_unary_op(UnaryOp::Display, self.clone().into())? {
KValue::Str(display_result) => {
ctx.append(display_result);
}
unexpected => return unexpected_type("String as @display result", &unexpected),
}
} else {
if let Some(meta_type) = self.meta_type() {
ctx.append(meta_type);
ctx.append(' ');
}
ctx.append('{');
let id = PtrMut::address(&self.data);
if ctx.is_in_parents(id) {
ctx.append("...");
} else {
ctx.push_container(id);
for (i, (key, value)) in self.data().iter().enumerate() {
if i > 0 {
ctx.append(", ");
}
let mut key_ctx = DisplayContext::default();
key.value().display(&mut key_ctx)?;
ctx.append(key_ctx.result());
ctx.append(": ");
value.display(ctx)?;
}
ctx.pop_container();
}
ctx.append('}');
}
Ok(())
}
}
impl From<ValueMap> for KMap {
fn from(value: ValueMap) -> Self {
KMap::with_data(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_and_remove_with_string() {
let m = KMap::default();
assert!(m.get("test").is_none());
m.insert("test", KValue::Null);
assert!(m.get("test").is_some());
assert!(matches!(m.remove("test"), Some(KValue::Null)));
assert!(m.get("test").is_none());
}
#[test]
fn remove_path() {
let b = KMap::default();
b.insert("c", KValue::Null);
b.insert("d", KValue::Null);
let a = KMap::default();
a.insert("b", b.clone());
let x = KMap::default();
x.insert("a", a);
x.remove_path("a.b.c");
assert!(b.get("c").is_none());
assert!(b.get("d").is_some());
}
}