Skip to content

Commit cfa9d8f

Browse files
phip1611nicholasbishop
authored andcommitted
cstr[ing]16: convenience functions
1 parent 739bd8e commit cfa9d8f

File tree

4 files changed

+224
-8
lines changed

4 files changed

+224
-8
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@
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::len()`
12+
- `CStr16::is_empty()`
13+
- `CStr16::char_replace_all_in_place()`
14+
- `CString16::new()`
15+
- `CString16::is_empty()`
16+
- `CString16::len()`
17+
- `CString16::char_replace_all_in_place()`
18+
- `CString16::push()`
19+
- `CString16::push_str()`
20+
- `From<&CStr16>` for `CString16`
21+
- `From<&CStr16>` for `String`
22+
- `From<&CString16>` for `String`
923

1024
### Changed
1125

@@ -16,6 +30,7 @@
1630
- `Error::new` and `Error::from` now panic if the status is `SUCCESS`.
1731
- `Image::get_image_file_system` now returns a `fs::FileSystem` instead of the
1832
protocol.
33+
- `CString16::default` now always contains a null byte.
1934

2035
## uefi-macros - [Unreleased]
2136

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: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ 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;
10+
use core::borrow::BorrowMut;
811
use core::{fmt, ops};
912

1013
/// Error returned by [`CString16::try_from::<&str>`].
@@ -47,9 +50,66 @@ impl core::error::Error for FromStrError {}
4750
/// let s = CString16::try_from("abc").unwrap();
4851
/// assert_eq!(s.to_string(), "abc");
4952
/// ```
50-
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
53+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
5154
pub struct CString16(Vec<Char16>);
5255

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

@@ -112,6 +172,20 @@ impl<'a> TryFrom<&UnalignedSlice<'a, u16>> for CString16 {
112172
}
113173
}
114174

175+
impl From<&CStr16> for CString16 {
176+
fn from(value: &CStr16) -> Self {
177+
let vec = value.as_slice_with_nul().to_vec();
178+
Self(vec)
179+
}
180+
}
181+
182+
impl From<&CString16> for String {
183+
fn from(value: &CString16) -> Self {
184+
let slice: &CStr16 = value.as_ref();
185+
String::from(slice)
186+
}
187+
}
188+
115189
impl<'a> UnalignedSlice<'a, u16> {
116190
/// Copies `self` to a new [`CString16`].
117191
pub fn to_cstring16(&self) -> Result<CString16, FromSliceWithNulError> {
@@ -127,6 +201,12 @@ impl ops::Deref for CString16 {
127201
}
128202
}
129203

204+
impl ops::DerefMut for CString16 {
205+
fn deref_mut(&mut self) -> &mut CStr16 {
206+
unsafe { &mut *(self.0.as_mut_slice() as *mut [Char16] as *mut CStr16) }
207+
}
208+
}
209+
130210
impl AsRef<CStr16> for CString16 {
131211
fn as_ref(&self) -> &CStr16 {
132212
self
@@ -139,6 +219,18 @@ impl Borrow<CStr16> for CString16 {
139219
}
140220
}
141221

222+
impl AsMut<CStr16> for CString16 {
223+
fn as_mut(&mut self) -> &mut CStr16 {
224+
self
225+
}
226+
}
227+
228+
impl BorrowMut<CStr16> for CString16 {
229+
fn borrow_mut(&mut self) -> &mut CStr16 {
230+
self
231+
}
232+
}
233+
142234
impl ToOwned for CStr16 {
143235
type Owned = CString16;
144236

@@ -256,4 +348,45 @@ mod tests {
256348
]
257349
);
258350
}
351+
352+
/// This tests the following UCS-2 string functions:
353+
/// - runtime constructor
354+
/// - len()
355+
/// - push() / push_str()
356+
/// - to rust string
357+
#[test]
358+
fn test_push_str() {
359+
let mut str1 = CString16::new();
360+
assert_eq!(str1.num_bytes(), 2, "Should have null-byte");
361+
assert_eq!(str1.len(), 0);
362+
str1.push(Char16::try_from('h').unwrap());
363+
str1.push(Char16::try_from('i').unwrap());
364+
assert_eq!(str1.len(), 2);
365+
366+
let mut str2 = CString16::new();
367+
str2.push(Char16::try_from('!').unwrap());
368+
369+
str2.push_str(str1.as_ref());
370+
assert_eq!(str2.len(), 3);
371+
372+
let rust_str = String::from(&str2);
373+
assert_eq!(rust_str, "!hi");
374+
}
375+
376+
#[test]
377+
#[should_panic]
378+
fn test_push_str_panic() {
379+
CString16::new().push(NUL_16);
380+
}
381+
382+
#[test]
383+
fn test_char_replace_all_in_place() {
384+
let mut input = CString16::try_from("foo/bar/foobar//").unwrap();
385+
let search = Char16::try_from('/').unwrap();
386+
let replace = Char16::try_from('\\').unwrap();
387+
input.char_replace_all_in_place(search, replace);
388+
389+
let input = String::from(&input);
390+
assert_eq!(input, "foo\\bar\\foobar\\\\")
391+
}
259392
}

uefi/src/data_types/strs.rs

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -309,26 +309,47 @@ 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.len()]
322+
}
323+
324+
/// Get the underlying [`Char16`]s as mut slice without the trailing null.
325+
#[must_use]
326+
fn as_slice_mut(&mut self) -> &mut [Char16] {
327+
let len = self.len();
328+
&mut self.0[..len]
329+
}
330+
331+
/// Replaces all chars in the string with the replace value.
332+
pub fn char_replace_all_in_place(&mut self, search: Char16, replace: Char16) {
333+
self.as_slice_mut()
334+
.iter_mut()
335+
.filter(|char| **char == search)
336+
.for_each(|char| *char = replace);
337+
}
338+
339+
/// Get the underlying [`Char16`]s as slice including the trailing null.
319340
#[must_use]
320341
pub const fn as_slice_with_nul(&self) -> &[Char16] {
321342
&self.0
322343
}
323344

324-
/// Converts this C string to a u16 slice
345+
/// Converts this C string to a u16 slice without the trailing null.
325346
#[must_use]
326347
pub fn to_u16_slice(&self) -> &[u16] {
327348
let chars = self.to_u16_slice_with_nul();
328349
&chars[..chars.len() - 1]
329350
}
330351

331-
/// Converts this C string to a u16 slice containing the trailing 0 char
352+
/// Converts this C string to a u16 slice containing the trailing null.
332353
#[must_use]
333354
pub const fn to_u16_slice_with_nul(&self) -> &[u16] {
334355
unsafe { &*(&self.0 as *const [Char16] as *const [u16]) }
@@ -343,7 +364,19 @@ impl CStr16 {
343364
}
344365
}
345366

346-
/// Get the number of bytes in the string (including the trailing null character).
367+
/// Returns the number of characters without the trailing null.
368+
#[must_use]
369+
pub const fn len(&self) -> usize {
370+
self.0.len() - 1
371+
}
372+
373+
/// Returns if the string is empty. This ignores the null byte.
374+
#[must_use]
375+
pub fn is_empty(&self) -> bool {
376+
self.len() == 0
377+
}
378+
379+
/// Get the number of bytes in the string (including the trailing null).
347380
#[must_use]
348381
pub const fn num_bytes(&self) -> usize {
349382
self.0.len() * 2
@@ -373,6 +406,20 @@ impl CStr16 {
373406
}
374407
}
375408

409+
#[cfg(feature = "alloc")]
410+
impl From<&CStr16> for alloc::string::String {
411+
fn from(value: &CStr16) -> Self {
412+
value
413+
.as_slice()
414+
.iter()
415+
.copied()
416+
.map(u16::from)
417+
.map(|int| int as u32)
418+
.map(|int| char::from_u32(int).expect("Should be encodable as UTF-8"))
419+
.collect::<alloc::string::String>()
420+
}
421+
}
422+
376423
impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr16 {
377424
fn eq_str_until_nul(&self, other: &StrType) -> bool {
378425
let other = other.as_ref();
@@ -391,7 +438,7 @@ impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr16 {
391438
}
392439
}
393440

394-
/// An iterator over `CStr16`.
441+
/// An iterator over the [`Char16`]s in a [`CStr16`].
395442
#[derive(Debug)]
396443
pub struct CStr16Iter<'a> {
397444
inner: &'a CStr16,
@@ -575,6 +622,16 @@ mod tests {
575622
);
576623
}
577624

625+
#[test]
626+
fn test_cstr16_as_slice() {
627+
let string: &CStr16 = cstr16!("a");
628+
assert_eq!(string.as_slice(), &[Char16::try_from('a').unwrap()]);
629+
assert_eq!(
630+
string.as_slice_with_nul(),
631+
&[Char16::try_from('a').unwrap(), NUL_16]
632+
);
633+
}
634+
578635
// Code generation helper for the compare tests of our CStrX types against "str" and "String"
579636
// from the standard library.
580637
#[allow(non_snake_case)]

0 commit comments

Comments
 (0)