Skip to content

Commit

Permalink
Implement String-like and str-like wrappers around FuriString
Browse files Browse the repository at this point in the history
  • Loading branch information
str4d committed Mar 4, 2023
1 parent 6c2ca6b commit bf91ccc
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 0 deletions.
1 change: 1 addition & 0 deletions crates/flipperzero/src/furi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod io;
pub mod message_queue;
pub mod string;
pub mod sync;
pub mod thread;

Expand Down
259 changes: 259 additions & 0 deletions crates/flipperzero/src/furi/string.rs
Original file line number Diff line number Diff line change
@@ -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<Str> for String {
fn eq(&self, other: &Str) -> bool {
self.as_str().eq(other)
}
}

impl PartialEq<String> for Str {
fn eq(&self, other: &String) -> bool {
self.eq(other.as_str())
}
}

impl PartialEq<CStr> for Str {
fn eq(&self, other: &CStr) -> bool {
unsafe { sys::furi_string_equal_str(self.raw(), other.as_ptr()) }
}
}

impl PartialEq<Str> for CStr {
fn eq(&self, other: &Str) -> bool {
other.eq(self)
}
}

impl PartialEq<CStr> for String {
fn eq(&self, other: &CStr) -> bool {
self.as_str().eq(other)
}
}

impl PartialEq<String> for CStr {
fn eq(&self, other: &String) -> bool {
other.as_str().eq(self)
}
}

#[cfg(feature = "alloc")]
impl PartialEq<CString> for Str {
fn eq(&self, other: &CString) -> bool {
self.eq(other.as_c_str())
}
}

#[cfg(feature = "alloc")]
impl PartialEq<Str> for CString {
fn eq(&self, other: &Str) -> bool {
other.eq(self.as_c_str())
}
}

#[cfg(feature = "alloc")]
impl PartialEq<CString> for String {
fn eq(&self, other: &CString) -> bool {
self.as_str().eq(other.as_c_str())
}
}

#[cfg(feature = "alloc")]
impl PartialEq<String> 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<Ordering> {
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(())
}
}

0 comments on commit bf91ccc

Please sign in to comment.