You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
8.8 KiB
354 lines
8.8 KiB
use std::{
|
|
mem::{self, MaybeUninit,},
|
|
slice,
|
|
str,
|
|
ptr,
|
|
|
|
fmt, error,
|
|
|
|
borrow::Borrow,
|
|
ops,
|
|
};
|
|
|
|
mod ext; use ext::*;
|
|
|
|
/// A `String` that lives entirely on the stack.
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature="copy", derive(Copy))]
|
|
#[repr(C)] // Needed for SmallString
|
|
pub struct StackString<const SIZE: usize>{
|
|
fill_ptr: usize,
|
|
buffer: MaybeUninit<[u8; SIZE]>,
|
|
}
|
|
/// Error returned when trying to create a `StackString` from a string that is too large to fit in it.
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct StrTooLargeError<const SIZE: usize>(usize);
|
|
|
|
mod small;
|
|
pub use small::SmallString;
|
|
|
|
impl<const SIZE: usize> StrTooLargeError<SIZE>
|
|
{
|
|
/// The invalid string's size
|
|
#[inline]
|
|
pub const fn invalid_size(&self) -> usize
|
|
{
|
|
self.0
|
|
}
|
|
/// The maximum allowed size
|
|
#[inline]
|
|
pub const fn max_size() -> usize
|
|
{
|
|
SIZE
|
|
}
|
|
}
|
|
|
|
impl<const SIZE: usize> error::Error for StrTooLargeError<SIZE>{}
|
|
impl<const SIZE: usize> fmt::Display for StrTooLargeError<SIZE>
|
|
{
|
|
#[inline]
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
write!(f, "string could not fit into {SIZE} bytes: was {} bytes long", self.0)
|
|
}
|
|
}
|
|
|
|
|
|
impl<const SIZE: usize> StackString<SIZE>
|
|
{
|
|
/// Try to create a `StackString` from an exact-sized buffer.
|
|
#[inline]
|
|
pub fn try_from_utf8_array(sz: [u8; SIZE]) -> Result<Self, std::str::Utf8Error>
|
|
{
|
|
let s = std::str::from_utf8(&sz)?.len();
|
|
Ok(Self {
|
|
fill_ptr: s,
|
|
buffer: MaybeUninit::new(sz),
|
|
})
|
|
}
|
|
/// The maximum capacity of this instance
|
|
#[inline]
|
|
pub const fn capacity(&self) -> usize
|
|
{
|
|
SIZE
|
|
}
|
|
/// The length of the string.
|
|
#[inline]
|
|
pub const fn len(&self) -> usize
|
|
{
|
|
self.fill_ptr
|
|
}
|
|
/// The amount of memory left before the instance becomes full.
|
|
#[inline]
|
|
pub const fn available(&self) -> usize
|
|
{
|
|
SIZE - self.fill_ptr
|
|
}
|
|
|
|
/// Create a new, empty `StackString`
|
|
#[inline]
|
|
pub const fn new() -> Self
|
|
{
|
|
Self {
|
|
fill_ptr: 0,
|
|
buffer: MaybeUninit::uninit(),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn buf_end(&mut self) -> *mut u8
|
|
{
|
|
unsafe {
|
|
(self.buffer.as_mut_ptr() as *mut u8).add(self.fill_ptr)
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn as_raw_buf_mut(&mut self) -> &mut [u8]
|
|
{
|
|
unsafe {
|
|
slice::from_raw_parts_mut(self.buffer.as_mut_ptr() as *mut u8, self.fill_ptr)
|
|
}
|
|
}
|
|
#[inline(always)]
|
|
fn as_raw_buf(&self) -> &[u8]
|
|
{
|
|
unsafe {
|
|
// MaybeUninit::slice_assume_init_ref(&self.buffer.as_bytes()[..self.fill_ptr]);
|
|
slice::from_raw_parts(self.buffer.as_ptr() as *const u8, self.fill_ptr)
|
|
}
|
|
}
|
|
|
|
/// A mutable reference to the `str` inside
|
|
#[inline]
|
|
pub fn as_mut_str(&mut self) -> &mut str
|
|
{
|
|
unsafe {
|
|
std::str::from_utf8_unchecked_mut(self.as_raw_buf_mut())
|
|
}
|
|
}
|
|
/// A reference to the `str` inside.
|
|
#[inline]
|
|
pub fn as_str(&self) -> &str
|
|
{
|
|
unsafe {
|
|
std::str::from_utf8_unchecked(self.as_raw_buf())
|
|
}
|
|
}
|
|
|
|
/// Append as much of `s` into `self` as possible, return the number of bytes appeneded.
|
|
///
|
|
/// This function guarantees:
|
|
/// * if `s` cannot fit wholely into `self`, the lowest valid UTF-8 codepoint of `s` that will fit into `self` is used instead
|
|
/// # Returns
|
|
/// The number of bytes copied from `s`.
|
|
#[inline(always)]
|
|
pub fn append_from_str(&mut self, s: &str) -> usize
|
|
{
|
|
let sl = s.len();
|
|
|
|
if self.fill_ptr >= SIZE || sl == 0 {
|
|
return 0;
|
|
}
|
|
|
|
let av = self.available();
|
|
let sl = if sl <= av {
|
|
// Can fit whole `str` in
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(s.as_bytes().as_ptr(), self.buf_end(), sl);
|
|
}
|
|
sl
|
|
} else if s.is_char_boundary(av) {
|
|
// Can only fit part in, check if on codepoint boundary
|
|
unsafe {
|
|
ptr::copy_nonoverlapping(s.as_bytes().as_ptr(), self.buf_end(), av);
|
|
}
|
|
av
|
|
} else {
|
|
// Can only fit part in, find the char boundary below `av` and append that.
|
|
return self.append_from_str(&s[..s.floor_char_boundary_impl(av)]); //TODO: implement floor_char_boundary() ourselves, and we probably don't need this recursive call.
|
|
};
|
|
self.fill_ptr += sl;
|
|
sl
|
|
}
|
|
|
|
/// Append as much of `s` into `self` as possible.
|
|
///
|
|
/// This function has the same guarantees as `append_from_str()`.
|
|
///
|
|
/// # Returns
|
|
/// * `Ok(s)` - if the entire string was appeneded: The part of `self` that now contains `s`.
|
|
/// * `Err(s)` - if the entire string was **not** appeneded: The part of `s` that was not copied into `self`. The difference between the returned string and the parameter `s` is how much was copied into `self.
|
|
#[inline]
|
|
pub fn try_append_from_str<'inner, 'outer>(&'inner mut self, s: &'outer str) -> Result<&'inner mut str, &'outer str>
|
|
{
|
|
match self.append_from_str(s) {
|
|
whole if whole == s.len() => {
|
|
let substr = self.fill_ptr - whole;
|
|
Ok(&mut self.as_mut_str()[substr..])
|
|
},
|
|
copied /*if copied < s.len()*/ => Err(&s[copied..]),
|
|
}
|
|
}
|
|
|
|
/// Append as much of `s` into `self` as possible.
|
|
///
|
|
/// This function has the same guarantees as `append_from_str()`.
|
|
///
|
|
/// # Returns
|
|
/// A tuple containing the copied part of `s`, and the part of `s` that `self` did not have space for.
|
|
#[inline]
|
|
pub fn append_from_str_split<'i, 'o>(&'i mut self, s: &'o str) -> (&'i mut str, &'o str)
|
|
{
|
|
let end = self.fill_ptr;
|
|
let written = self.append_from_str(s);
|
|
if written == s.len() {
|
|
(&mut self.as_mut_str()[end..], "")
|
|
} else {
|
|
(&mut self.as_mut_str()[end..written], &s[written..])
|
|
}
|
|
}
|
|
|
|
/// Attempt to append the whole string `s` into `self`.
|
|
///
|
|
/// # Returns
|
|
/// * `Some(substr)` - If all of `s` fits in to `self`: the substring of `self` that now contains `s`.
|
|
/// * `None` - If `s` was too large to fit entirely in to `self`
|
|
#[inline]
|
|
pub fn try_append_whole_str<'i>(&'i mut self, s: &str) -> Result<&'i mut str, StrTooLargeError<SIZE>>
|
|
{
|
|
let av = self.available();
|
|
if s.len() <= av {
|
|
let len = self.append_from_str(s);
|
|
debug_assert_eq!(len, s.len(), "Bad append");
|
|
let _ = len;
|
|
|
|
Ok(&mut self.as_mut_str()[av..])
|
|
} else {
|
|
Err(StrTooLargeError(s.len()))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<const SIZE: usize> Borrow<str> for StackString<SIZE>
|
|
{
|
|
#[inline]
|
|
fn borrow(&self) -> &str {
|
|
self.as_str()
|
|
}
|
|
}
|
|
|
|
impl<const SIZE: usize> ops::Deref for StackString<SIZE>
|
|
{
|
|
type Target = str;
|
|
#[inline]
|
|
fn deref(&self) -> &Self::Target {
|
|
self.as_str()
|
|
}
|
|
}
|
|
|
|
impl<const SIZE: usize> std::str::FromStr for StackString<SIZE>
|
|
{
|
|
type Err = StrTooLargeError<SIZE>;
|
|
#[inline]
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
if s.len() > SIZE {
|
|
Err(StrTooLargeError(s.len()))
|
|
} else {
|
|
let mut o = Self::new();
|
|
o.try_append_whole_str(s)?;
|
|
Ok(o)
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<const SIZE: usize> TryFrom<String> for StackString<SIZE>
|
|
{
|
|
type Error = StrTooLargeError<SIZE>;
|
|
|
|
#[inline]
|
|
fn try_from(from: String) -> Result<Self, Self::Error>
|
|
{
|
|
from.parse()
|
|
}
|
|
}
|
|
|
|
|
|
impl<'a, const SIZE: usize> TryFrom<&'a str> for StackString<SIZE>
|
|
{
|
|
type Error = StrTooLargeError<SIZE>;
|
|
|
|
#[inline]
|
|
fn try_from(from: &'a str) -> Result<Self, Self::Error>
|
|
{
|
|
from.parse()
|
|
}
|
|
}
|
|
|
|
|
|
impl<const SIZE: usize> TryFrom<[u8; SIZE]> for StackString<SIZE>
|
|
{
|
|
type Error = std::str::Utf8Error;
|
|
|
|
#[inline]
|
|
fn try_from(from: [u8; SIZE]) -> Result<Self, Self::Error>
|
|
{
|
|
Self::try_from_utf8_array(from)
|
|
}
|
|
}
|
|
|
|
impl<const SIZE: usize> fmt::Display for StackString<SIZE>
|
|
{
|
|
#[inline]
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
f.write_str(self.as_str())
|
|
}
|
|
}
|
|
|
|
|
|
impl<const SIZE: usize> fmt::Write for StackString<SIZE>
|
|
{
|
|
#[inline]
|
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
|
self.try_append_whole_str(s).map_err(|_| fmt::Error::default())?;
|
|
Ok(())
|
|
}
|
|
#[inline]
|
|
fn write_char(&mut self, c: char) -> fmt::Result {
|
|
let l = c.len_utf8();
|
|
if l > self.available() {
|
|
Err(fmt::Error::default())
|
|
} else {
|
|
let end = self.fill_ptr;
|
|
let end = c.encode_utf8(&mut (self.as_raw_buf_mut())[end..]).len();
|
|
self.fill_ptr += end;
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
const _: () = {
|
|
use std::io::{
|
|
self,Write
|
|
};
|
|
impl<const SIZE: usize> Write for StackString<SIZE>
|
|
{
|
|
#[inline]
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
let buf = std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
Ok(self.append_from_str(buf))
|
|
}
|
|
#[inline]
|
|
fn flush(&mut self) -> io::Result<()> {
|
|
Ok(())
|
|
}
|
|
#[inline]
|
|
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
|
|
let buf = std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
self.try_append_whole_str(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)).map(|_| ())
|
|
}
|
|
}
|
|
};
|