use super::{StringError, char_count, decode_utf8};
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
use super::char8::Char8Array;
#[cfg(any(test, feature = "alloc"))]
use alloc::{string::String, vec::Vec};
#[cfg(any(test, feature = "alloc"))]
use super::char8::{Char8Str, Char8String};
const SURROGATE_RANGE: core::ops::RangeInclusive<u16> = 0xD800..=0xDFFF;
#[repr(transparent)]
pub struct Char16Str([u16]);
impl Char16Str {
pub const EMPTY: &'static Char16Str = unsafe { Self::from_units_with_nul_unchecked(&[0]) };
pub fn from_units_with_nul(units: &[u16]) -> Result<&Char16Str, StringError> {
let body = match units.split_last() {
Some((0, body)) => body,
_ => return Err(StringError::MissingNulTerminator),
};
for (position, &unit) in body.iter().enumerate() {
if unit == 0 {
return Err(StringError::InteriorNul { position });
}
if SURROGATE_RANGE.contains(&unit) {
return Err(StringError::NotUcs2 { position, value: unit as u32 });
}
}
Ok(unsafe { Self::from_units_with_nul_unchecked(units) })
}
pub const unsafe fn from_units_with_nul_unchecked(units: &[u16]) -> &Char16Str {
unsafe { &*(units as *const [u16] as *const Char16Str) }
}
#[allow(clippy::indexing_slicing)]
pub fn from_units_until_nul(units: &[u16]) -> Result<&Char16Str, StringError> {
let end = units.iter().position(|&unit| unit == 0).ok_or(StringError::MissingNulTerminator)?;
Self::from_units_with_nul(&units[..=end])
}
pub unsafe fn from_ptr<'a>(ptr: *const u16) -> Result<&'a Char16Str, StringError> {
let mut len = 0usize;
while unsafe { *ptr.add(len) } != 0 {
len += 1;
}
let units = unsafe { core::slice::from_raw_parts(ptr, len + 1) };
Self::from_units_with_nul(units)
}
pub unsafe fn from_ptr_bounded<'a>(ptr: *const u16, max_units: usize) -> Result<&'a Char16Str, StringError> {
let mut len = 0usize;
while len < max_units && unsafe { *ptr.add(len) } != 0 {
len += 1;
}
if len == max_units {
return Err(StringError::MissingNulTerminator);
}
let units = unsafe { core::slice::from_raw_parts(ptr, len + 1) };
Self::from_units_with_nul(units)
}
pub const fn as_ptr(&self) -> *const u16 {
self.0.as_ptr()
}
pub const fn as_units_with_nul(&self) -> &[u16] {
&self.0
}
pub fn as_units(&self) -> &[u16] {
match self.0.split_last() {
Some((_, body)) => body,
None => &[],
}
}
pub fn len(&self) -> usize {
self.as_units().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> core::slice::Iter<'_, u16> {
self.as_units().iter()
}
pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
self.iter().map(|&unit| char::from_u32(unit as u32).unwrap_or(char::REPLACEMENT_CHARACTER))
}
}
impl PartialEq for Char16Str {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for Char16Str {}
impl PartialOrd for Char16Str {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Char16Str {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl core::hash::Hash for Char16Str {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl PartialEq<str> for Char16Str {
fn eq(&self, other: &str) -> bool {
self.chars().eq(other.chars())
}
}
impl PartialEq<&str> for Char16Str {
fn eq(&self, other: &&str) -> bool {
self == *other
}
}
impl PartialEq<Char16Str> for str {
fn eq(&self, other: &Char16Str) -> bool {
other == self
}
}
impl core::fmt::Display for Char16Str {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for c in self.chars() {
write!(f, "{c}")?;
}
Ok(())
}
}
impl core::fmt::Debug for Char16Str {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("\"")?;
for c in self.chars() {
write!(f, "{}", c.escape_debug())?;
}
f.write_str("\"")
}
}
#[cfg(any(test, feature = "alloc"))]
fn decode_le_units(bytes: &[u8]) -> Result<Vec<u16>, StringError> {
if !bytes.len().is_multiple_of(2) {
return Err(StringError::OddByteLength { len: bytes.len() });
}
Ok(bytes
.chunks_exact(2)
.map(|pair| u16::from_le_bytes(pair.try_into().expect("chunks_exact(2) yields 2-byte slices")))
.collect())
}
#[cfg(any(test, feature = "alloc"))]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Char16String(Vec<u16>);
#[cfg(any(test, feature = "alloc"))]
impl Char16String {
pub fn new() -> Self {
Self(alloc::vec![0])
}
pub fn try_from_str(s: &str) -> Result<Self, StringError> {
let mut units = Vec::with_capacity(s.len() + 1);
for (position, c) in s.chars().enumerate() {
let value = c as u32;
if value == 0 {
return Err(StringError::InteriorNul { position });
}
if value > 0xFFFF {
return Err(StringError::NotUcs2 { position, value });
}
units.push(value as u16);
}
units.push(0);
Ok(Self(units))
}
pub fn from_units_with_nul(units: Vec<u16>) -> Result<Self, StringError> {
Char16Str::from_units_with_nul(&units)?;
Ok(Self(units))
}
pub fn from_le_bytes_with_nul(bytes: &[u8]) -> Result<Self, StringError> {
Self::from_units_with_nul(decode_le_units(bytes)?)
}
pub fn from_le_bytes_until_nul(bytes: &[u8]) -> Result<Self, StringError> {
use alloc::borrow::ToOwned;
let units = decode_le_units(bytes)?;
Ok(Char16Str::from_units_until_nul(&units)?.to_owned())
}
pub unsafe fn from_units_with_nul_unchecked(units: Vec<u16>) -> Self {
Self(units)
}
pub fn as_char16_str(&self) -> &Char16Str {
unsafe { Char16Str::from_units_with_nul_unchecked(&self.0) }
}
pub fn into_units_with_nul(self) -> Vec<u16> {
self.0
}
}
#[cfg(any(test, feature = "alloc"))]
impl Default for Char16String {
fn default() -> Self {
Self::new()
}
}
#[cfg(any(test, feature = "alloc"))]
impl core::ops::Deref for Char16String {
type Target = Char16Str;
fn deref(&self) -> &Char16Str {
self.as_char16_str()
}
}
#[cfg(any(test, feature = "alloc"))]
impl core::borrow::Borrow<Char16Str> for Char16String {
fn borrow(&self) -> &Char16Str {
self.as_char16_str()
}
}
#[cfg(any(test, feature = "alloc"))]
impl alloc::borrow::ToOwned for Char16Str {
type Owned = Char16String;
fn to_owned(&self) -> Char16String {
Char16String(self.0.to_vec())
}
}
#[cfg(any(test, feature = "alloc"))]
impl From<&Char16Str> for Char16String {
fn from(s: &Char16Str) -> Self {
use alloc::borrow::ToOwned;
s.to_owned()
}
}
#[cfg(any(test, feature = "alloc"))]
impl<'a> TryFrom<&'a str> for Char16String {
type Error = StringError;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
Self::try_from_str(s)
}
}
#[cfg(any(test, feature = "alloc"))]
impl From<&Char8Str> for Char16String {
fn from(value: &Char8Str) -> Self {
let mut units = Vec::with_capacity(value.len() + 1);
for &byte in value.iter() {
units.push(byte as u16);
}
units.push(0);
Self(units)
}
}
#[cfg(any(test, feature = "alloc"))]
impl From<&Char8String> for Char16String {
fn from(value: &Char8String) -> Self {
Self::from(value.as_char8_str())
}
}
#[cfg(any(test, feature = "alloc"))]
impl From<Char8String> for Char16String {
fn from(value: Char8String) -> Self {
Self::from(&value)
}
}
#[cfg(any(test, feature = "alloc"))]
impl core::fmt::Display for Char16String {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self.as_char16_str(), f)
}
}
#[cfg(any(test, feature = "alloc"))]
impl core::fmt::Debug for Char16String {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(self.as_char16_str(), f)
}
}
#[cfg(any(test, feature = "alloc"))]
impl PartialEq<str> for Char16String {
fn eq(&self, other: &str) -> bool {
self.as_char16_str() == other
}
}
#[cfg(any(test, feature = "alloc"))]
impl PartialEq<&str> for Char16String {
fn eq(&self, other: &&str) -> bool {
self.as_char16_str() == *other
}
}
#[cfg(any(test, feature = "alloc"))]
impl PartialEq<Char16String> for str {
fn eq(&self, other: &Char16String) -> bool {
other.as_char16_str() == self
}
}
#[cfg(any(test, feature = "alloc"))]
impl PartialEq<Char16Str> for Char16String {
fn eq(&self, other: &Char16Str) -> bool {
self.as_char16_str() == other
}
}
#[cfg(any(test, feature = "alloc"))]
impl PartialEq<&Char16Str> for Char16String {
fn eq(&self, other: &&Char16Str) -> bool {
self.as_char16_str() == *other
}
}
#[cfg(any(test, feature = "alloc"))]
impl PartialEq<Char16String> for Char16Str {
fn eq(&self, other: &Char16String) -> bool {
self == other.as_char16_str()
}
}
#[doc(hidden)]
pub const fn ucs2_capacity(s: &str) -> usize {
char_count(s) + 1
}
#[repr(transparent)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct Char16Array<const N: usize>([u16; N]);
impl<const N: usize> Char16Array<N> {
#[allow(clippy::indexing_slicing)]
pub const fn try_from_str(s: &str) -> Result<Self, StringError> {
let bytes = s.as_bytes();
let mut out = [0u16; N];
let mut index = 0;
let mut out_index = 0;
let mut position = 0;
while index < bytes.len() {
let (value, len) = decode_utf8(bytes, index);
if value == 0 {
return Err(StringError::InteriorNul { position });
}
if value > 0xFFFF {
return Err(StringError::NotUcs2 { position, value });
}
if out_index + 1 >= N {
return Err(StringError::TooLong { capacity: N.saturating_sub(1), required: out_index + 2 });
}
out[out_index] = value as u16;
out_index += 1;
index += len;
position += 1;
}
if out_index >= N {
return Err(StringError::TooLong { capacity: 0, required: 1 });
}
Ok(Self(out))
}
pub const fn from_str(s: &str) -> Self {
match Self::try_from_str(s) {
Ok(array) => array,
Err(_) => panic!("invalid UCS-2 string literal"),
}
}
#[allow(clippy::indexing_slicing)]
pub fn as_char16_str(&self) -> &Char16Str {
let mut len = 0;
while len < N && self.0[len] != 0 {
len += 1;
}
if len == N {
return Char16Str::EMPTY;
}
unsafe { Char16Str::from_units_with_nul_unchecked(&self.0[..=len]) }
}
pub const fn as_array(&self) -> &[u16; N] {
&self.0
}
}
impl<const N: usize> core::ops::Deref for Char16Array<N> {
type Target = Char16Str;
fn deref(&self) -> &Char16Str {
self.as_char16_str()
}
}
impl<const N: usize> core::fmt::Display for Char16Array<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self.as_char16_str(), f)
}
}
impl<const N: usize> Default for Char16Array<N> {
fn default() -> Self {
Self([0; N])
}
}
impl<'a, const N: usize> TryFrom<&'a str> for Char16Array<N> {
type Error = StringError;
fn try_from(s: &'a str) -> Result<Self, StringError> {
Self::try_from_str(s)
}
}
impl<const N: usize> From<Char8Array<N>> for Char16Array<N> {
#[allow(clippy::indexing_slicing)]
fn from(value: Char8Array<N>) -> Self {
let src = value.as_char8_str();
let mut out = [0u16; N];
for (position, &byte) in src.iter().enumerate() {
out[position] = byte as u16;
}
Self(out)
}
}
#[cfg(any(test, feature = "alloc"))]
impl<const N: usize> From<Char16Array<N>> for Char16String {
fn from(value: Char16Array<N>) -> Self {
use alloc::borrow::ToOwned;
value.as_char16_str().to_owned()
}
}
#[cfg(any(test, feature = "alloc"))]
impl<const N: usize> From<Char16Array<N>> for String {
fn from(value: Char16Array<N>) -> Self {
value.as_char16_str().chars().collect()
}
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
use super::*;
use crate::char16;
use alloc::string::ToString;
#[test]
fn test_char16_from_units_with_nul_valid() {
let units = [0x0045u16, 0x0046, 0x0049, 0x0000];
let s = Char16Str::from_units_with_nul(&units).unwrap();
assert_eq!(s.len(), 3);
assert!(!s.is_empty());
assert_eq!(s.as_units(), &[0x0045, 0x0046, 0x0049]);
assert_eq!(s.as_units_with_nul(), &units);
}
#[test]
fn test_char16_empty_string() {
let units = [0x0000u16];
let s = Char16Str::from_units_with_nul(&units).unwrap();
assert_eq!(s.len(), 0);
assert!(s.is_empty());
}
#[test]
fn test_char16_missing_nul_terminator() {
let units = [0x0045u16, 0x0046];
assert_eq!(Char16Str::from_units_with_nul(&units), Err(StringError::MissingNulTerminator));
assert_eq!(Char16Str::from_units_with_nul(&[]), Err(StringError::MissingNulTerminator));
}
#[test]
fn test_char16_interior_nul() {
let units = [0x0045u16, 0x0000, 0x0049, 0x0000];
assert_eq!(Char16Str::from_units_with_nul(&units), Err(StringError::InteriorNul { position: 1 }));
}
#[test]
fn test_char16_surrogate_rejected() {
let units = [0x0045u16, 0xD800, 0x0000];
assert_eq!(Char16Str::from_units_with_nul(&units), Err(StringError::NotUcs2 { position: 1, value: 0xD800 }));
}
#[test]
fn test_char16_chars_and_iter() {
let units = [0x0041u16, 0x00E9, 0x20AC, 0x0000]; let s = Char16Str::from_units_with_nul(&units).unwrap();
let chars: alloc::vec::Vec<char> = s.chars().collect();
assert_eq!(chars, ['A', 'é', '€']);
let code_units: alloc::vec::Vec<u16> = s.iter().copied().collect();
assert_eq!(code_units, [0x0041, 0x00E9, 0x20AC]);
}
#[test]
fn test_char16_chars_lossy_on_unchecked_surrogate() {
let units = [0x0041u16, 0xD800, 0x0000];
let s = unsafe { Char16Str::from_units_with_nul_unchecked(&units) };
let chars: alloc::vec::Vec<char> = s.chars().collect();
assert_eq!(chars, ['A', char::REPLACEMENT_CHARACTER]);
}
#[test]
fn test_char16_from_ptr() {
let units = [0x0045u16, 0x0046, 0x0049, 0x0000];
let s = unsafe { Char16Str::from_ptr(units.as_ptr()) }.unwrap();
assert!(s == "EFI");
}
#[test]
fn test_char16_from_ptr_bounded_finds_nul() {
let units = [0x0045u16, 0x0046, 0x0000, 0x0049];
let s = unsafe { Char16Str::from_ptr_bounded(units.as_ptr(), 4) }.unwrap();
assert_eq!(s.len(), 2);
}
#[test]
fn test_char16_from_ptr_bounded_no_nul() {
let units = [0x0045u16, 0x0046, 0x0049];
let result = unsafe { Char16Str::from_ptr_bounded(units.as_ptr(), 3) };
assert_eq!(result, Err(StringError::MissingNulTerminator));
}
#[test]
fn test_char16_as_ptr_points_to_first_unit() {
let units = [0x0045u16, 0x0046, 0x0049, 0x0000];
let s = Char16Str::from_units_with_nul(&units).unwrap();
assert_eq!(s.as_ptr(), units.as_ptr());
}
#[test]
fn test_char16_display_and_debug() {
let units = [0x0045u16, 0x0046, 0x0049, 0x0000];
let s = Char16Str::from_units_with_nul(&units).unwrap();
assert_eq!(s.to_string(), "EFI");
assert_eq!(alloc::format!("{s:?}"), "\"EFI\"");
}
#[test]
fn test_char16_equality_and_ordering() {
let a = [0x0041u16, 0x0000];
let b = [0x0042u16, 0x0000];
let a = Char16Str::from_units_with_nul(&a).unwrap();
let b = Char16Str::from_units_with_nul(&b).unwrap();
assert!(a < b);
assert_ne!(a, b);
assert!(a == "A");
assert!(*"A" == *a);
}
#[test]
fn test_char16string_try_from_str() {
let s = Char16String::try_from_str("Boot0001").unwrap();
assert_eq!(s.len(), 8);
assert!(*s == *"Boot0001");
assert_eq!(s.to_string(), "Boot0001");
}
#[test]
fn test_char16string_partial_eq_str() {
let s = Char16String::try_from_str("EFI").unwrap();
assert!(s == "EFI");
assert!(s == *"EFI");
assert!(*"EFI" == s);
assert!(s != "NOPE");
assert!(*"NOPE" != s);
}
#[test]
fn test_char16string_partial_eq_char16str() {
let s = Char16String::try_from_str("EFI").unwrap();
let same = Char16String::try_from_str("EFI").unwrap();
let different = Char16String::try_from_str("NOPE").unwrap();
let same_str: &Char16Str = same.as_char16_str();
let different_str: &Char16Str = different.as_char16_str();
assert!(s == *same_str);
assert!(s == same_str);
assert!(*same_str == s);
assert!(s != *different_str);
assert!(*different_str != s);
}
#[test]
fn test_char16string_from_le_bytes_with_nul() {
let bytes = [0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x00, 0x00];
let s = Char16String::from_le_bytes_with_nul(&bytes).unwrap();
assert_eq!(s.len(), 3);
assert_eq!(s.to_string(), "EFI");
}
#[test]
fn test_char16string_from_le_bytes_with_nul_rejects_trailing_padding() {
let bytes = [0x45, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00];
assert_eq!(Char16String::from_le_bytes_with_nul(&bytes), Err(StringError::InteriorNul { position: 1 }));
}
#[test]
fn test_char16string_from_le_bytes_until_nul_ignores_padding() {
let bytes = [0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x00, 0x00, 0xFF, 0xFF];
let s = Char16String::from_le_bytes_until_nul(&bytes).unwrap();
assert_eq!(s.to_string(), "EFI");
assert_eq!(s.into_units_with_nul(), alloc::vec![0x0045, 0x0046, 0x0049, 0x0000]);
}
#[test]
fn test_char16string_from_le_bytes_odd_length() {
let bytes = [0x45, 0x00, 0x46];
assert_eq!(Char16String::from_le_bytes_with_nul(&bytes), Err(StringError::OddByteLength { len: 3 }));
assert_eq!(Char16String::from_le_bytes_until_nul(&bytes), Err(StringError::OddByteLength { len: 3 }));
}
#[test]
fn test_char16string_from_le_bytes_until_nul_missing_terminator() {
let bytes = [0x45, 0x00, 0x46, 0x00];
assert_eq!(Char16String::from_le_bytes_until_nul(&bytes), Err(StringError::MissingNulTerminator));
}
#[test]
fn test_char16string_from_le_bytes_surrogate_rejected() {
let bytes = [0x45, 0x00, 0x00, 0xD8, 0x00, 0x00];
assert_eq!(
Char16String::from_le_bytes_until_nul(&bytes),
Err(StringError::NotUcs2 { position: 1, value: 0xD800 })
);
}
#[test]
fn test_char16string_rejects_non_bmp() {
let result = Char16String::try_from_str("A\u{1F600}");
assert_eq!(result, Err(StringError::NotUcs2 { position: 1, value: 0x1F600 }));
}
#[test]
fn test_char16string_rejects_interior_nul() {
let result = Char16String::try_from_str("A\0B");
assert_eq!(result, Err(StringError::InteriorNul { position: 1 }));
}
#[test]
fn test_char16string_new_and_default() {
let a = Char16String::new();
let b = Char16String::default();
assert!(a.is_empty());
assert_eq!(a, b);
assert_eq!(a.clone().into_units_with_nul(), alloc::vec![0]);
}
#[test]
fn test_char16string_to_owned_roundtrip() {
use alloc::borrow::ToOwned;
let owned = Char16String::try_from_str("Test").unwrap();
let borrowed: &Char16Str = &owned;
let reowned = borrowed.to_owned();
assert_eq!(owned, reowned);
}
#[test]
fn test_char16string_from_ref_char16str() {
let owned = Char16String::try_from_str("Test").unwrap();
let borrowed: &Char16Str = &owned;
let converted = Char16String::from(borrowed);
assert_eq!(owned, converted);
let via_into: Char16String = borrowed.into();
assert_eq!(owned, via_into);
}
#[test]
fn test_char16_from_ptr_surrogate_error() {
let units = [0x0041u16, 0xD800, 0x0000];
let result = unsafe { Char16Str::from_ptr(units.as_ptr()) };
assert_eq!(result, Err(StringError::NotUcs2 { position: 1, value: 0xD800 }));
}
#[test]
fn test_char16str_partial_eq_ref_str() {
let units = [0x0045u16, 0x0046, 0x0049, 0x0000];
let s = Char16Str::from_units_with_nul(&units).unwrap();
let other: &str = "EFI";
assert!(s == other);
assert!(s != "NOPE");
}
#[test]
fn test_char16str_hash() {
use core::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
let units = [0x0045u16, 0x0000];
let s = Char16Str::from_units_with_nul(&units).unwrap();
let mut hasher = DefaultHasher::new();
s.hash(&mut hasher);
let _ = hasher.finish();
}
#[test]
fn test_char16string_debug_borrow_and_display() {
use core::borrow::Borrow;
let s = Char16String::try_from_str("EFI").unwrap();
assert_eq!(alloc::format!("{s:?}"), "\"EFI\"");
assert_eq!(s.to_string(), "EFI");
let borrowed: &Char16Str = s.borrow();
assert_eq!(borrowed.len(), 3);
}
#[test]
fn test_char16_from_units_until_nul_exact() {
let units = [0x0045u16, 0x0046, 0x0049, 0x0000];
let s = Char16Str::from_units_until_nul(&units).unwrap();
assert!(s == "EFI");
}
#[test]
fn test_char16_from_units_until_nul_trailing_padding() {
let units = [0x0045u16, 0x0046, 0x0049, 0x0000, 0x0000, 0x0000];
let s = Char16Str::from_units_until_nul(&units).unwrap();
assert_eq!(s.len(), 3);
assert!(s == "EFI");
}
#[test]
fn test_char16_from_units_until_nul_trailing_garbage() {
let units = [0x0045u16, 0x0000, 0x1234];
let s = Char16Str::from_units_until_nul(&units).unwrap();
assert_eq!(s.len(), 1);
assert!(s == "E");
}
#[test]
fn test_char16_from_units_until_nul_missing_nul() {
let units = [0x0045u16, 0x0046];
assert_eq!(Char16Str::from_units_until_nul(&units), Err(StringError::MissingNulTerminator));
assert_eq!(Char16Str::from_units_until_nul(&[]), Err(StringError::MissingNulTerminator));
}
#[test]
fn test_char16_from_units_until_nul_surrogate_rejected() {
let units = [0x0045u16, 0xD800, 0x0000];
assert_eq!(Char16Str::from_units_until_nul(&units), Err(StringError::NotUcs2 { position: 1, value: 0xD800 }));
}
#[test]
fn test_char16string_from_units_with_nul() {
let units = alloc::vec![0x0045u16, 0x0046, 0x0049, 0x0000];
let s = Char16String::from_units_with_nul(units).unwrap();
assert_eq!(s.len(), 3);
assert!(*s == *"EFI");
}
#[test]
fn test_char16string_from_units_with_nul_missing_terminator() {
let units = alloc::vec![0x0045u16, 0x0046];
assert_eq!(Char16String::from_units_with_nul(units), Err(StringError::MissingNulTerminator));
}
#[test]
fn test_char16string_from_units_with_nul_interior_nul() {
let units = alloc::vec![0x0045u16, 0x0000, 0x0049, 0x0000];
assert_eq!(Char16String::from_units_with_nul(units), Err(StringError::InteriorNul { position: 1 }));
}
#[test]
fn test_char16string_from_units_with_nul_surrogate_rejected() {
let units = alloc::vec![0x0045u16, 0xD800, 0x0000];
assert_eq!(Char16String::from_units_with_nul(units), Err(StringError::NotUcs2 { position: 1, value: 0xD800 }));
}
#[test]
fn test_char16string_from_units_with_nul_unchecked() {
let units = alloc::vec![0x0045u16, 0x0046, 0x0049, 0x0000];
let s = unsafe { Char16String::from_units_with_nul_unchecked(units) };
assert!(*s == *"EFI");
}
#[test]
fn test_char16_array_from_str_exact_fit() {
const NAME: Char16Array<4> = Char16Array::from_str("EFI");
assert_eq!(NAME.as_char16_str().len(), 3);
assert!(NAME.as_char16_str() == "EFI");
assert_eq!(NAME.to_string(), "EFI");
}
#[test]
fn test_char16_array_with_padding() {
let array = Char16Array::<8>::from_str("EFI");
assert_eq!(array.as_char16_str().len(), 3);
assert_eq!(array.as_array(), &[0x45, 0x46, 0x49, 0, 0, 0, 0, 0]);
}
#[test]
fn test_char16_array_too_long() {
assert_eq!(Char16Array::<3>::try_from_str("EFI"), Err(StringError::TooLong { capacity: 2, required: 4 }));
}
#[test]
fn test_char16_array_rejects_non_bmp() {
assert_eq!(
Char16Array::<8>::try_from_str("A\u{1F600}"),
Err(StringError::NotUcs2 { position: 1, value: 0x1F600 })
);
}
#[test]
fn test_char16_array_zero_capacity() {
assert_eq!(Char16Array::<0>::try_from_str(""), Err(StringError::TooLong { capacity: 0, required: 1 }));
}
#[test]
fn test_char16_macro() {
let name = char16!("Firmware");
assert_eq!(name.len(), 8);
assert!(name == "Firmware");
}
#[test]
fn test_char16_macro_multibyte_utf8() {
let name = char16!("café");
assert_eq!(name.len(), 4);
assert!(name == "café");
}
#[test]
fn test_char16_array_binary_layout() {
assert_eq!(core::mem::size_of::<Char16Array<8>>(), 16);
}
#[test]
fn test_char16_array_runtime_multibyte_decode() {
let two_byte = Char16Array::<8>::try_from_str("café").unwrap(); assert!(two_byte.as_char16_str() == "café");
let three_byte = Char16Array::<8>::try_from_str("€").unwrap(); assert_eq!(three_byte.as_char16_str().len(), 1);
assert!(matches!(Char16Array::<8>::try_from_str("\u{1F600}"), Err(StringError::NotUcs2 { .. })));
}
#[test]
fn test_char16_capacity_helper_runtime() {
assert_eq!(ucs2_capacity("EFI"), 4);
assert_eq!(ucs2_capacity(""), 1);
}
#[test]
fn test_char16_array_try_from_str_trait() {
let array = Char16Array::<9>::try_from("Firmware").unwrap();
assert!(array.as_char16_str() == "Firmware");
}
#[test]
fn test_char16_array_from_char8_array() {
let narrow = Char8Array::<9>::from_str("Firmware");
let wide: Char16Array<9> = narrow.into();
assert!(wide.as_char16_str() == "Firmware");
}
#[test]
fn test_char16_array_from_char8_array_high_latin1() {
let narrow = Char8Array::<2>::from_str("\u{00E9}"); let wide: Char16Array<2> = narrow.into();
assert!(wide.as_char16_str() == "\u{00E9}");
}
#[test]
fn test_char16_array_from_char8_array_empty_capacity() {
let narrow = Char8Array::<0>::default();
let wide: Char16Array<0> = narrow.into();
assert!(wide.as_char16_str().is_empty());
}
#[test]
fn test_char16_array_to_char16string() {
let array = Char16Array::<9>::from_str("Firmware");
let owned: Char16String = array.into();
assert!(owned == "Firmware");
}
#[test]
fn test_char16_array_to_string() {
let array = Char16Array::<9>::from_str("Firmware");
let owned: alloc::string::String = array.into();
assert_eq!(owned, "Firmware");
}
#[test]
fn test_char16string_from_char8str() {
let narrow = Char8Array::<4>::from_str("EFI");
let wide = Char16String::from(narrow.as_char8_str());
assert!(wide == "EFI");
}
#[test]
fn test_char16string_from_char8string() {
let narrow = Char8String::try_from_str("EFI").unwrap();
let wide = Char16String::from(&narrow);
assert!(wide == "EFI");
let wide_owned = Char16String::from(narrow);
assert!(wide_owned == "EFI");
}
}