use crate::fallback::{LocaleFallbackConfig, LocaleFallbackPriority};
use crate::{DataError, DataErrorKind, DataLocale, DataProvider, DataProviderWithMarker};
use core::fmt;
use core::marker::PhantomData;
use icu_locale_core::preferences::LocalePreferences;
use yoke::Yokeable;
use zerovec::ule::*;
pub trait DynamicDataMarker: 'static {
type DataStruct: for<'a> Yokeable<'a>;
}
pub trait DataMarker: DynamicDataMarker {
const INFO: DataMarkerInfo;
}
pub trait DataMarkerExt: DataMarker + Sized {
fn bind<P>(provider: P) -> DataProviderWithMarker<Self, P>
where
P: DataProvider<Self>;
fn make_locale(locale: LocalePreferences) -> DataLocale;
}
impl<M: DataMarker + Sized> DataMarkerExt for M {
fn bind<P>(provider: P) -> DataProviderWithMarker<Self, P>
where
P: DataProvider<Self>,
{
DataProviderWithMarker::new(provider)
}
fn make_locale(locale: LocalePreferences) -> DataLocale {
M::INFO.make_locale(locale)
}
}
#[derive(Debug, Copy, Clone)]
pub struct NeverMarker<Y>(PhantomData<Y>);
impl<Y> DynamicDataMarker for NeverMarker<Y>
where
for<'a> Y: Yokeable<'a>,
{
type DataStruct = Y;
}
impl<Y> DataMarker for NeverMarker<Y>
where
for<'a> Y: Yokeable<'a>,
{
const INFO: DataMarkerInfo = DataMarkerInfo::from_id(DataMarkerId {
#[cfg(any(feature = "export", debug_assertions))]
debug: "NeverMarker",
hash: *b"nevermar",
});
}
#[doc(hidden)] #[macro_export]
macro_rules! __impl_data_provider_never_marker {
($ty:path) => {
impl<Y> $crate::DataProvider<$crate::marker::NeverMarker<Y>> for $ty
where
for<'a> Y: $crate::prelude::yoke::Yokeable<'a>,
{
fn load(
&self,
req: $crate::DataRequest,
) -> Result<$crate::DataResponse<$crate::marker::NeverMarker<Y>>, $crate::DataError>
{
Err($crate::DataErrorKind::MarkerNotFound.with_req(
<$crate::marker::NeverMarker<Y> as $crate::DataMarker>::INFO,
req,
))
}
}
};
}
#[doc(inline)]
pub use __impl_data_provider_never_marker as impl_data_provider_never_marker;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash, ULE)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct DataMarkerIdHash([u8; 4]);
impl DataMarkerIdHash {
pub const LEADING_TAG: &[u8] = b"tdmh";
pub const fn to_bytes(self) -> [u8; 4] {
self.0
}
}
#[expect(clippy::indexing_slicing)]
const fn fxhash_32(bytes: &[u8]) -> u32 {
#[inline]
const fn hash_word_32(mut hash: u32, word: u32) -> u32 {
const ROTATE: u32 = 5;
const SEED32: u32 = 0x9e_37_79_b9;
hash = hash.rotate_left(ROTATE);
hash ^= word;
hash = hash.wrapping_mul(SEED32);
hash
}
let mut cursor = 0;
let end = bytes.len();
let mut hash = 0;
while end - cursor >= 4 {
let word = u32::from_le_bytes([
bytes[cursor],
bytes[cursor + 1],
bytes[cursor + 2],
bytes[cursor + 3],
]);
hash = hash_word_32(hash, word);
cursor += 4;
}
if end - cursor >= 2 {
let word = u16::from_le_bytes([bytes[cursor], bytes[cursor + 1]]);
hash = hash_word_32(hash, word as u32);
cursor += 2;
}
if end - cursor >= 1 {
hash = hash_word_32(hash, bytes[cursor] as u32);
}
hash
}
#[cfg(feature = "alloc")]
impl<'a> zerovec::maps::ZeroMapKV<'a> for DataMarkerIdHash {
type Container = zerovec::ZeroVec<'a, DataMarkerIdHash>;
type Slice = zerovec::ZeroSlice<DataMarkerIdHash>;
type GetType = <DataMarkerIdHash as AsULE>::ULE;
type OwnedType = DataMarkerIdHash;
}
impl AsULE for DataMarkerIdHash {
type ULE = Self;
#[inline]
fn to_unaligned(self) -> Self::ULE {
self
}
#[inline]
fn from_unaligned(unaligned: Self::ULE) -> Self {
unaligned
}
}
unsafe impl EqULE for DataMarkerIdHash {}
#[derive(Debug, Copy, Clone, Eq)]
pub struct DataMarkerId {
#[cfg(any(feature = "export", debug_assertions))]
debug: &'static str,
hash: [u8; 8],
}
impl PartialEq for DataMarkerId {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash
}
}
impl Ord for DataMarkerId {
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.hash.cmp(&other.hash)
}
}
impl PartialOrd for DataMarkerId {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl core::hash::Hash for DataMarkerId {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.hash.hash(state)
}
}
impl DataMarkerId {
#[doc(hidden)]
pub const fn from_name(name: &'static str) -> Result<Self, (&'static str, usize)> {
#![allow(clippy::indexing_slicing)]
if !name.as_bytes()[name.len() - 1].is_ascii_digit() {
return Err(("[0-9]", name.len()));
}
let mut i = name.len() - 1;
while name.as_bytes()[i - 1].is_ascii_digit() {
i -= 1;
}
if name.as_bytes()[i - 1] != b'V' {
return Err(("V", i));
}
let magic = DataMarkerIdHash::LEADING_TAG;
let hash = fxhash_32(name.as_bytes()).to_le_bytes();
Ok(Self {
#[cfg(any(feature = "export", debug_assertions))]
debug: name,
hash: [
magic[0], magic[1], magic[2], magic[3], hash[0], hash[1], hash[2], hash[3],
],
})
}
#[inline]
pub const fn hashed(self) -> DataMarkerIdHash {
let [.., h1, h2, h3, h4] = self.hash;
DataMarkerIdHash([h1, h2, h3, h4])
}
#[cfg(feature = "export")]
pub const fn name(self) -> &'static str {
self.debug
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DataMarkerInfo {
pub id: DataMarkerId,
pub is_singleton: bool,
pub has_checksum: bool,
pub fallback_config: LocaleFallbackConfig,
#[cfg(feature = "export")]
pub attributes_domain: &'static str,
#[cfg(feature = "export")]
pub expose_baked_consts: bool,
}
impl PartialOrd for DataMarkerInfo {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DataMarkerInfo {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.id.cmp(&other.id)
}
}
impl core::hash::Hash for DataMarkerInfo {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}
impl DataMarkerInfo {
pub const fn from_id(id: DataMarkerId) -> Self {
Self {
id,
fallback_config: LocaleFallbackConfig::default(),
is_singleton: false,
has_checksum: false,
#[cfg(feature = "export")]
attributes_domain: "",
#[cfg(feature = "export")]
expose_baked_consts: false,
}
}
pub fn match_marker(self, marker: Self) -> Result<(), DataError> {
if self == marker {
Ok(())
} else {
Err(DataErrorKind::MarkerNotFound.with_marker(marker))
}
}
pub fn make_locale(self, locale: LocalePreferences) -> DataLocale {
if self.fallback_config.priority == LocaleFallbackPriority::Region {
locale.to_data_locale_region_priority()
} else {
locale.to_data_locale_language_priority()
}
}
}
#[macro_export] macro_rules! data_marker {
($(#[$doc:meta])* $name:ident, $($debug:literal,)? $struct:ty $(, $(#[$meta:meta])* $info_field:ident = $info_val:expr)* $(,)?) => {
$(#[$doc])*
#[non_exhaustive]
pub struct $name;
impl $crate::DynamicDataMarker for $name {
type DataStruct = $struct;
}
impl $crate::DataMarker for $name {
const INFO: $crate::DataMarkerInfo = {
$(
#[doc = concat!("let ident = \"", stringify!($name), "\";")]
#[doc = concat!("let debug = \"", $debug, "\";")]
#[allow(dead_code)]
struct DebugTest;
)?
#[allow(unused_mut)]
let mut info = const { $crate::DataMarkerInfo::from_id(
match $crate::marker::DataMarkerId::from_name(stringify!($name)) {
Ok(path) => path,
Err(_) => panic!(concat!("Invalid marker name: ", stringify!($name))),
})};
$(
$(#[$meta])*
{info.$info_field = $info_val;}
)*
info
};
}
}
}
impl fmt::Debug for DataMarkerInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[cfg(any(feature = "export", debug_assertions))]
return f.write_str(self.id.debug);
#[cfg(not(any(feature = "export", debug_assertions)))]
return write!(f, "{:?}", self.id);
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ErasedMarker<DataStruct: for<'a> Yokeable<'a>>(PhantomData<DataStruct>);
impl<DataStruct: for<'a> Yokeable<'a>> DynamicDataMarker for ErasedMarker<DataStruct> {
type DataStruct = DataStruct;
}
#[test]
fn test_marker_syntax() {
DataMarkerId::from_name("HelloWorldV1").unwrap();
DataMarkerId::from_name("HelloWorldFooV1").unwrap();
DataMarkerId::from_name("HelloWorldV999").unwrap();
DataMarkerId::from_name("Hello485FooV1").unwrap();
assert_eq!(
DataMarkerId::from_name("HelloWorld"),
Err(("[0-9]", "HelloWorld".len()))
);
assert_eq!(
DataMarkerId::from_name("HelloWorldV"),
Err(("[0-9]", "HelloWorldV".len()))
);
assert_eq!(
DataMarkerId::from_name("HelloWorldVFoo"),
Err(("[0-9]", "HelloWorldVFoo".len()))
);
assert_eq!(
DataMarkerId::from_name("HelloWorldV1Foo"),
Err(("[0-9]", "HelloWorldV1Foo".len()))
);
}
#[test]
fn test_id_debug() {
assert_eq!(DataMarkerId::from_name("BarV1").unwrap().debug, "BarV1");
}
#[test]
fn test_hash_word_32() {
assert_eq!(0, fxhash_32(b""));
assert_eq!(0xF3051F19, fxhash_32(b"a"));
assert_eq!(0x2F9DF119, fxhash_32(b"ab"));
assert_eq!(0xCB1D9396, fxhash_32(b"abc"));
assert_eq!(0x8628F119, fxhash_32(b"abcd"));
assert_eq!(0xBEBDB56D, fxhash_32(b"abcde"));
assert_eq!(0x1CE8476D, fxhash_32(b"abcdef"));
assert_eq!(0xC0F176A4, fxhash_32(b"abcdefg"));
assert_eq!(0x09AB476D, fxhash_32(b"abcdefgh"));
assert_eq!(0xB72F5D88, fxhash_32(b"abcdefghi"));
}
#[test]
fn test_id_hash() {
assert_eq!(
DataMarkerId::from_name("BarV1").unwrap().hashed(),
DataMarkerIdHash([212, 77, 158, 241]),
);
}