use memcmp::Memcmp;
use std::fmt::{Debug, Display, Formatter};
#[derive(Clone, Eq, PartialEq)]
pub struct BoxedString {
data: Box<[u8]>,
}
impl BoxedString {
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl From<&str> for BoxedString {
fn from(value: &str) -> Self {
BoxedString {
data: value.as_bytes().into(),
}
}
}
impl AsRef<str> for BoxedString {
fn as_ref(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(&self.data) }
}
}
impl Debug for BoxedString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_ref())?;
f.write_str(" (heap)")?;
Ok(())
}
}
impl Display for BoxedString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_ref())
}
}
pub struct InlineString {
data: [u8; InlineString::MAX],
size: u8,
}
impl InlineString {
pub const MAX: usize = crate::ig::node::NODE_MAP_SIZE - 1;
pub fn len(&self) -> usize {
self.size as usize
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
}
impl From<&str> for InlineString {
fn from(value: &str) -> Self {
let mut data = [0_u8; InlineString::MAX];
data[0..value.len()].clone_from_slice(&value.as_bytes()[0..value.len()]);
InlineString {
data,
size: value.len() as u8,
}
}
}
impl Clone for InlineString {
fn clone(&self) -> Self {
InlineString {
data: self.data,
size: self.size,
}
}
}
impl PartialEq for InlineString {
fn eq(&self, other: &Self) -> bool {
if self.size == other.size {
let size = self.size as usize;
self.data[0..size].memcmp(&other.data[0..size])
} else {
false
}
}
}
impl AsRef<str> for InlineString {
fn as_ref(&self) -> &str {
let size = self.size as usize;
unsafe { std::str::from_utf8_unchecked(&self.data[0..size]) }
}
}
impl Debug for InlineString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_ref())?;
f.write_str(" (inlined)")?;
Ok(())
}
}
impl Display for InlineString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_ref())
}
}
#[cfg(test)]
mod tests {
use crate::ig::strings::BoxedString;
use crate::ig::strings::InlineString;
#[test]
fn boxed_strings_behave_like_strings() {
for i in 0..256 {
let data = "X".repeat(i);
let boxed_data = BoxedString::from(data.as_str());
assert_eq!(boxed_data, boxed_data);
assert_ne!(boxed_data, BoxedString::from("OTHER"));
assert_eq!(data.as_str(), boxed_data.as_ref());
assert_eq!(data.len(), boxed_data.len());
assert_eq!(data.is_empty(), boxed_data.is_empty());
}
}
#[test]
fn inline_strings_behave_like_strings() {
for i in 0..=InlineString::MAX {
let data = "X".repeat(i);
let inline_data = InlineString::from(data.as_str());
assert_eq!(inline_data, inline_data);
assert_ne!(inline_data, InlineString::from("OTHER"));
assert_eq!(data.as_str(), inline_data.as_ref());
assert_eq!(data.len(), inline_data.len());
assert_eq!(data.is_empty(), inline_data.is_empty());
}
}
#[test]
fn cloning_boxed_strings_works() {
let original = BoxedString::from("Test String");
assert_eq!(original.as_ref(), "Test String");
assert_eq!(original.clone().as_ref(), "Test String");
}
#[test]
fn cloning_inline_strings_works() {
let original = InlineString::from("Test String");
assert_eq!(original.as_ref(), "Test String");
assert_eq!(original.clone().as_ref(), "Test String");
}
#[test]
#[should_panic]
fn inline_strings_panic_if_too_long() {
let _ = InlineString::from("X".repeat(InlineString::MAX + 1).as_str());
}
}