use std::fmt;
use std::str::from_utf8_unchecked;
mod iter;
mod str_list;
mod str_list_ref;
#[cfg(feature = "serde")]
mod with_serde;
pub use iter::StrStackIter;
pub use str_list::StrList;
pub use str_list::StrListIter;
pub use str_list_ref::StrListRef;
pub use str_list_ref::StrListValidationError;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Checkpoint {
items: u32,
bytes: u32,
}
#[derive(Debug, Clone)]
pub struct StrStackOverflow {
attempted: usize,
}
impl fmt::Display for StrStackOverflow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"StrStack overflow: attempted total byte length {} exceeds u32::MAX",
self.attempted
)
}
}
#[derive(Clone, Default, PartialEq, Eq)]
pub struct StrStack {
data: Vec<u8>,
ends: Vec<u32>,
}
impl StrStack {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn with_capacity(items: usize, bytes: usize) -> Self {
Self {
data: Vec::with_capacity(bytes),
ends: Vec::with_capacity(items),
}
}
#[inline]
pub fn len(&self) -> usize {
self.ends.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.ends.is_empty()
}
#[inline]
pub fn bytes_len(&self) -> u32 {
self.data.len() as u32
}
#[inline]
pub fn reserve_items(&mut self, additional: usize) {
self.ends.reserve(additional);
}
#[inline]
pub fn reserve_bytes(&mut self, additional: usize) {
self.data.reserve(additional);
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe { from_utf8_unchecked(&self.data) }
}
#[inline]
pub fn get(&self, index: usize) -> Option<&str> {
let (begin, end) = self.get_bounds(index)?;
Some(unsafe { self.get_unchecked_internal(begin as usize, end as usize) })
}
#[inline]
#[deprecated(note = "Use `get()` instead. This will be removed in a future version.")]
pub unsafe fn get_unchecked(&self, begin: usize, end: usize) -> &str {
unsafe {
let slice = self.data.get_unchecked(begin..end);
from_utf8_unchecked(slice)
}
}
#[inline]
pub(crate) unsafe fn get_unchecked_internal(&self, begin: usize, end: usize) -> &str {
unsafe {
let slice = self.data.get_unchecked(begin..end);
from_utf8_unchecked(slice)
}
}
#[inline]
pub fn get_bounds(&self, index: usize) -> Option<(u32, u32)> {
if index + 1 > self.ends.len() {
return None;
}
let (start, end) = if index > 0 {
(self.ends[index - 1], self.ends[index])
} else {
(0, self.ends[0])
};
debug_assert!(start <= end);
debug_assert!((end as usize) <= self.data.len());
Some((start, end))
}
#[inline]
pub fn get_top(&self) -> Option<&str> {
match self.ends.len() {
0 => None,
len => self.get(len - 1),
}
}
#[inline]
pub fn last(&self) -> Option<&str> {
self.get_top()
}
#[inline]
pub fn remove_top(&mut self) -> Option<()> {
self.ends.pop()?;
let end = self.ends.last().copied().unwrap_or(0) as usize;
self.data.truncate(end);
Some(())
}
#[inline]
pub fn pop_owned<T>(&mut self) -> Option<T>
where
T: for<'a> From<&'a str>,
{
let s = self.get_top()?.into();
self.remove_top();
Some(s)
}
#[inline]
pub fn push(&mut self, s: &str) {
self.data.extend_from_slice(s.as_bytes());
let new_end: u32 = self
.data
.len()
.try_into()
.expect("StrStack: total byte length exceeds u32::MAX");
self.ends.push(new_end);
}
#[inline]
pub fn try_push(&mut self, s: &str) -> Result<(), StrStackOverflow> {
let new_len = self.data.len() + s.len();
let new_end: u32 = new_len
.try_into()
.map_err(|_| StrStackOverflow { attempted: new_len })?;
self.data.extend_from_slice(s.as_bytes());
self.ends.push(new_end);
Ok(())
}
#[inline]
pub fn clear(&mut self) {
self.data.clear();
self.ends.clear();
}
#[inline]
pub fn truncate(&mut self, len: usize) {
if len >= self.ends.len() {
return;
}
self.ends.truncate(len);
let byte_end = self.ends.last().copied().unwrap_or(0) as usize;
self.data.truncate(byte_end);
}
#[inline]
pub fn checkpoint(&self) -> Checkpoint {
Checkpoint {
items: self.ends.len() as u32,
bytes: self.data.len() as u32,
}
}
#[inline]
pub fn reset(&mut self, cp: Checkpoint) {
assert!(
cp.items as usize <= self.ends.len() && cp.bytes as usize <= self.data.len(),
"StrStack::reset: invalid checkpoint (items: {}, bytes: {}) for stack (items: {}, bytes: {})",
cp.items, cp.bytes, self.ends.len(), self.data.len()
);
self.ends.truncate(cp.items as usize);
self.data.truncate(cp.bytes as usize);
}
#[inline]
pub fn iter(&self) -> StrStackIter<'_> {
StrStackIter::new(self)
}
#[inline]
pub(crate) fn data_as_slice(&self) -> &[u8] {
&self.data
}
#[inline]
pub(crate) fn ends_as_slice(&self) -> &[u32] {
&self.ends
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use super::*;
use crate::SmartString;
#[test]
fn test_create() {
let stack = StrStack::new();
assert_eq!(stack.len(), 0);
assert!(stack.is_empty());
assert_eq!(stack.get_top(), None);
assert_eq!(stack.get(0), None);
assert_eq!(stack.get_bounds(0), None);
}
#[test]
fn test_push() {
let mut stack = StrStack::new();
stack.push("123");
assert_eq!(stack.len(), 1);
assert!(!stack.is_empty());
assert_eq!(stack.get_top(), Some("123"));
assert_eq!(stack.get(0), Some("123"));
assert_eq!(stack.get_bounds(0), Some((0u32, 3u32)));
assert_eq!(stack.get(1), None);
assert_eq!(stack.get_bounds(1), None);
stack.push("456");
assert_eq!(stack.len(), 2);
assert!(!stack.is_empty());
assert_eq!(stack.get_top(), Some("456"));
assert_eq!(stack.get(0), Some("123"));
assert_eq!(stack.get_bounds(0), Some((0u32, 3u32)));
assert_eq!(stack.get(1), Some("456"));
assert_eq!(stack.get_bounds(1), Some((3u32, 6u32)));
assert_eq!(stack.get(2), None);
assert_eq!(stack.get_bounds(2), None);
}
#[test]
fn test_remove_top() {
let mut stack = StrStack::new();
stack.push("123");
stack.push("456");
stack.push("789");
assert_eq!(stack.len(), 3);
assert!(stack.remove_top().is_some());
assert_eq!(stack.len(), 2);
assert!(!stack.is_empty());
assert_eq!(stack.get_top(), Some("456"));
assert_eq!(stack.get(0), Some("123"));
assert_eq!(stack.get(1), Some("456"));
assert!(stack.get(2).is_none());
assert!(stack.get_bounds(2).is_none());
assert!(stack.remove_top().is_some());
assert_eq!(stack.len(), 1);
assert!(!stack.is_empty());
assert_eq!(stack.get_top(), Some("123"));
assert_eq!(stack.get(0), Some("123"));
assert!(stack.get(1).is_none());
assert!(stack.get_bounds(1).is_none());
assert!(stack.remove_top().is_some());
assert_eq!(stack.len(), 0);
assert!(stack.is_empty());
assert!(stack.get_top().is_none());
assert!(stack.get(0).is_none());
assert!(stack.get_bounds(0).is_none());
assert!(stack.remove_top().is_none());
}
#[test]
fn test_pop_owned() {
let mut stack = StrStack::new();
stack.push("123");
stack.push("456");
stack.push("789");
assert_eq!(stack.len(), 3);
assert_eq!(stack.pop_owned::<String>(), Some("789".into()));
assert_eq!(stack.len(), 2);
assert_eq!(stack.get_top(), Some("456"));
assert_eq!(stack.get(0), Some("123"));
assert_eq!(stack.get(1), Some("456"));
assert!(stack.get(2).is_none());
assert!(stack.get_bounds(2).is_none());
assert_eq!(stack.pop_owned::<SmartString>(), Some("456".into()));
assert_eq!(stack.len(), 1);
assert_eq!(stack.get_top(), Some("123"));
assert_eq!(stack.get(0), Some("123"));
assert!(stack.get(1).is_none());
assert!(stack.get_bounds(1).is_none());
assert_eq!(stack.pop_owned::<Rc<str>>(), Some("123".into()));
assert_eq!(stack.len(), 0);
assert!(stack.get_top().is_none());
assert!(stack.get(0).is_none());
assert!(stack.get_bounds(0).is_none());
assert!(stack.pop_owned::<Box<str>>().is_none());
}
#[test]
fn test_iter() {
let mut stack = StrStack::new();
stack.push("123");
stack.push("456");
stack.push("789");
let mut iter = stack.iter();
assert_eq!(iter.next(), Some("123"));
assert_eq!(iter.next(), Some("456"));
assert_eq!(iter.next(), Some("789"));
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
}
#[test]
fn test_unicode_push_get_bounds_and_as_str() {
let mut stack = StrStack::new();
stack.push("€"); stack.push("a"); stack.push("😊");
assert_eq!(stack.as_str(), "€a😊");
assert_eq!(stack.get(0), Some("€"));
assert_eq!(stack.get(1), Some("a"));
assert_eq!(stack.get(2), Some("😊"));
assert_eq!(stack.get_bounds(0), Some((0u32, 3u32)));
assert_eq!(stack.get_bounds(1), Some((3u32, 4u32)));
assert_eq!(stack.get_bounds(2), Some((4u32, 8u32)));
}
#[test]
fn test_unicode_remove_top_truncates_byte_buffer() {
let mut stack = StrStack::new();
stack.push("€"); stack.push("😊"); stack.push("a");
assert_eq!(stack.as_str(), "€😊a");
assert_eq!(stack.len(), 3);
stack.remove_top().unwrap();
assert_eq!(stack.as_str(), "€😊");
assert_eq!(stack.len(), 2);
assert_eq!(stack.get_top(), Some("😊"));
stack.remove_top().unwrap();
assert_eq!(stack.as_str(), "€");
assert_eq!(stack.len(), 1);
assert_eq!(stack.get_top(), Some("€"));
}
#[test]
fn test_push_remove_push_sequence() {
let mut stack = StrStack::new();
stack.push("aaa");
stack.push("bbb");
stack.push("ccc");
assert_eq!(stack.len(), 3);
stack.remove_top();
stack.remove_top();
assert_eq!(stack.len(), 1);
assert_eq!(stack.as_str(), "aaa");
assert_eq!(stack.get(0), Some("aaa"));
stack.push("ddd");
stack.push("eee");
assert_eq!(stack.len(), 3);
assert_eq!(stack.as_str(), "aaadddeee");
assert_eq!(stack.get(0), Some("aaa"));
assert_eq!(stack.get(1), Some("ddd"));
assert_eq!(stack.get(2), Some("eee"));
let collected: Vec<&str> = stack.iter().collect();
assert_eq!(collected, vec!["aaa", "ddd", "eee"]);
}
#[test]
fn test_push_remove_all_push_again() {
let mut stack = StrStack::new();
stack.push("first");
stack.push("second");
stack.remove_top();
stack.remove_top();
assert!(stack.is_empty());
assert_eq!(stack.as_str(), "");
stack.push("third");
assert_eq!(stack.len(), 1);
assert_eq!(stack.get(0), Some("third"));
assert_eq!(stack.as_str(), "third");
}
#[test]
fn test_push_remove_push_unicode() {
let mut stack = StrStack::new();
stack.push("你好"); stack.push("世界"); assert_eq!(stack.as_str(), "你好世界");
stack.remove_top();
assert_eq!(stack.as_str(), "你好");
stack.push("🦀"); assert_eq!(stack.as_str(), "你好🦀");
assert_eq!(stack.get(0), Some("你好"));
assert_eq!(stack.get(1), Some("🦀"));
let collected: Vec<&str> = stack.iter().collect();
assert_eq!(collected, vec!["你好", "🦀"]);
}
#[test]
fn test_as_str_equals_iter_concatenation() {
let mut stack = StrStack::new();
stack.push("abc");
stack.push("€");
stack.push("def");
stack.remove_top();
stack.push("ghi");
stack.push("😊");
let concatenated: String = stack.iter().collect();
assert_eq!(stack.as_str(), concatenated.as_str());
}
#[test]
fn test_clear() {
let mut stack = StrStack::new();
stack.push("hello");
stack.push("world");
assert_eq!(stack.len(), 2);
stack.remove_top();
stack.remove_top();
assert!(stack.is_empty());
assert_eq!(stack.len(), 0);
assert_eq!(stack.as_str(), "");
assert!(stack.iter().next().is_none());
stack.push("new");
assert_eq!(stack.len(), 1);
assert_eq!(stack.get(0), Some("new"));
}
#[test]
fn test_push_empty_strings() {
let mut stack = StrStack::new();
stack.push("");
stack.push("abc");
stack.push("");
assert_eq!(stack.len(), 3);
assert_eq!(stack.get(0), Some(""));
assert_eq!(stack.get(1), Some("abc"));
assert_eq!(stack.get(2), Some(""));
assert_eq!(stack.as_str(), "abc");
let collected: Vec<&str> = stack.iter().collect();
assert_eq!(collected, vec!["", "abc", ""]);
}
#[test]
fn test_with_capacity() {
let stack = StrStack::with_capacity(10, 100);
assert_eq!(stack.len(), 0);
assert!(stack.is_empty());
}
#[test]
fn test_bytes_len() {
let mut stack = StrStack::new();
assert_eq!(stack.bytes_len(), 0);
stack.push("abc"); assert_eq!(stack.bytes_len(), 3);
stack.push("€"); assert_eq!(stack.bytes_len(), 6);
stack.push("😊"); assert_eq!(stack.bytes_len(), 10);
}
#[test]
fn test_last() {
let mut stack = StrStack::new();
assert_eq!(stack.last(), None);
stack.push("first");
assert_eq!(stack.last(), Some("first"));
stack.push("second");
assert_eq!(stack.last(), Some("second"));
assert_eq!(stack.last(), stack.get_top());
}
#[test]
fn test_clear_public() {
let mut stack = StrStack::new();
stack.push("hello");
stack.push("world");
assert_eq!(stack.len(), 2);
assert_eq!(stack.bytes_len(), 10);
stack.clear();
assert!(stack.is_empty());
assert_eq!(stack.len(), 0);
assert_eq!(stack.bytes_len(), 0);
assert_eq!(stack.as_str(), "");
stack.push("new");
assert_eq!(stack.len(), 1);
assert_eq!(stack.get(0), Some("new"));
}
#[test]
fn test_truncate() {
let mut stack = StrStack::new();
stack.push("aaa");
stack.push("bbb");
stack.push("ccc");
stack.push("ddd");
assert_eq!(stack.len(), 4);
stack.truncate(2);
assert_eq!(stack.len(), 2);
assert_eq!(stack.get(0), Some("aaa"));
assert_eq!(stack.get(1), Some("bbb"));
assert_eq!(stack.get(2), None);
assert_eq!(stack.as_str(), "aaabbb");
}
#[test]
fn test_truncate_noop() {
let mut stack = StrStack::new();
stack.push("aaa");
stack.push("bbb");
stack.truncate(5);
assert_eq!(stack.len(), 2);
stack.truncate(2);
assert_eq!(stack.len(), 2);
}
#[test]
fn test_truncate_to_zero() {
let mut stack = StrStack::new();
stack.push("aaa");
stack.push("bbb");
stack.truncate(0);
assert!(stack.is_empty());
assert_eq!(stack.as_str(), "");
}
#[test]
fn test_truncate_unicode() {
let mut stack = StrStack::new();
stack.push("你好"); stack.push("世界"); stack.push("🦀");
stack.truncate(1);
assert_eq!(stack.len(), 1);
assert_eq!(stack.get(0), Some("你好"));
assert_eq!(stack.bytes_len(), 6);
}
#[test]
fn test_try_push_success() {
let mut stack = StrStack::new();
assert!(stack.try_push("hello").is_ok());
assert_eq!(stack.len(), 1);
assert_eq!(stack.get(0), Some("hello"));
}
#[test]
fn test_reserve() {
let mut stack = StrStack::new();
stack.reserve_items(10);
stack.reserve_bytes(100);
assert_eq!(stack.len(), 0);
assert!(stack.is_empty());
for i in 0..10 {
stack.push(&format!("{}", i));
}
assert_eq!(stack.len(), 10);
}
#[test]
fn test_checkpoint_basic() {
let mut stack = StrStack::new();
stack.push("aaa");
stack.push("bbb");
let cp = stack.checkpoint();
stack.push("ccc");
stack.push("ddd");
assert_eq!(stack.len(), 4);
stack.reset(cp);
assert_eq!(stack.len(), 2);
assert_eq!(stack.get(0), Some("aaa"));
assert_eq!(stack.get(1), Some("bbb"));
assert_eq!(stack.get(2), None);
assert_eq!(stack.as_str(), "aaabbb");
}
#[test]
fn test_checkpoint_empty_stack() {
let mut stack = StrStack::new();
let cp = stack.checkpoint();
stack.push("aaa");
stack.push("bbb");
assert_eq!(stack.len(), 2);
stack.reset(cp);
assert!(stack.is_empty());
assert_eq!(stack.as_str(), "");
}
#[test]
fn test_checkpoint_at_end_is_noop() {
let mut stack = StrStack::new();
stack.push("aaa");
stack.push("bbb");
let cp = stack.checkpoint();
stack.reset(cp);
assert_eq!(stack.len(), 2);
assert_eq!(stack.get(0), Some("aaa"));
assert_eq!(stack.get(1), Some("bbb"));
}
#[test]
fn test_checkpoint_nested() {
let mut stack = StrStack::new();
stack.push("aaa");
let cp1 = stack.checkpoint();
stack.push("bbb");
let cp2 = stack.checkpoint();
stack.push("ccc");
assert_eq!(stack.len(), 3);
stack.reset(cp2);
assert_eq!(stack.len(), 2);
assert_eq!(stack.last(), Some("bbb"));
stack.reset(cp1);
assert_eq!(stack.len(), 1);
assert_eq!(stack.last(), Some("aaa"));
}
#[test]
fn test_checkpoint_unicode() {
let mut stack = StrStack::new();
stack.push("你好"); let cp = stack.checkpoint();
stack.push("😊"); stack.push("🦀"); assert_eq!(stack.bytes_len(), 14);
stack.reset(cp);
assert_eq!(stack.len(), 1);
assert_eq!(stack.get(0), Some("你好"));
assert_eq!(stack.bytes_len(), 6);
}
#[test]
fn test_checkpoint_then_push_after_reset() {
let mut stack = StrStack::new();
stack.push("aaa");
let cp = stack.checkpoint();
stack.push("bbb");
stack.reset(cp);
stack.push("ccc");
assert_eq!(stack.len(), 2);
assert_eq!(stack.get(0), Some("aaa"));
assert_eq!(stack.get(1), Some("ccc"));
assert_eq!(stack.as_str(), "aaaccc");
}
#[test]
fn test_truncate_preserves_earlier_checkpoint() {
let mut stack = StrStack::new();
stack.push("aaa");
let cp = stack.checkpoint();
stack.push("bbb");
stack.push("ccc");
stack.push("ddd");
stack.truncate(3);
assert_eq!(stack.len(), 3);
stack.reset(cp);
assert_eq!(stack.len(), 1);
assert_eq!(stack.get(0), Some("aaa"));
}
#[test]
#[should_panic(expected = "invalid checkpoint")]
fn test_reset_stale_checkpoint_panics() {
let mut stack = StrStack::new();
stack.push("aaa");
stack.push("bbb");
stack.push("ccc");
let cp_late = stack.checkpoint();
stack.truncate(0);
assert!(stack.is_empty());
stack.reset(cp_late);
}
}