extern crate alloc;
use alloc::string::String;
use core::{fmt, mem::MaybeUninit};
#[cfg(all(feature = "text_fixed", feature = "text_utf16"))]
mod utf16;
#[cfg(all(feature = "text_fixed", feature = "text_utf32"))]
mod utf32;
#[cfg(all(feature = "text_fixed", feature = "text_utf8"))]
pub mod utf8;
#[cfg(all(feature = "text_fixed", feature = "text_utf16"))]
pub use utf16::*;
#[cfg(all(feature = "text_fixed", feature = "text_utf8"))]
pub use utf8::*;
#[cfg(all(feature = "text_fixed", feature = "text_utf32"))]
pub use utf32::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FixedTextError {
WrongCodepointCount { expected: usize, found: usize },
}
#[cfg(all(feature = "text_fixed", feature = "text_utf8"))]
impl From<FixedUtf8Error> for FixedTextError {
fn from(e: FixedUtf8Error) -> Self {
match e {
FixedUtf8Error::TooManyBytes { max, found } => FixedTextError::WrongCodepointCount {
expected: max,
found,
},
FixedUtf8Error::InvalidUtf8 => FixedTextError::WrongCodepointCount {
expected: 0,
found: 0,
},
}
}
}
#[cfg(all(feature = "text_fixed", feature = "text_utf16"))]
impl From<FixedUtf16Error> for FixedTextError {
fn from(e: FixedUtf16Error) -> Self {
match e {
FixedUtf16Error::WrongCodeUnitCount { expected, found } => {
FixedTextError::WrongCodepointCount { expected, found }
}
FixedUtf16Error::InvalidUtf16 => FixedTextError::WrongCodepointCount {
expected: 0,
found: 0,
},
}
}
}
#[cfg(all(feature = "text_fixed", feature = "text_utf32"))]
impl From<FixedUtf32Error> for FixedTextError {
fn from(e: FixedUtf32Error) -> Self {
match e {
FixedUtf32Error::WrongCodeUnitCount { expected, found } => {
FixedTextError::WrongCodepointCount { expected, found }
}
FixedUtf32Error::InvalidUtf32 => FixedTextError::WrongCodepointCount {
expected: 0,
found: 0,
},
}
}
}
impl fmt::Display for FixedTextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FixedTextError::WrongCodepointCount { expected, found } => {
write!(
f,
"wrong number of codepoints (expected {expected}, found {found})"
)
}
}
}
}
#[cfg(any(feature = "io-std", feature = "io"))]
impl std::error::Error for FixedTextError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct FixedCodepointString<const N: usize> {
chars: [char; N],
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct FixedCodepointStr<'a, const N: usize>(pub &'a [char; N]);
impl<const N: usize> FixedCodepointString<N> {
pub const fn as_chars(&self) -> &[char; N] {
&self.chars
}
pub const fn as_str_view(&self) -> FixedCodepointStr<'_, N> {
FixedCodepointStr(&self.chars)
}
pub fn to_string_lossless(&self) -> String {
self.chars.iter().collect()
}
}
impl<const N: usize> fmt::Display for FixedCodepointString<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for c in self.chars {
write!(f, "{c}")?;
}
Ok(())
}
}
impl<const N: usize> AsRef<[char]> for FixedCodepointString<N> {
fn as_ref(&self) -> &[char] {
self.chars.as_slice()
}
}
impl<'a, const N: usize> FixedCodepointStr<'a, N> {
pub const fn as_chars(&self) -> &'a [char; N] {
self.0
}
}
impl<'a, const N: usize> From<&'a [char; N]> for FixedCodepointStr<'a, N> {
fn from(v: &'a [char; N]) -> Self {
Self(v)
}
}
impl<'a, const N: usize> From<&'a FixedCodepointString<N>> for FixedCodepointStr<'a, N> {
fn from(v: &'a FixedCodepointString<N>) -> Self {
v.as_str_view()
}
}
impl<'a, const N: usize> fmt::Display for FixedCodepointStr<'a, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for c in *self.0 {
write!(f, "{c}")?;
}
Ok(())
}
}
impl<'a, const N: usize> AsRef<[char]> for FixedCodepointStr<'a, N> {
fn as_ref(&self) -> &[char] {
self.0.as_slice()
}
}
impl<const N: usize> From<&FixedCodepointString<N>> for String {
fn from(v: &FixedCodepointString<N>) -> Self {
v.chars.iter().collect()
}
}
#[cfg(all(test, feature = "text_utf16", feature = "text_utf32"))]
mod tests {
use super::*;
use crate::{BigEndian, LittleEndian};
#[test]
fn fixed_utf16_code_units_alias_is_host_endian() {
#[cfg(target_endian = "little")]
{
let _v: FixedUtf16CodeUnits<2> = FixedUtf16LeCodeUnits::<2>::try_from("hi").unwrap();
}
#[cfg(target_endian = "big")]
{
let _v: FixedUtf16CodeUnits<2> = FixedUtf16BeCodeUnits::<2>::try_from("hi").unwrap();
}
}
#[test]
fn fixed_utf32_code_units_alias_is_host_endian() {
#[cfg(target_endian = "little")]
{
let _v: FixedUtf32CodeUnits<2> = FixedUtf32LeCodeUnits::<2>::try_from("hi").unwrap();
}
#[cfg(target_endian = "big")]
{
let _v: FixedUtf32CodeUnits<2> = FixedUtf32BeCodeUnits::<2>::try_from("hi").unwrap();
}
}
#[test]
fn fixed_code_units_can_be_wrapped_in_endian_types() {
const N: usize = 2;
let le16: LittleEndian<FixedUtf16CodeUnits<N>> = "hi".try_into().unwrap();
let be16: BigEndian<FixedUtf16CodeUnits<N>> = "hi".try_into().unwrap();
let _native16: FixedUtf16CodeUnits<N> = le16.to_native();
let _native16b: FixedUtf16CodeUnits<N> = be16.to_native();
let le32: LittleEndian<FixedUtf32CodeUnits<N>> = "hi".try_into().unwrap();
let be32: BigEndian<FixedUtf32CodeUnits<N>> = "hi".try_into().unwrap();
let _native32: FixedUtf32CodeUnits<N> = le32.to_native();
let _native32b: FixedUtf32CodeUnits<N> = be32.to_native();
}
}
impl<const N: usize> From<FixedCodepointStr<'_, N>> for String {
fn from(v: FixedCodepointStr<'_, N>) -> Self {
v.0.iter().collect()
}
}
impl<const N: usize> From<FixedCodepointStr<'_, N>> for FixedCodepointString<N> {
fn from(v: FixedCodepointStr<'_, N>) -> Self {
Self { chars: *v.0 }
}
}
impl<const N: usize> From<FixedCodepointStr<'_, N>> for [char; N] {
fn from(v: FixedCodepointStr<'_, N>) -> Self {
*v.0
}
}
impl<const N: usize> TryFrom<&str> for FixedCodepointString<N> {
type Error = FixedTextError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let mut out: [MaybeUninit<char>; N] = [MaybeUninit::uninit(); N];
let mut count = 0usize;
for c in s.chars() {
if count >= N {
return Err(FixedTextError::WrongCodepointCount {
expected: N,
found: count + 1,
});
}
out[count].write(c);
count += 1;
}
if count != N {
return Err(FixedTextError::WrongCodepointCount {
expected: N,
found: count,
});
}
let chars = unsafe { out.map(|m| m.assume_init()) };
Ok(Self { chars })
}
}
impl<const N: usize> TryFrom<String> for FixedCodepointString<N> {
type Error = FixedTextError;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::try_from(s.as_str())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FixedUtf16Error {
WrongCodeUnitCount { expected: usize, found: usize },
InvalidUtf16,
}
#[cfg(all(feature = "text_fixed", feature = "text_utf16"))]
impl<const N: usize> TryFrom<&str> for crate::LittleEndian<FixedUtf16CodeUnits<N>> {
type Error = FixedUtf16Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let native = FixedUtf16CodeUnits::<N>::try_from(s)?;
Ok(crate::LittleEndian::from(native))
}
}
#[cfg(all(feature = "text_fixed", feature = "text_utf16"))]
impl<const N: usize> TryFrom<String> for crate::LittleEndian<FixedUtf16CodeUnits<N>> {
type Error = FixedUtf16Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::try_from(s.as_str())
}
}
#[cfg(all(feature = "text_fixed", feature = "text_utf16"))]
impl<const N: usize> TryFrom<&str> for crate::BigEndian<FixedUtf16CodeUnits<N>> {
type Error = FixedUtf16Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let native = FixedUtf16CodeUnits::<N>::try_from(s)?;
Ok(crate::BigEndian::from(native))
}
}
#[cfg(all(feature = "text_fixed", feature = "text_utf16"))]
impl<const N: usize> TryFrom<String> for crate::BigEndian<FixedUtf16CodeUnits<N>> {
type Error = FixedUtf16Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::try_from(s.as_str())
}
}
#[cfg(all(feature = "text_fixed", feature = "text_utf32"))]
impl<const N: usize> TryFrom<&str> for crate::LittleEndian<FixedUtf32CodeUnits<N>> {
type Error = FixedUtf32Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let native = FixedUtf32CodeUnits::<N>::try_from(s)?;
Ok(crate::LittleEndian::from(native))
}
}
#[cfg(all(feature = "text_fixed", feature = "text_utf32"))]
impl<const N: usize> TryFrom<String> for crate::LittleEndian<FixedUtf32CodeUnits<N>> {
type Error = FixedUtf32Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::try_from(s.as_str())
}
}
#[cfg(all(feature = "text_fixed", feature = "text_utf32"))]
impl<const N: usize> TryFrom<&str> for crate::BigEndian<FixedUtf32CodeUnits<N>> {
type Error = FixedUtf32Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let native = FixedUtf32CodeUnits::<N>::try_from(s)?;
Ok(crate::BigEndian::from(native))
}
}
#[cfg(all(feature = "text_fixed", feature = "text_utf32"))]
impl<const N: usize> TryFrom<String> for crate::BigEndian<FixedUtf32CodeUnits<N>> {
type Error = FixedUtf32Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::try_from(s.as_str())
}
}
impl fmt::Display for FixedUtf16Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FixedUtf16Error::WrongCodeUnitCount { expected, found } => write!(
f,
"wrong number of UTF-16 code units (expected {expected}, found {found})"
),
FixedUtf16Error::InvalidUtf16 => write!(f, "invalid UTF-16"),
}
}
}
#[cfg(any(feature = "io-std", feature = "io"))]
impl std::error::Error for FixedUtf16Error {}