From 811715dd586d16f237fe405382f219f097691eb6 Mon Sep 17 00:00:00 2001 From: Elie ROUDNINSKI Date: Thu, 26 Dec 2024 14:14:38 +0100 Subject: [PATCH] Add mutable accessors to `TypedArray` and `TypedMut` --- engine/src/lhs_types/array.rs | 172 +++++++++++++++++++++++++++---- engine/src/lhs_types/map.rs | 186 ++++++++++++++++++++++++++++++---- engine/src/lib.rs | 2 +- engine/src/types.rs | 9 +- 4 files changed, 325 insertions(+), 44 deletions(-) diff --git a/engine/src/lhs_types/array.rs b/engine/src/lhs_types/array.rs index 0c583d14..cb536c27 100644 --- a/engine/src/lhs_types/array.rs +++ b/engine/src/lhs_types/array.rs @@ -17,11 +17,13 @@ use std::{ ops::Deref, }; +use super::{map::InnerMap, TypedMap}; + // Ideally, we would want to use Cow<'a, LhsValue<'a>> here // but it doesnt work for unknown reasons // See https://github.com/rust-lang/rust/issues/23707#issuecomment-557312736 #[derive(Debug, Clone)] -enum InnerArray<'a> { +pub(crate) enum InnerArray<'a> { Owned(Vec>), Borrowed(&'a [LhsValue<'a>]), } @@ -89,7 +91,7 @@ impl Default for InnerArray<'_> { #[derive(Debug, Clone)] pub struct Array<'a> { val_type: CompoundType, - data: InnerArray<'a>, + pub(crate) data: InnerArray<'a>, } impl<'a> Array<'a> { @@ -485,7 +487,7 @@ impl<'a, 'b> From<&'a mut Array<'b>> for ArrayMut<'a, 'b> { /// Typed wrapper over an `Array` which provides /// infaillible operations. -#[derive(Debug)] +#[repr(transparent)] pub struct TypedArray<'a, V> where V: IntoValue<'a>, @@ -518,6 +520,14 @@ impl<'a, V: IntoValue<'a>> TypedArray<'a, V> { pub fn truncate(&mut self, len: usize) { self.array.truncate(len); } + + /// Converts the strongly typed array into a borrowed loosely typed array. + pub fn as_array(&'a self) -> Array<'a> { + Array { + val_type: V::TYPE.into(), + data: InnerArray::Borrowed(self.array.deref()), + } + } } impl TypedArray<'static, bool> { @@ -538,9 +548,26 @@ impl TypedArray<'static, bool> { } } -impl> PartialEq for TypedArray<'static, bool> { - fn eq(&self, other: &T) -> bool { - self.iter().eq(other.as_ref()) +impl<'a, V: IntoValue<'a>> fmt::Debug for TypedArray<'a, V> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_list().entries(self.array.iter()).finish() + } +} + +impl<'a, V: IntoValue<'a>> PartialEq for TypedArray<'a, V> { + fn eq(&self, other: &Self) -> bool { + self.array.deref() == other.array.deref() + } +} + +impl<'a, V: Copy + IntoValue<'a>, S: AsRef<[V]>> PartialEq for TypedArray<'a, V> { + fn eq(&self, other: &S) -> bool { + other + .as_ref() + .iter() + .copied() + .map(IntoValue::into_value) + .eq(self.array.iter()) } } @@ -594,26 +621,131 @@ impl<'a, V: IntoValue<'a>> IntoValue<'a> for TypedArray<'a, V> { } } -#[test] -fn test_size_of_array() { - assert_eq!(std::mem::size_of::>(), 32); +impl<'a, V: IntoValue<'a>> TypedArray<'a, TypedArray<'a, V>> { + /// Returns a mutable reference to an element or None if the index is out of bounds. + pub fn get(&self, index: usize) -> Option<&TypedArray<'a, V>> { + self.array.get(index).map(|val| match val { + LhsValue::Array(array) => { + // Safety: this is safe because `TypedArray` is a repr(transparent) + // newtype over `InnerArray`. + unsafe { std::mem::transmute::<&InnerArray<'a>, &TypedArray<'a, V>>(&array.data) } + } + _ => unreachable!(), + }) + } + + /// Returns a mutable reference to an element or None if the index is out of bounds. + pub fn get_mut(&mut self, index: usize) -> Option<&mut TypedArray<'a, V>> { + self.array.get_mut(index).map(|val| match val { + LhsValue::Array(array) => { + // Safety: this is safe because `TypedArray` is a repr(transparent) + // newtype over `InnerArray`. + unsafe { + std::mem::transmute::<&mut InnerArray<'a>, &mut TypedArray<'a, V>>( + &mut array.data, + ) + } + } + _ => unreachable!(), + }) + } +} + +impl<'a, V: IntoValue<'a>> TypedArray<'a, TypedMap<'a, V>> { + /// Returns a mutable reference to an element or None if the index is out of bounds. + pub fn get(&self, index: usize) -> Option<&TypedMap<'a, V>> { + self.array.get(index).map(|val| match val { + LhsValue::Map(map) => { + // Safety: this is safe because `TypedMap` is a repr(transparent) + // newtype over `InnerMap`. + unsafe { std::mem::transmute::<&InnerMap<'a>, &TypedMap<'a, V>>(&map.data) } + } + _ => unreachable!(), + }) + } + + /// Returns a mutable reference to an element or None if the index is out of bounds. + pub fn get_mut(&mut self, index: usize) -> Option<&mut TypedMap<'a, V>> { + self.array.get_mut(index).map(|val| match val { + LhsValue::Map(map) => { + // Safety: this is safe because `TypedMap` is a repr(transparent) + // newtype over `InnerMap`. + unsafe { + std::mem::transmute::<&mut InnerMap<'a>, &mut TypedMap<'a, V>>(&mut map.data) + } + } + _ => unreachable!(), + }) + } } -#[test] -fn test_borrowed_eq_owned() { - let mut owned = Array::new(Type::Bytes); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_size_of_array() { + assert_eq!(std::mem::size_of::>(), 32); + } + + #[test] + fn test_borrowed_eq_owned() { + let mut owned = Array::new(Type::Bytes); + + owned + .push(LhsValue::Bytes("borrowed".as_bytes().into())) + .unwrap(); + + let borrowed = owned.as_ref(); + + assert!(matches!(owned.data, InnerArray::Owned(_))); - owned - .push(LhsValue::Bytes("borrowed".as_bytes().into())) - .unwrap(); + assert!(matches!(borrowed.data, InnerArray::Borrowed(_))); - let borrowed = owned.as_ref(); + assert_eq!(owned, borrowed); - assert!(matches!(owned.data, InnerArray::Owned(_))); + assert_eq!(borrowed, borrowed.to_owned()); + } + + #[test] + fn test_typed_array_get_typed_array() { + let mut array = TypedArray::from_iter([ + TypedArray::from_iter(["a", "b", "c"]), + TypedArray::from_iter(["d", "e"]), + ]); + + assert_eq!(*array.get(0).unwrap(), ["a", "b", "c"],); + + assert_eq!(*array.get(1).unwrap(), ["d", "e"]); + + array.get_mut(1).unwrap().push("f"); + + assert_eq!(*array.get(1).unwrap(), ["d", "e", "f"]); + } + + fn key(s: &str) -> Box<[u8]> { + s.as_bytes().to_vec().into_boxed_slice() + } - assert!(matches!(borrowed.data, InnerArray::Borrowed(_))); + #[test] + fn test_typed_array_get_typed_map() { + let mut array = TypedArray::from_iter([ + TypedMap::from_iter([(key("a"), 42), (key("b"), 1337), (key("c"), 0)]), + TypedMap::from_iter([(key("d"), 7), (key("e"), 3)]), + ]); - assert_eq!(owned, borrowed); + assert_eq!( + array.get(0).unwrap(), + &[(b"a" as &[u8], 42), (b"b", 1337), (b"c", 0)] + ); - assert_eq!(borrowed, borrowed.to_owned()); + assert_eq!(array.get(1).unwrap(), &[(b"d" as &[u8], 7), (b"e", 3)]); + + array.get_mut(1).unwrap().insert(key("f"), 99); + + assert_eq!( + array.get(1).unwrap(), + &[(b"d" as &[u8], 7), (b"e", 3), (b"f", 99)] + ); + } } diff --git a/engine/src/lhs_types/map.rs b/engine/src/lhs_types/map.rs index a7218d0c..4a157063 100644 --- a/engine/src/lhs_types/map.rs +++ b/engine/src/lhs_types/map.rs @@ -19,8 +19,10 @@ use std::{ ops::Deref, }; +use super::{array::InnerArray, TypedArray}; + #[derive(Debug, Clone)] -enum InnerMap<'a> { +pub(crate) enum InnerMap<'a> { Owned(BTreeMap, LhsValue<'a>>), Borrowed(&'a BTreeMap, LhsValue<'a>>), } @@ -78,7 +80,7 @@ impl Default for InnerMap<'_> { #[derive(Debug, Clone)] pub struct Map<'a> { val_type: CompoundType, - data: InnerMap<'a>, + pub(crate) data: InnerMap<'a>, } impl<'a> Map<'a> { @@ -91,13 +93,13 @@ impl<'a> Map<'a> { } /// Get a reference to an element if it exists - pub fn get(&self, key: &[u8]) -> Option<&LhsValue<'a>> { - self.data.get(key) + pub fn get>(&self, key: K) -> Option<&LhsValue<'a>> { + self.data.get(key.as_ref()) } /// Get a mutable reference to an element if it exists - pub fn get_mut(&mut self, key: &[u8]) -> Option> { - self.data.get_mut(key).map(LhsValueMut::from) + pub fn get_mut>(&mut self, key: K) -> Option> { + self.data.get_mut(key.as_ref()).map(LhsValueMut::from) } /// Inserts an element, overwriting if one already exists @@ -479,7 +481,7 @@ impl<'a, 'b> From<&'a mut Map<'b>> for MapMut<'a, 'b> { /// Typed wrapper over a `Map` which provides /// infaillible operations. -#[derive(Debug)] +#[repr(transparent)] pub struct TypedMap<'a, V> where V: IntoValue<'a>, @@ -506,6 +508,37 @@ impl<'a, V: IntoValue<'a>> TypedMap<'a, V> { pub fn is_empty(&self) -> bool { self.map.is_empty() } + + /// Converts the strongly typed map into a borrowed loosely typed map. + pub fn as_map(&'a self) -> Map<'a> { + Map { + val_type: V::TYPE.into(), + data: InnerMap::Borrowed(self.map.deref()), + } + } +} + +impl<'a, V: IntoValue<'a>> fmt::Debug for TypedMap<'a, V> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_map().entries(self.map.iter()).finish() + } +} + +impl<'a, V: IntoValue<'a>> PartialEq for TypedMap<'a, V> { + fn eq(&self, other: &Self) -> bool { + self.map.deref() == other.map.deref() + } +} + +impl<'a, 'k, V: Copy + IntoValue<'a>, S: AsRef<[(&'k [u8], V)]>> PartialEq for TypedMap<'a, V> { + fn eq(&self, other: &S) -> bool { + other + .as_ref() + .iter() + .copied() + .map(|(k, v)| (k, v.into_value())) + .eq(self.map.iter().map(|(k, v)| (&**k, v.as_ref()))) + } } impl<'a, V: IntoValue<'a>> From> for Map<'a> { @@ -558,26 +591,137 @@ impl<'a, V: IntoValue<'a>> IntoValue<'a> for TypedMap<'a, V> { } } -#[test] -fn test_size_of_map() { - assert_eq!(std::mem::size_of::>(), 40); +impl<'a, V: IntoValue<'a>> TypedMap<'a, TypedMap<'a, V>> { + /// Returns a mutable reference to the value corresponding to the key. + pub fn get>(&self, key: K) -> Option<&TypedMap<'a, V>> { + self.map.get(key.as_ref()).map(|val| match val { + LhsValue::Map(map) => { + // Safety: this is safe because `TypedMap` is a repr(transparent) + // newtype over `InnerMap`. + unsafe { std::mem::transmute::<&InnerMap<'a>, &TypedMap<'a, V>>(&map.data) } + } + _ => unreachable!(), + }) + } + + /// Returns a mutable reference to the value corresponding to the key. + pub fn get_mut>(&mut self, key: K) -> Option<&mut TypedMap<'a, V>> { + self.map.get_mut(key.as_ref()).map(|val| match val { + LhsValue::Map(map) => { + // Safety: this is safe because `TypedMap` is a repr(transparent) + // newtype over `InnerMap`. + unsafe { + std::mem::transmute::<&mut InnerMap<'a>, &mut TypedMap<'a, V>>(&mut map.data) + } + } + _ => unreachable!(), + }) + } } -#[test] -fn test_borrowed_eq_owned() { - let mut owned = Map::new(Type::Bytes); +impl<'a, V: IntoValue<'a>> TypedMap<'a, TypedArray<'a, V>> { + /// Returns a mutable reference to the value corresponding to the key. + pub fn get>(&self, key: K) -> Option<&TypedArray<'a, V>> { + self.map.get(key.as_ref()).map(|val| match val { + LhsValue::Array(array) => { + // Safety: this is safe because `TypedArray` is a repr(transparent) + // newtype over `InnerArray`. + unsafe { std::mem::transmute::<&InnerArray<'a>, &TypedArray<'a, V>>(&array.data) } + } + _ => unreachable!(), + }) + } + + /// Returns a mutable reference to the value corresponding to the key. + pub fn get_mut>(&mut self, key: K) -> Option<&mut TypedArray<'a, V>> { + self.map.get_mut(key.as_ref()).map(|val| match val { + LhsValue::Array(array) => { + // Safety: this is safe because `TypedArray` is a repr(transparent) + // newtype over `InnerArray`. + unsafe { + std::mem::transmute::<&mut InnerArray<'a>, &mut TypedArray<'a, V>>( + &mut array.data, + ) + } + } + _ => unreachable!(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_size_of_map() { + assert_eq!(std::mem::size_of::>(), 40); + } + + #[test] + fn test_borrowed_eq_owned() { + let mut owned = Map::new(Type::Bytes); - owned - .insert(b"key", LhsValue::Bytes("borrowed".as_bytes().into())) - .unwrap(); + owned + .insert(b"key", LhsValue::Bytes("borrowed".as_bytes().into())) + .unwrap(); - let borrowed = owned.as_ref(); + let borrowed = owned.as_ref(); - assert!(matches!(owned.data, InnerMap::Owned(_))); + assert!(matches!(owned.data, InnerMap::Owned(_))); - assert!(matches!(borrowed.data, InnerMap::Borrowed(_))); + assert!(matches!(borrowed.data, InnerMap::Borrowed(_))); + + assert_eq!(owned, borrowed); + + assert_eq!(borrowed, borrowed.to_owned()); + } + + fn key(s: &str) -> Box<[u8]> { + s.as_bytes().to_vec().into_boxed_slice() + } - assert_eq!(owned, borrowed); + #[test] + fn test_typed_map_get_typed_map() { + let mut map = TypedMap::from_iter([ + ( + key("first"), + TypedMap::from_iter([(key("a"), 42), (key("b"), 1337), (key("c"), 0)]), + ), + ( + key("second"), + TypedMap::from_iter([(key("d"), 7), (key("e"), 3)]), + ), + ]); - assert_eq!(borrowed, borrowed.to_owned()); + assert_eq!( + *map.get("first").unwrap(), + [(b"a" as &[u8], 42), (b"b", 1337), (b"c", 0)] + ); + + assert_eq!(*map.get("second").unwrap(), [(b"d" as &[u8], 7), (b"e", 3)]); + + map.get_mut("second").unwrap().insert(key("f"), 99); + + assert_eq!( + *map.get("second").unwrap(), + [(b"d" as &[u8], 7), (b"e", 3), (b"f", 99)] + ); + } + + #[test] + fn test_typed_map_get_typed_array() { + let mut map = TypedMap::from_iter([ + (key("first"), TypedArray::from_iter(["a", "b", "c"])), + (key("second"), TypedArray::from_iter(["d", "e"])), + ]); + + assert_eq!(*map.get("first").unwrap(), ["a", "b", "c"]); + + assert_eq!(*map.get("second").unwrap(), ["d", "e"]); + + map.get_mut("second").unwrap().push("f"); + + assert_eq!(*map.get("second").unwrap(), ["d", "e", "f"]); + } } diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 4ec97ebe..2bf87641 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -102,7 +102,7 @@ pub use self::{ SimpleFunctionParam, }, lex::LexErrorKind, - lhs_types::{Array, ArrayMut, Map, MapIter, MapMut, TypedArray}, + lhs_types::{Array, ArrayMut, Map, MapIter, MapMut, TypedArray, TypedMap}, list_matcher::{ AlwaysList, AlwaysListMatcher, ListDefinition, ListMatcher, NeverList, NeverListMatcher, }, diff --git a/engine/src/types.rs b/engine/src/types.rs index 9dfc73e2..0223e6e5 100644 --- a/engine/src/types.rs +++ b/engine/src/types.rs @@ -423,6 +423,12 @@ impl GetType for CompoundType { } } +impl PartialEq<&LhsValue<'_>> for LhsValue<'_> { + fn eq(&self, other: &&LhsValue<'_>) -> bool { + self.eq(*other) + } +} + impl StrictPartialOrd for LhsValue<'_> {} impl PartialEq for LhsValue<'_> { @@ -453,8 +459,7 @@ impl<'a> BytesOrString<'a> { mod private { use super::IntoValue; - use crate::lhs_types::TypedMap; - use crate::TypedArray; + use crate::{TypedArray, TypedMap}; use std::borrow::Cow; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};