Skip to content

Commit 3c9d948

Browse files
committed
cstr[ing]16: convenience functions
1 parent 451d52b commit 3c9d948

File tree

4 files changed

+196
-8
lines changed

4 files changed

+196
-8
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@
66

77
- There is a new `fs` module that provides a high-level API for file-system
88
access. The API is close to the `std::fs` module.
9+
- Multiple convenience methods for `CString16` and `CStr16`, including:
10+
- `CStr16::as_slice()`
11+
- `CStr16::num_chars()`
12+
- `CStr16::is_empty()`
13+
- `CString16::new()`
14+
- `CString16::is_empty()`
15+
- `CString16::num_chars()`
16+
- `CString16::replace_char()`
17+
- `CString16::push()`
18+
- `CString16::push_str()`
19+
- `From<&CStr16>` for `CString16`
20+
- `From<&CStr16>` for `String`
21+
- `From<&CString16>` for `String`
922

1023
### Changed
1124

@@ -16,6 +29,7 @@
1629
- `Error::new` and `Error::from` now panic if the status is `SUCCESS`.
1730
- `Image::get_image_file_system` now returns a `fs::FileSystem` instead of the
1831
protocol.
32+
- `CString16::default` now always contains a null byte.
1933

2034
## uefi-macros - [Unreleased]
2135

uefi/src/data_types/chars.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ pub const NUL_8: Char8 = Char8(0);
6565
#[repr(transparent)]
6666
pub struct Char16(u16);
6767

68+
impl Char16 {
69+
/// Creates a UCS-2 character from a Rust character without checks.
70+
///
71+
/// # Safety
72+
/// The caller must be sure that the character is valid.
73+
#[must_use]
74+
pub const unsafe fn from_u16_unchecked(val: u16) -> Self {
75+
Self(val)
76+
}
77+
}
78+
6879
impl TryFrom<char> for Char16 {
6980
type Error = CharConversionError;
7081

@@ -125,4 +136,4 @@ impl fmt::Display for Char16 {
125136
}
126137

127138
/// UCS-2 version of the NUL character
128-
pub const NUL_16: Char16 = Char16(0);
139+
pub const NUL_16: Char16 = unsafe { Char16::from_u16_unchecked(0) };

uefi/src/data_types/owned_strs.rs

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use crate::data_types::strs::EqStrUntilNul;
44
use crate::data_types::UnalignedSlice;
55
use crate::polyfill::vec_into_raw_parts;
66
use alloc::borrow::{Borrow, ToOwned};
7+
use alloc::string::String;
8+
use alloc::vec;
79
use alloc::vec::Vec;
810
use core::{fmt, ops};
911

@@ -47,9 +49,73 @@ impl core::error::Error for FromStrError {}
4749
/// let s = CString16::try_from("abc").unwrap();
4850
/// assert_eq!(s.to_string(), "abc");
4951
/// ```
50-
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
52+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
5153
pub struct CString16(Vec<Char16>);
5254

55+
impl CString16 {
56+
/// Creates a new empty string with a null-byte.
57+
#[must_use]
58+
pub fn new() -> Self {
59+
Self(vec![NUL_16])
60+
}
61+
62+
/// Inserts a character at the end of the string, right before the null
63+
/// character.
64+
///
65+
/// # Panics
66+
/// Panics if the char is a null character.
67+
pub fn push(&mut self, char: Char16) {
68+
assert_ne!(char, NUL_16, "Pushing a null-character is illegal");
69+
let last_elem = self
70+
.0
71+
.last_mut()
72+
.expect("There should be at least a null character");
73+
*last_elem = char;
74+
self.0.push(NUL_16);
75+
}
76+
77+
/// Extends the string with the given [`CStr16`]. The null character is
78+
/// automatically kept at the end.
79+
pub fn push_str(&mut self, str: &CStr16) {
80+
str.as_slice()
81+
.iter()
82+
.copied()
83+
.for_each(|char| self.push(char));
84+
}
85+
86+
/// Replaces all chars in the string with the replace value in-place.
87+
pub fn replace_char(&mut self, search: Char16, replace: Char16) {
88+
assert_ne!(search, NUL_16, "Replacing a null character is illegal");
89+
assert_ne!(
90+
replace, NUL_16,
91+
"Replacing with a null character is illegal"
92+
);
93+
self.0
94+
.as_mut_slice()
95+
.iter_mut()
96+
.filter(|char| **char == search)
97+
.for_each(|char| *char = replace);
98+
}
99+
100+
/// Returns the number of characters without the trailing null character.
101+
#[must_use]
102+
pub fn num_chars(&self) -> usize {
103+
self.0.len() - 1
104+
}
105+
106+
/// Returns if the string is empty. This ignores the null character.
107+
#[must_use]
108+
pub fn is_empty(&self) -> bool {
109+
self.num_chars() == 0
110+
}
111+
}
112+
113+
impl Default for CString16 {
114+
fn default() -> Self {
115+
CString16::new()
116+
}
117+
}
118+
53119
impl TryFrom<&str> for CString16 {
54120
type Error = FromStrError;
55121

@@ -112,6 +178,20 @@ impl<'a> TryFrom<&UnalignedSlice<'a, u16>> for CString16 {
112178
}
113179
}
114180

181+
impl From<&CStr16> for CString16 {
182+
fn from(value: &CStr16) -> Self {
183+
let vec = value.as_slice_with_nul().to_vec();
184+
Self(vec)
185+
}
186+
}
187+
188+
impl From<&CString16> for String {
189+
fn from(value: &CString16) -> Self {
190+
let slice: &CStr16 = value.as_ref();
191+
String::from(slice)
192+
}
193+
}
194+
115195
impl<'a> UnalignedSlice<'a, u16> {
116196
/// Copies `self` to a new [`CString16`].
117197
pub fn to_cstring16(&self) -> Result<CString16, FromSliceWithNulError> {
@@ -256,4 +336,45 @@ mod tests {
256336
]
257337
);
258338
}
339+
340+
/// This tests the following UCS-2 string functions:
341+
/// - runtime constructor
342+
/// - len()
343+
/// - push() / push_str()
344+
/// - to rust string
345+
#[test]
346+
fn test_push_str() {
347+
let mut str1 = CString16::new();
348+
assert_eq!(str1.num_bytes(), 2, "Should have null-byte");
349+
assert_eq!(str1.num_chars(), 0);
350+
str1.push(Char16::try_from('h').unwrap());
351+
str1.push(Char16::try_from('i').unwrap());
352+
assert_eq!(str1.num_chars(), 2);
353+
354+
let mut str2 = CString16::new();
355+
str2.push(Char16::try_from('!').unwrap());
356+
357+
str2.push_str(str1.as_ref());
358+
assert_eq!(str2.num_chars(), 3);
359+
360+
let rust_str = String::from(&str2);
361+
assert_eq!(rust_str, "!hi");
362+
}
363+
364+
#[test]
365+
#[should_panic]
366+
fn test_push_str_panic() {
367+
CString16::new().push(NUL_16);
368+
}
369+
370+
#[test]
371+
fn test_char_replace_all_in_place() {
372+
let mut input = CString16::try_from("foo/bar/foobar//").unwrap();
373+
let search = Char16::try_from('/').unwrap();
374+
let replace = Char16::try_from('\\').unwrap();
375+
input.replace_char(search, replace);
376+
377+
let input = String::from(&input);
378+
assert_eq!(input, "foo\\bar\\foobar\\\\")
379+
}
259380
}

uefi/src/data_types/strs.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -309,26 +309,32 @@ impl CStr16 {
309309
})
310310
}
311311

312-
/// Returns the inner pointer to this C string
312+
/// Returns the inner pointer to this C16 string.
313313
#[must_use]
314314
pub const fn as_ptr(&self) -> *const Char16 {
315315
self.0.as_ptr()
316316
}
317317

318-
/// Get the underlying [`Char16`] slice, including the trailing null.
318+
/// Get the underlying [`Char16`]s as slice without the trailing null.
319+
#[must_use]
320+
pub fn as_slice(&self) -> &[Char16] {
321+
&self.0[..self.num_chars()]
322+
}
323+
324+
/// Get the underlying [`Char16`]s as slice including the trailing null.
319325
#[must_use]
320326
pub const fn as_slice_with_nul(&self) -> &[Char16] {
321327
&self.0
322328
}
323329

324-
/// Converts this C string to a u16 slice
330+
/// Converts this C string to a u16 slice without the trailing null.
325331
#[must_use]
326332
pub fn to_u16_slice(&self) -> &[u16] {
327333
let chars = self.to_u16_slice_with_nul();
328334
&chars[..chars.len() - 1]
329335
}
330336

331-
/// Converts this C string to a u16 slice containing the trailing 0 char
337+
/// Converts this C string to a u16 slice containing the trailing null.
332338
#[must_use]
333339
pub const fn to_u16_slice_with_nul(&self) -> &[u16] {
334340
unsafe { &*(&self.0 as *const [Char16] as *const [u16]) }
@@ -343,7 +349,19 @@ impl CStr16 {
343349
}
344350
}
345351

346-
/// Get the number of bytes in the string (including the trailing null character).
352+
/// Returns the number of characters without the trailing null. character
353+
#[must_use]
354+
pub const fn num_chars(&self) -> usize {
355+
self.0.len() - 1
356+
}
357+
358+
/// Returns if the string is empty. This ignores the null character.
359+
#[must_use]
360+
pub fn is_empty(&self) -> bool {
361+
self.num_chars() == 0
362+
}
363+
364+
/// Get the number of bytes in the string (including the trailing null).
347365
#[must_use]
348366
pub const fn num_bytes(&self) -> usize {
349367
self.0.len() * 2
@@ -373,6 +391,20 @@ impl CStr16 {
373391
}
374392
}
375393

394+
#[cfg(feature = "alloc")]
395+
impl From<&CStr16> for alloc::string::String {
396+
fn from(value: &CStr16) -> Self {
397+
value
398+
.as_slice()
399+
.iter()
400+
.copied()
401+
.map(u16::from)
402+
.map(|int| int as u32)
403+
.map(|int| char::from_u32(int).expect("Should be encodable as UTF-8"))
404+
.collect::<alloc::string::String>()
405+
}
406+
}
407+
376408
impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr16 {
377409
fn eq_str_until_nul(&self, other: &StrType) -> bool {
378410
let other = other.as_ref();
@@ -391,7 +423,7 @@ impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr16 {
391423
}
392424
}
393425

394-
/// An iterator over `CStr16`.
426+
/// An iterator over the [`Char16`]s in a [`CStr16`].
395427
#[derive(Debug)]
396428
pub struct CStr16Iter<'a> {
397429
inner: &'a CStr16,
@@ -575,6 +607,16 @@ mod tests {
575607
);
576608
}
577609

610+
#[test]
611+
fn test_cstr16_as_slice() {
612+
let string: &CStr16 = cstr16!("a");
613+
assert_eq!(string.as_slice(), &[Char16::try_from('a').unwrap()]);
614+
assert_eq!(
615+
string.as_slice_with_nul(),
616+
&[Char16::try_from('a').unwrap(), NUL_16]
617+
);
618+
}
619+
578620
// Code generation helper for the compare tests of our CStrX types against "str" and "String"
579621
// from the standard library.
580622
#[allow(non_snake_case)]

0 commit comments

Comments
 (0)