use crate::error::EfiError;
pub mod char16;
pub mod char8;
pub use char8::{Char8Array, Char8Str, Char8String};
pub use char16::{Char16Array, Char16Str, Char16String};
#[doc(hidden)]
pub use char8::latin1_capacity;
#[doc(hidden)]
pub use char16::ucs2_capacity;
#[allow(clippy::indexing_slicing)] const fn decode_utf8(bytes: &[u8], index: usize) -> (u32, usize) {
let b0 = bytes[index];
if b0 < 0x80 {
(b0 as u32, 1)
} else if b0 >> 5 == 0b110 {
let b1 = bytes[index + 1];
(((b0 as u32 & 0x1F) << 6) | (b1 as u32 & 0x3F), 2)
} else if b0 >> 4 == 0b1110 {
let b1 = bytes[index + 1];
let b2 = bytes[index + 2];
(((b0 as u32 & 0x0F) << 12) | ((b1 as u32 & 0x3F) << 6) | (b2 as u32 & 0x3F), 3)
} else {
let b1 = bytes[index + 1];
let b2 = bytes[index + 2];
let b3 = bytes[index + 3];
(((b0 as u32 & 0x07) << 18) | ((b1 as u32 & 0x3F) << 12) | ((b2 as u32 & 0x3F) << 6) | (b3 as u32 & 0x3F), 4)
}
}
const fn char_count(s: &str) -> usize {
let bytes = s.as_bytes();
let mut index = 0;
let mut count = 0;
while index < bytes.len() {
let (_, len) = decode_utf8(bytes, index);
index += len;
count += 1;
}
count
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StringError {
InteriorNul {
position: usize,
},
MissingNulTerminator,
OddByteLength {
len: usize,
},
NotUcs2 {
position: usize,
value: u32,
},
NotLatin1 {
position: usize,
value: u32,
},
TooLong {
capacity: usize,
required: usize,
},
}
impl core::fmt::Display for StringError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
StringError::InteriorNul { position } => {
write!(f, "interior NUL found at position {position}")
}
StringError::MissingNulTerminator => write!(f, "string is not NUL-terminated"),
StringError::OddByteLength { len } => {
write!(f, "byte buffer length {len} is not a multiple of two CHAR16 code units")
}
StringError::NotUcs2 { position, value } => {
write!(f, "code point U+{value:04X} at position {position} is not valid UCS-2")
}
StringError::NotLatin1 { position, value } => {
write!(f, "code point U+{value:04X} at position {position} is not valid Latin-1 (CHAR8)")
}
StringError::TooLong { capacity, required } => {
write!(f, "string requires {required} code units but the buffer capacity is {capacity}")
}
}
}
}
impl core::error::Error for StringError {}
impl From<StringError> for EfiError {
fn from(_: StringError) -> Self {
EfiError::InvalidParameter
}
}
#[macro_export]
macro_rules! char16 {
($s:literal) => {{
static CHAR16_LITERAL: $crate::base::string::Char16Array<{ $crate::base::string::ucs2_capacity($s) }> =
$crate::base::string::Char16Array::from_str($s);
CHAR16_LITERAL.as_char16_str()
}};
}
#[macro_export]
macro_rules! char8 {
($s:literal) => {{
static CHAR8_LITERAL: $crate::base::string::Char8Array<{ $crate::base::string::latin1_capacity($s) }> =
$crate::base::string::Char8Array::from_str($s);
CHAR8_LITERAL.as_char8_str()
}};
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn test_string_error_display() {
assert_eq!(StringError::InteriorNul { position: 3 }.to_string(), "interior NUL found at position 3");
assert_eq!(StringError::MissingNulTerminator.to_string(), "string is not NUL-terminated");
assert_eq!(
StringError::NotUcs2 { position: 1, value: 0x1F600 }.to_string(),
"code point U+1F600 at position 1 is not valid UCS-2"
);
assert_eq!(
StringError::NotLatin1 { position: 2, value: 0x0100 }.to_string(),
"code point U+0100 at position 2 is not valid Latin-1 (CHAR8)"
);
assert_eq!(
StringError::TooLong { capacity: 2, required: 4 }.to_string(),
"string requires 4 code units but the buffer capacity is 2"
);
}
#[test]
fn test_string_error_into_efi_error() {
let error: EfiError = StringError::MissingNulTerminator.into();
assert_eq!(error, EfiError::InvalidParameter);
}
#[test]
fn test_string_error_is_error_trait() {
fn assert_error<T: core::error::Error>(_: &T) {}
assert_error(&StringError::MissingNulTerminator);
}
}