#![allow(clippy::exhaustive_structs, clippy::exhaustive_enums)]
use icu_provider::prelude::*;
use crate::provider::data::CaseMapData;
use crate::provider::exceptions::CaseMapExceptions;
use icu_collections::codepointtrie::CodePointTrie;
#[cfg(feature = "datagen")]
use icu_collections::codepointtrie::CodePointTrieHeader;
pub mod data;
pub mod exception_helpers;
pub mod exceptions;
#[cfg(feature = "datagen")]
mod exceptions_builder;
mod unfold;
#[cfg(feature = "compiled_data")]
#[derive(Debug)]
pub struct Baked;
#[cfg(feature = "compiled_data")]
#[allow(unused_imports)]
const _: () = {
use icu_casemap_data::*;
pub mod icu {
pub use crate as casemap;
pub use icu_collections as collections;
}
make_provider!(Baked);
impl_case_map_v1!(Baked);
impl_case_map_unfold_v1!(Baked);
};
icu_provider::data_marker!(
CaseMapV1,
"case/map/v1",
CaseMap<'static>,
is_singleton = true
);
icu_provider::data_marker!(
CaseMapUnfoldV1,
"case/map/unfold/v1",
CaseMapUnfold<'static>,
is_singleton = true
);
#[cfg(feature = "datagen")]
pub const MARKERS: &[DataMarkerInfo] = &[CaseMapUnfoldV1::INFO, CaseMapV1::INFO];
pub use self::unfold::CaseMapUnfold;
#[derive(Debug, PartialEq, Clone, yoke::Yokeable, zerofrom::ZeroFrom)]
#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_casemap::provider))]
#[yoke(prove_covariance_manually)]
pub struct CaseMap<'data> {
pub trie: CodePointTrie<'data, CaseMapData>,
pub exceptions: CaseMapExceptions<'data>,
}
icu_provider::data_struct!(
CaseMap<'_>,
#[cfg(feature = "datagen")]
);
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for CaseMap<'de> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(serde::Deserialize)]
pub struct Raw<'data> {
#[serde(borrow)]
pub trie: CodePointTrie<'data, CaseMapData>,
#[serde(borrow)]
pub exceptions: CaseMapExceptions<'data>,
}
let Raw { trie, exceptions } = Raw::deserialize(deserializer)?;
let result = Self { trie, exceptions };
debug_assert!(result.validate().is_ok());
Ok(result)
}
}
impl CaseMap<'_> {
#[cfg(feature = "datagen")]
pub fn try_from_icu(
trie_header: CodePointTrieHeader,
trie_index: &[u16],
trie_data: &[u16],
exceptions: &[u16],
) -> Result<Self, DataError> {
use self::exceptions_builder::CaseMapExceptionsBuilder;
use zerovec::ZeroVec;
let exceptions_builder = CaseMapExceptionsBuilder::new(exceptions);
let (exceptions, idx_map) = exceptions_builder.build()?;
let trie_index = ZeroVec::alloc_from_slice(trie_index);
#[expect(clippy::unwrap_used)] let trie_data = trie_data
.iter()
.map(|&i| {
CaseMapData::try_from_icu_integer(i)
.unwrap()
.with_updated_exception(&idx_map)
})
.collect::<ZeroVec<_>>();
let trie = CodePointTrie::try_new(trie_header, trie_index, trie_data)
.map_err(|_| DataError::custom("Casemapping data does not form valid trie"))?;
let result = Self { trie, exceptions };
result.validate().map_err(DataError::custom)?;
Ok(result)
}
#[cfg(any(feature = "serde", feature = "datagen"))]
#[allow(unused)] pub(crate) fn validate(&self) -> Result<(), &'static str> {
let valid_exception_indices = self.exceptions.validate()?;
let validate_delta = |c: char, delta: i32| -> Result<(), &'static str> {
let new_c =
u32::try_from(c as i32 + delta).map_err(|_| "Delta larger than character")?;
char::from_u32(new_c).ok_or("Invalid delta")?;
Ok(())
};
for i in 0..char::MAX as u32 {
if let Some(c) = char::from_u32(i) {
let data = self.lookup_data(c);
if data.has_exception() {
let idx = data.exception_index();
let exception = self.exceptions.get(idx);
if !valid_exception_indices.contains(&idx) {
return Err("Invalid exception index in trie data");
}
exception.validate()?;
} else {
validate_delta(c, data.delta() as i32)?;
}
}
}
Ok(())
}
pub(crate) fn lookup_data(&self, c: char) -> CaseMapData {
self.trie.get32(c as u32)
}
}