#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod tests;
#[cfg(feature = "std")]
mod std_only;
#[macro_use]
mod macros;
use core::{
cmp::PartialEq,
fmt,
ops::{Deref, DerefMut},
ptr,
str::{from_utf8_unchecked, from_utf8_unchecked_mut}
};
#[derive(Clone)]
pub struct MicroStr<const CAP: usize> {
buffer: [u8; CAP],
len: usize,
}
impl<const CAP: usize> MicroStr<CAP>
{
#[inline]
pub const fn new() -> Self {
Self {
buffer: [0; CAP],
len: 0,
}
}
#[must_use = "this returns a new `MicroStr`, it does not modify `self`"]
pub const fn from_str(s: &str) -> Result<Self, (Self, usize)> {
let mut result = Self::new();
match result.push_str(s) {
Ok(()) => {Ok(result)},
Err(bytes) => {Err((result, bytes))}
}
}
pub const fn from_const(s: &str) -> Self {
let mut result = Self::new();
let truncating = utf8_truncator(s, CAP);
unsafe {
ptr::copy_nonoverlapping(s.as_ptr(), result.as_mut_ptr(), truncating);
}
result.len = truncating;
result
}
pub const unsafe fn from_raw_buffer<const N: usize>(buf: [u8; N]) -> Self {
let len = const_min(N, CAP);
let mut buffer = [0; CAP];
ptr::copy_nonoverlapping(buf.as_ptr(), buffer.as_mut_ptr(), len);
Self { buffer, len }
}
pub const unsafe fn from_str_unchecked(s: &str) -> Self {
let mut buf = [0; CAP];
let to_copy = const_min(s.len(), CAP);
ptr::copy_nonoverlapping(s.as_ptr(), buf.as_mut_ptr(), to_copy);
Self {
buffer: buf,
len: to_copy
}
}
#[inline]
pub const fn as_ptr(&self) -> *const u8 {
self.buffer.as_ptr()
}
#[inline]
pub const fn as_mut_ptr(&mut self) -> *mut u8 {
self.buffer.as_mut_ptr()
}
#[inline]
pub const fn capacity(&self) -> usize {
CAP
}
#[inline]
pub const fn extra_capacity(&self) -> usize {
CAP - self.len
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub const fn bytes_len(&self) -> usize {
self.len
}
pub fn len(&self) -> usize {
self.chars().count()
}
pub const unsafe fn push_unchecked(&mut self, ch: char) {
let char_len = ch.len_utf8();
let char_bytes = char_to_bytes_utf8(ch);
let char_ptr = char_bytes.as_ptr();
let buf_ptr = self.as_mut_ptr().add(self.len);
ptr::copy_nonoverlapping(char_ptr, buf_ptr, char_len);
self.len += char_len;
}
pub const fn push(&mut self, ch: char) -> Result<(), ()> {
if ch.len_utf8() + self.len <= CAP {
unsafe { self.push_unchecked(ch) };
return Ok(());
}
Err(())
}
pub const unsafe fn push_str_unchecked(&mut self, s: &str) {
ptr::copy_nonoverlapping(s.as_ptr(), self.as_mut_ptr().add(self.len), s.len());
self.len += s.len();
}
pub const fn push_str(&mut self, s: &str) -> Result<(), usize> {
let truncating_len = utf8_truncator(s, self.extra_capacity());
unsafe { ptr::copy_nonoverlapping(s.as_ptr(), self.as_mut_ptr().add(self.len), truncating_len) };
self.len += truncating_len;
if truncating_len == s.len() {
return Ok(());
}
else {
return Err(truncating_len);
}
}
pub fn as_str(&self) -> &str {
unsafe { from_utf8_unchecked(self.as_bytes()) }
}
pub fn as_str_mut(&mut self) -> &mut str {
unsafe { from_utf8_unchecked_mut(self.as_mut_bytes()) }
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.buffer[..self.len]
}
pub fn as_mut_bytes(&mut self) -> &mut [u8] {
&mut self.buffer[..self.len]
}
pub const fn into_raw_buffer(self) -> [u8; CAP] {
self.buffer
}
#[inline]
pub const fn clear(&mut self) {
self.len = 0;
if CAP > 0 {
self.buffer[0] = b'\0';
}
}
pub fn truncate(&mut self, char_idx : usize) {
if char_idx > self.len() { return; }
let mut byte_idx = 0;
for (idx, ch) in self.chars().enumerate() {
if idx == char_idx {
break;
}
byte_idx += ch.len_utf8();
}
unsafe { self.as_mut_ptr().add(byte_idx).write(0) };
self.len = byte_idx;
}
}
impl<const CAP: usize> Default for MicroStr<CAP> {
fn default() -> Self {
Self::new()
}
}
impl<const A: usize, const B: usize> PartialEq<MicroStr<B>> for MicroStr<A> {
fn eq(&self, other: &MicroStr<B>) -> bool {
self.as_str() == other.as_str()
}
fn ne(&self, other: &MicroStr<B>) -> bool {
self.as_str() != other.as_str()
}
}
impl<const CAP: usize> Deref for MicroStr<CAP> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl<const CAP: usize> DerefMut for MicroStr<CAP> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_str_mut()
}
}
impl<const CAP: usize> fmt::Write for MicroStr<CAP> {
fn write_char(&mut self, c: char) -> fmt::Result {
self.push(c).map_err(|_| fmt::Error)
}
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
self.push_str(args.as_str().ok_or(fmt::Error)?).map_err(|_| fmt::Error)
}
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s).map_err(|_| fmt::Error)
}
}
const fn utf8_truncator(s: &str, idx : usize) -> usize {
if idx >= s.len() { return s.len(); }
let bytes = s.as_bytes();
let mut i = idx;
while i > idx.saturating_sub(4) {
if !is_utf8_continuation(bytes[i]) {
break;
}
i -= 1;
}
return i;
}
#[inline(always)]
const fn is_utf8_continuation(byte : u8) -> bool {
byte & 0b1100_0000 == 0b1000_0000
}
#[inline(always)]
const fn const_min(a : usize, b : usize) -> usize {
if a <= b {
a
} else {
b
}
}
#[inline]
const fn char_to_bytes_utf8(ch: char) -> [u8; 4] {
let mut result = [0; 4];
ch.encode_utf8(&mut result);
result
}