From 6ee3d8ce1f491609c91764e55105f93b370b538f Mon Sep 17 00:00:00 2001 From: Orion Kindel Date: Mon, 24 Apr 2023 18:01:44 -0500 Subject: [PATCH] feat: add many String inherent methods and functionality (#332) --- Cargo.lock | 17 +-- toad-string/Cargo.toml | 5 +- toad-string/src/lib.rs | 309 ++++++++++++++++++++++++++++++++++++++++- toad/src/step/retry.rs | 2 +- 4 files changed, 316 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63889e41..3385f139 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -984,11 +984,10 @@ checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" [[package]] name = "toad" -version = "0.17.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbf25e4cf7c7d65cc0dacef72ad68f9530f2af3b58a8bd2a21fb436a70fbb55" +version = "0.17.7" dependencies = [ "embedded-time", + "lazycell", "log", "naan", "nb", @@ -998,7 +997,9 @@ dependencies = [ "rand", "rand_chacha", "serde", + "serde-json-core", "serde_json", + "simple_logger", "tinyvec", "toad-array 0.2.3", "toad-hash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1013,9 +1014,10 @@ dependencies = [ [[package]] name = "toad" version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e101d750c3f88a11895f3dbfe1e06b3bb65a0e65d44529670d19bd18891b2e75" dependencies = [ "embedded-time", - "lazycell", "log", "naan", "nb", @@ -1025,9 +1027,7 @@ dependencies = [ "rand", "rand_chacha", "serde", - "serde-json-core", "serde_json", - "simple_logger", "tinyvec", "toad-array 0.2.3", "toad-hash 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1103,7 +1103,7 @@ dependencies = [ [[package]] name = "toad-jni" -version = "0.15.0" +version = "0.16.0" dependencies = [ "embedded-time", "jni", @@ -1111,7 +1111,7 @@ dependencies = [ "nb", "no-std-net", "tinyvec", - "toad 0.17.6", + "toad 0.17.7 (registry+https://github.com/rust-lang/crates.io-index)", "toad-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "toad-len 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "toad-msg 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1223,6 +1223,7 @@ version = "0.1.0" dependencies = [ "tinyvec", "toad-array 0.2.3", + "toad-len 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "toad-writable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/toad-string/Cargo.toml b/toad-string/Cargo.toml index 9314b9dd..006470af 100644 --- a/toad-string/Cargo.toml +++ b/toad-string/Cargo.toml @@ -16,12 +16,13 @@ maintenance = { status = "actively-developed" } [features] default = ["std"] -std = ["alloc", "toad-array/std", "toad-writable/std"] -alloc = ["toad-array/alloc", "toad-writable/alloc"] +std = ["alloc", "toad-array/std", "toad-len/std", "toad-writable/std"] +alloc = ["toad-array/alloc", "toad-len/alloc", "toad-writable/alloc"] test = [] docs = [] [dependencies] +toad-len = { version = "0.1.3", default_features = false } toad-array = { version = "0.2.3", default_features = false } toad-writable = { version = "0.1.1", default_features = false } tinyvec = {version = "1.5", default_features = false, features = ["rustc_1_55"]} diff --git a/toad-string/src/lib.rs b/toad-string/src/lib.rs index af9a3a2a..51aedc46 100644 --- a/toad-string/src/lib.rs +++ b/toad-string/src/lib.rs @@ -23,27 +23,58 @@ #[cfg(feature = "alloc")] extern crate alloc as std_alloc; -use core::fmt::Display; +use core::fmt::{Display, Write}; +use core::ops::{Deref, DerefMut}; use tinyvec::ArrayVec; +use toad_array::AppendCopy; +use toad_len::Len; use toad_writable::Writable; -/// Stack-allocated mutable string with capacity of 1KB +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Default)] +pub struct FromUtf8Error; + +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Default)] +pub struct FromUtf16Error; + +/// [`String`]-returning copy of [`std::format`] /// /// ``` -/// use toad_string::String; -/// -/// assert_eq!(String::<16>::from("ron stampler").as_str(), "ron stampler") +/// use toad_string::{format, String}; +/// assert_eq!(format!(32, "hello, {}!", String::<5>::from("jason")), +/// String::<32>::from("hello, jason!")); /// ``` +#[macro_export] +macro_rules! format { + ($cap:literal, $($arg:tt)*) => { + $crate::String::<$cap>::fmt(format_args!($($arg)*)) + }; +} + +/// Stack-allocated UTF-8 string with a fixed capacity. +/// +/// Has many of the same inherent functions as [`std::string::String`]. #[derive(Debug, Copy, Clone, Default)] pub struct String(Writable>); impl String { - /// Alias for [`AsRef`] + /// Creates a new string with the specified capacity + pub fn new() -> Self { + Default::default() + } + + /// Gets a string slice containing the entire [`String`] pub fn as_str(&self) -> &str { self.as_ref() } + /// Convert the [`String`] to a mutable string slice + pub fn as_mut_str(&mut self) -> &mut str { + self.as_mut() + } + /// Resize the String to a new length /// /// If `M` is less than `N`, the extra bytes are @@ -63,6 +94,229 @@ impl String { pub fn as_writable(&mut self) -> &mut Writable> { &mut self.0 } + + /// Creates a [`String`] using the output of [`format_args`] + pub fn fmt(args: core::fmt::Arguments) -> Self { + let mut s = Self::new(); + s.write_fmt(args).ok(); + s + } + + /// Returns this [`String`]'s capacity, in bytes. + pub fn capacity(&self) -> usize { + N + } + + /// Truncates this [`String`], removing all contents. + pub fn clear(&mut self) { + self.0.clear() + } + + /// Copy a slice of bytes to a `String`. + /// + /// A string ([`String`]) is made of bytes ([`u8`]), and a vector of bytes + /// ([`Vec`]) is made of bytes, so this function converts between the + /// two. Not all byte slices are valid `String`s, however: `String` + /// requires that it is valid UTF-8. `from_utf8()` checks to ensure that + /// the bytes are valid UTF-8, and then does the conversion. + /// + /// # Errors + /// + /// Returns [`Err`] if the slice is not UTF-8 with a description as to why the + /// provided bytes are not UTF-8. The vector you moved in is also included. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use toad_string::String; + /// + /// // some bytes, in a vector + /// let sparkle_heart = vec![240, 159, 146, 150]; + /// + /// // We know these bytes are valid, so we'll use `unwrap()`. + /// let sparkle_heart = String::<16>::from_utf8(&sparkle_heart).unwrap(); + /// + /// assert_eq!("💖", sparkle_heart); + /// ``` + /// + /// Incorrect bytes: + /// + /// ``` + /// use toad_string::String; + /// + /// // some invalid bytes, in a vector + /// let sparkle_heart = vec![0, 159, 146, 150]; + /// + /// assert!(String::<16>::from_utf8(&sparkle_heart).is_err()); + /// ``` + /// + /// [`Vec`]: std::vec::Vec "Vec" + /// [`&str`]: prim@str "&str" + #[inline] + pub fn from_utf8(bytes: &[u8]) -> Result { + match core::str::from_utf8(bytes) { + | Ok(s) => Ok(Self::from(s)), + | Err(_) => Err(FromUtf8Error), + } + } + + /// Decode a UTF-16–encoded vector `v` into a `String`, returning [`Err`] + /// if `v` contains any invalid data. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use toad_string::String; + /// + /// // 𝄞music + /// let v = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; + /// assert_eq!(String::<16>::from("𝄞music"), + /// String::<16>::from_utf16(v).unwrap()); + /// + /// // 𝄞muic + /// let v = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063]; + /// assert!(String::<16>::from_utf16(v).is_err()); + /// ``` + pub fn from_utf16(v: &[u16]) -> Result { + let mut ret = String::new(); + for c in char::decode_utf16(v.iter().cloned()) { + if let Ok(c) = c { + ret.push(c); + } else { + return Err(FromUtf16Error); + } + } + Ok(ret) + } + + /// Inserts a string slice into this `String` at a byte position. + /// + /// This is an *O*(*n*) operation as it requires copying every element in the + /// buffer. + /// + /// # Panics + /// + /// Panics if `idx` is larger than the `String`'s length, or if it does not + /// lie on a [`char`] boundary. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use toad_string::String; + /// + /// let mut s = String::<16>::from("bar"); + /// + /// s.insert_str(0, "foo"); + /// + /// assert_eq!("foobar", s); + /// ``` + #[inline] + pub fn insert_str(&mut self, idx: usize, string: &str) { + assert!(self.is_char_boundary(idx)); + + for (i, b) in string.bytes().enumerate() { + self.0.insert(idx + i, b); + } + } + + /// Inserts a character into this `String` at a byte position. + /// + /// This is an *O*(*n*) operation as it requires copying every element in the + /// buffer. + /// + /// # Panics + /// + /// Panics if `idx` is larger than the `String`'s length, or if it does not + /// lie on a [`char`] boundary. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use toad_string::String; + /// + /// let mut s = String::<16>::new(); + /// + /// s.insert(0, 'f'); + /// s.insert(1, 'o'); + /// s.insert(2, 'o'); + /// + /// assert_eq!("foo", s); + /// ``` + #[inline] + pub fn insert(&mut self, idx: usize, ch: char) { + assert!(self.is_char_boundary(idx)); + let mut bits = [0; 4]; + let bits = ch.encode_utf8(&mut bits).as_bytes(); + + for (i, b) in bits.iter().enumerate() { + self.0.insert(idx + i, *b); + } + } + + /// Appends the given [`char`] to the end of this `String`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use toad_string::String; + /// + /// let mut s = String::<16>::from("abc"); + /// + /// s.push('1'); + /// s.push('2'); + /// s.push('3'); + /// + /// assert_eq!("abc123", s); + /// ``` + pub fn push(&mut self, ch: char) { + match ch.len_utf8() { + | 1 => self.0.push(ch as u8), + | _ => self.0 + .extend_from_slice(ch.encode_utf8(&mut [0; 4]).as_bytes()), + } + } + + /// Appends a given string slice onto the end of this `String`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use toad_string::String; + /// + /// let mut s = String::<16>::from("foo"); + /// + /// s.push_str("bar"); + /// + /// assert_eq!("foobar", s); + /// ``` + #[inline] + pub fn push_str(&mut self, string: &str) { + self.0.append_copy(string.as_bytes()) + } +} + +impl Len for String { + const CAPACITY: Option = Some(N); + + fn len(&self) -> usize { + self.0.len() + } + + fn is_full(&self) -> bool { + self.0.is_full() + } } impl Display for String { @@ -94,14 +348,57 @@ impl<'a, const N: usize> From<&'a str> for String { } } +impl Deref for String { + type Target = str; + fn deref(&self) -> &str { + self.as_str() + } +} + +impl DerefMut for String { + fn deref_mut(&mut self) -> &mut str { + self.as_mut() + } +} + impl AsRef for String { fn as_ref(&self) -> &str { self.0.as_str() } } +impl AsMut for String { + fn as_mut(&mut self) -> &mut str { + core::str::from_utf8_mut(self.0.as_mut_slice()).unwrap() + } +} + impl AsRef<[u8]> for String { fn as_ref(&self) -> &[u8] { self.0.as_str().as_bytes() } } + +impl PartialEq<&str> for String { + fn eq(&self, other: &&str) -> bool { + self.as_str() == *other + } +} + +impl PartialEq for String { + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl PartialEq> for &str { + fn eq(&self, other: &String) -> bool { + *self == other.as_str() + } +} + +impl PartialEq<&String> for &str { + fn eq(&self, other: &&String) -> bool { + *self == other.as_str() + } +} diff --git a/toad/src/step/retry.rs b/toad/src/step/retry.rs index 679f88ce..92f3018b 100644 --- a/toad/src/step/retry.rs +++ b/toad/src/step/retry.rs @@ -18,7 +18,7 @@ pub trait Buf

where P: PlatformTypes, Self: Array, Addrd>)> { - /// Do some black box magic to send all messages that need to be sent + /// Send all messages that need to be sent fn attempt_all(&mut self, time: Instant, effects: &mut

::Effects)