diff --git a/crates/flipperzero/src/furi/mod.rs b/crates/flipperzero/src/furi/mod.rs index a36119a8..9ccd1756 100644 --- a/crates/flipperzero/src/furi/mod.rs +++ b/crates/flipperzero/src/furi/mod.rs @@ -2,6 +2,7 @@ pub mod io; pub mod message_queue; +pub mod string; pub mod sync; pub mod thread; diff --git a/crates/flipperzero/src/furi/string.rs b/crates/flipperzero/src/furi/string.rs new file mode 100644 index 00000000..418692a8 --- /dev/null +++ b/crates/flipperzero/src/furi/string.rs @@ -0,0 +1,259 @@ +//! String primitives built around `FuriString`. + +use core::{ + cmp::Ordering, + convert::Infallible, + ffi::{c_char, CStr}, + fmt, ops, +}; + +#[cfg(feature = "alloc")] +use alloc::ffi::CString; + +use flipperzero_sys as sys; + +/// A Furi string. +/// +/// This is similar to Rust's [`String`], but used inside the Flipper Zero SDK. +#[derive(Eq)] +pub struct String(*mut sys::FuriString); + +impl Drop for String { + fn drop(&mut self) { + unsafe { sys::furi_string_free(self.0) }; + } +} + +// Implementations matching `std::string::String`. +impl String { + /// Creates a new empty `String`. + pub fn new() -> Self { + String(unsafe { sys::furi_string_alloc() }) + } + + /// Creates a new empty `String` with at least the specified capacity. + pub fn with_capacity(capacity: usize) -> Self { + let mut s = Self::new(); + s.reserve(capacity); + s + } + + /// Extracts a string slice containing the entire `String`. + #[inline] + #[must_use] + pub fn as_str(&self) -> &Str { + Str::from_raw(self.0) + } + + /// Appends a given `CStr` onto the end of this `String`. + pub fn push_cstr(&mut self, string: &CStr) { + unsafe { sys::furi_string_cat_str(self.0, string.as_ptr()) } + } + + /// Appends a given string slice onto the end of this `String`. + pub fn push_str(&mut self, string: &str) { + self.reserve(string.len()); + for ch in string.chars() { + self.push(ch); + } + } + + /// Reserves capacity for at least `additional` bytes more than the current length. + pub fn reserve(&mut self, additional: usize) { + unsafe { sys::furi_string_reserve(self.0, self.len() + additional) }; + } + + /// Appends the given [`char`] to the end of this `String`. + pub fn push(&mut self, ch: char) { + match ch.len_utf8() { + 1 => unsafe { sys::furi_string_push_back(self.0, ch as c_char) }, + _ => unsafe { sys::furi_string_utf8_push(self.0, ch as u32) }, + } + } + + /// Shortens this `String` to the specified length. + /// + /// If `new_len` is greater than the string's current length, this has no effect. + pub fn truncate(&mut self, new_len: usize) { + if new_len < self.len() { + unsafe { sys::furi_string_right(self.0, new_len) }; + } + } + + /// Truncates this `String`, removing all contents. + /// + /// While this means the `String` will have a length of zero, it does not touch its + /// capacity. + pub fn clear(&mut self) { + unsafe { sys::furi_string_reset(self.0) }; + } +} + +/// A Furi string reference. +/// +/// This is similar to Rust's [`str`], but backed by a `FuriString`. +pub struct Str(sys::FuriString); + +// Implementations matching `&str`. +impl Str { + #[inline] + fn from_raw<'s>(raw: *const sys::FuriString) -> &'s Self { + unsafe { (raw as *const Str).as_ref().unwrap() } + } + + #[inline] + fn raw(&self) -> *const sys::FuriString { + self as *const Str as *const sys::FuriString + } + + /// Returns the length of `self`. + /// + /// This length is in bytes, not [`char`]s or graphemes. In other words, it might not + /// be what a human considers the length of the string. + pub fn len(&self) -> usize { + unsafe { sys::furi_string_size(self.raw()) } + } + + /// Returns `true` if `self` has a length of zero bytes. + pub fn is_empty(&self) -> bool { + unsafe { sys::furi_string_empty(self.raw()) } + } + + /// Extracts a `CStr` containing the entire `String`. + #[must_use] + pub fn as_c_str(&self) -> &CStr { + unsafe { CStr::from_ptr(sys::furi_string_get_cstr(self.raw())) } + } +} + +impl Default for String { + fn default() -> Self { + Self::new() + } +} + +impl ops::Deref for String { + type Target = Str; + + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +impl PartialEq for Str { + fn eq(&self, other: &Str) -> bool { + unsafe { sys::furi_string_equal(self.raw(), other.raw()) } + } +} + +impl PartialEq for String { + fn eq(&self, other: &Self) -> bool { + self.as_str().eq(other.as_str()) + } +} + +impl PartialEq for String { + fn eq(&self, other: &Str) -> bool { + self.as_str().eq(other) + } +} + +impl PartialEq for Str { + fn eq(&self, other: &String) -> bool { + self.eq(other.as_str()) + } +} + +impl PartialEq for Str { + fn eq(&self, other: &CStr) -> bool { + unsafe { sys::furi_string_equal_str(self.raw(), other.as_ptr()) } + } +} + +impl PartialEq for CStr { + fn eq(&self, other: &Str) -> bool { + other.eq(self) + } +} + +impl PartialEq for String { + fn eq(&self, other: &CStr) -> bool { + self.as_str().eq(other) + } +} + +impl PartialEq for CStr { + fn eq(&self, other: &String) -> bool { + other.as_str().eq(self) + } +} + +#[cfg(feature = "alloc")] +impl PartialEq for Str { + fn eq(&self, other: &CString) -> bool { + self.eq(other.as_c_str()) + } +} + +#[cfg(feature = "alloc")] +impl PartialEq for CString { + fn eq(&self, other: &Str) -> bool { + other.eq(self.as_c_str()) + } +} + +#[cfg(feature = "alloc")] +impl PartialEq for String { + fn eq(&self, other: &CString) -> bool { + self.as_str().eq(other.as_c_str()) + } +} + +#[cfg(feature = "alloc")] +impl PartialEq for CString { + fn eq(&self, other: &String) -> bool { + other.as_str().eq(self.as_c_str()) + } +} + +impl Ord for String { + fn cmp(&self, other: &Self) -> Ordering { + match unsafe { sys::furi_string_cmp(self.0, other.0) } { + ..=-1 => Ordering::Less, + 0 => Ordering::Equal, + 1.. => Ordering::Greater, + } + } +} + +impl PartialOrd for String { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl fmt::Write for String { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.push_str(s); + Ok(()) + } + + fn write_char(&mut self, c: char) -> fmt::Result { + self.push(c); + Ok(()) + } +} + +impl ufmt::uWrite for String { + type Error = Infallible; + + fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { + self.push_str(s); + Ok(()) + } + + fn write_char(&mut self, c: char) -> Result<(), Self::Error> { + self.push(c); + Ok(()) + } +}