From 3faca18dc64a702f98356f6a062173bbbcc86c5c Mon Sep 17 00:00:00 2001 From: Juliette Cordor Date: Sun, 28 Jul 2024 15:24:12 +1000 Subject: [PATCH] add sized string struct and macro --- Cargo.toml | 3 +- quork-proc/src/lib.rs | 13 ++++++ quork-proc/src/sized_string.rs | 27 ++++++++++++ src/lib.rs | 3 ++ src/sized_string.rs | 79 ++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 quork-proc/src/sized_string.rs create mode 100644 src/sized_string.rs diff --git a/Cargo.toml b/Cargo.toml index 2bad8b7..efb2e39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,11 +38,12 @@ windows = { version = "0.56", features = [ nix = { version = "0.28", features = ["user"] } [features] -all = ["macros", "network", "root", "std", "traits"] +all = ["macros", "network", "root", "std", "traits", "sized_string"] default = ["all"] macros = ["quork-proc"] network = [] root = ["std"] +sized_string = [] std = [] traits = [] diff --git a/quork-proc/src/lib.rs b/quork-proc/src/lib.rs index d80cb5b..cba8404 100644 --- a/quork-proc/src/lib.rs +++ b/quork-proc/src/lib.rs @@ -10,6 +10,7 @@ mod const_str; mod enum_list; mod from_tuple; mod new; +mod sized_string; mod time_fn; mod trim_lines; @@ -122,3 +123,15 @@ pub fn lstrip_lines(input: proc_macro::TokenStream) -> proc_macro::TokenStream { trim_lines::trim_lines(&literal, &trim_lines::Alignment::Left).into() } + +/// Creates a [`SizedString`] from a string literal +/// +/// # Examples +/// +/// ```rust +/// let s = sized_string!("Hello, World!"); +/// ``` +#[proc_macro] +pub fn sized_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + sized_string::sized_string(&syn::parse_macro_input!(input as LitStr)).into() +} diff --git a/quork-proc/src/sized_string.rs b/quork-proc/src/sized_string.rs new file mode 100644 index 0000000..fd5e641 --- /dev/null +++ b/quork-proc/src/sized_string.rs @@ -0,0 +1,27 @@ +use proc_macro2::Span; +use proc_macro_crate::FoundCrate; +use syn::Ident; + +pub fn sized_string(input: &syn::LitStr) -> proc_macro2::TokenStream { + let length = input.value().chars().count(); + let chars = input + .value() + .chars() + .map(|c| quote::quote! { #c as u8 }) + .collect::>(); + + let quork_crate = + match proc_macro_crate::crate_name("quork").expect("quork is present in `Cargo.toml`") { + FoundCrate::Itself => quote::quote! { crate }, + FoundCrate::Name(name) => { + let ident = Ident::new(&name, Span::call_site()); + quote::quote! { #ident } + } + }; + + let output = quote::quote! { + #quork_crate::sized_string::SizedString::<#length>::new([#(#chars),*]) + }; + + output +} diff --git a/src/lib.rs b/src/lib.rs index b1a3e65..4798454 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,9 @@ pub mod traits; #[cfg(feature = "network")] pub mod network; +#[cfg(feature = "sized_string")] +pub mod sized_string; + cfg_if::cfg_if! { if #[cfg(all(feature = "root", feature = "std"))] { pub mod root; diff --git a/src/sized_string.rs b/src/sized_string.rs new file mode 100644 index 0000000..1b073f9 --- /dev/null +++ b/src/sized_string.rs @@ -0,0 +1,79 @@ +//! A sized, stack allocated string type. +//! +//! This is useful for when you need a string to be stack allocated, but you also need it to be sized (i.e not a reference to a [`str`]). +//! +//! Especially when using shared memory this can be useful as the actual string will be stored in shared memory, rather than just the pointer to the string. + +use std::ops::Deref; + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +/// A sized, stack allocated string type. +/// +/// This is useful for when you need a string to be stack allocated, but you also need it to be sized (i.e not a reference to a [`str`]). +/// +/// Especially when using shared memory this can be useful as the actual string will be stored in shared memory, rather than just the pointer to the string. +pub struct SizedString([u8; N]); + +impl SizedString { + #[must_use] + /// Construct a new [`SizedString`] from a byte array + pub const fn new(bytes: [u8; N]) -> Self { + Self(bytes) + } + + #[must_use] + /// Get the string as a [`str`] + pub const fn as_str(&self) -> &str { + unsafe { std::str::from_utf8_unchecked(&self.0) } + } +} + +impl Deref for SizedString { + type Target = str; + + fn deref(&self) -> &Self::Target { + unsafe { std::str::from_utf8_unchecked(&self.0) } + } +} + +impl AsRef for SizedString { + fn as_ref(&self) -> &str { + self + } +} + +impl AsRef for SizedString +where + str: std::convert::AsRef, +{ + fn as_ref(&self) -> &T { + let s: &str = self.as_ref(); + s.as_ref() + } +} + +mod string_trait_impls { + use std::fmt::Debug; + + use super::SizedString; + + impl Debug for SizedString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("SizedString").field(&self.as_str()).finish() + } + } +} + +#[cfg(test)] +mod tests { + + #[test] + fn test_sized_string() { + let s = quork_proc::sized_string!("hello world"); + + let s_str: &str = s.as_ref(); + + assert_eq!(s.len(), 11); + assert_eq!(s_str, "hello world"); + } +}