use super::{GenericPatternItem, PatternItem};
use crate::provider::fields;
use core::convert::TryFrom;
use zerovec::ule::{AsULE, UleError, ULE};
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(transparent)]
pub struct PatternItemULE([u8; 3]);
impl PatternItemULE {
#[inline]
fn determine_field_from_u8(byte: u8) -> bool {
byte & 0b1000_0000 != 0
}
#[inline]
fn bytes_in_range(value: (&u8, &u8, &u8)) -> bool {
if Self::determine_field_from_u8(*value.0) {
fields::FieldULE::validate_byte_pair((*value.1, *value.2)).is_ok()
&& *value.0 == 0b1000_0000
} else {
char::try_from(u32::from_be_bytes([0x00, *value.0, *value.1, *value.2])).is_ok()
}
}
}
unsafe impl ULE for PatternItemULE {
fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
if bytes.len() % 3 != 0 {
return Err(UleError::length::<Self>(bytes.len()));
}
#[expect(clippy::indexing_slicing)] if !bytes
.chunks(3)
.all(|c| Self::bytes_in_range((&c[0], &c[1], &c[2])))
{
return Err(UleError::parse::<Self>());
}
Ok(())
}
}
impl AsULE for PatternItem {
type ULE = PatternItemULE;
#[inline]
fn to_unaligned(self) -> Self::ULE {
match self {
Self::Field(field) => {
PatternItemULE([0b1000_0000, field.symbol.idx(), field.length.idx()])
}
Self::Literal(ch) => {
let u = ch as u32;
let bytes = u.to_be_bytes();
PatternItemULE([bytes[1], bytes[2], bytes[3]])
}
}
}
#[inline]
fn from_unaligned(unaligned: Self::ULE) -> Self {
let value = unaligned.0;
#[expect(clippy::unwrap_used)] if PatternItemULE::determine_field_from_u8(value[0]) {
let symbol = fields::FieldSymbol::from_idx(value[1]).unwrap();
let length = fields::FieldLength::from_idx(value[2]).unwrap();
PatternItem::Field(fields::Field { symbol, length })
} else {
PatternItem::Literal(unsafe {
char::from_u32_unchecked(u32::from_be_bytes([0x00, value[0], value[1], value[2]]))
})
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(transparent)]
pub struct GenericPatternItemULE([u8; 3]);
impl GenericPatternItemULE {
#[inline]
fn determine_field_from_u8(byte: u8) -> bool {
byte & 0b1000_0000 != 0
}
#[inline]
fn bytes_in_range(value: (&u8, &u8, &u8)) -> bool {
if Self::determine_field_from_u8(*value.0) {
*value.0 == 0b1000_0000 && *value.1 == 0 && *value.2 < 10
} else {
let u = u32::from_be_bytes([0x00, *value.0, *value.1, *value.2]);
char::try_from(u).is_ok()
}
}
#[inline]
pub(crate) fn as_pattern_item_ule(&self) -> Result<&PatternItemULE, u8> {
if Self::determine_field_from_u8(self.0[0]) {
Err(self.0[2])
} else {
if cfg!(debug_assertions) {
let GenericPatternItem::Literal(c) = GenericPatternItem::from_unaligned(*self)
else {
unreachable!("expected a literal!")
};
let pattern_item_ule = PatternItem::Literal(c).to_unaligned();
debug_assert_eq!(self.0, pattern_item_ule.0);
}
Ok(unsafe { &*(self as *const GenericPatternItemULE as *const PatternItemULE) })
}
}
}
unsafe impl ULE for GenericPatternItemULE {
fn validate_bytes(bytes: &[u8]) -> Result<(), UleError> {
if bytes.len() % 3 != 0 {
return Err(UleError::length::<Self>(bytes.len()));
}
#[expect(clippy::indexing_slicing)] if !bytes
.chunks_exact(3)
.all(|c| Self::bytes_in_range((&c[0], &c[1], &c[2])))
{
return Err(UleError::parse::<Self>());
}
Ok(())
}
}
impl GenericPatternItem {
#[inline]
pub(crate) const fn to_unaligned_const(self) -> <Self as AsULE>::ULE {
match self {
Self::Placeholder(idx) => GenericPatternItemULE([0b1000_0000, 0x00, idx]),
Self::Literal(ch) => {
let u = ch as u32;
let bytes = u.to_be_bytes();
GenericPatternItemULE([bytes[1], bytes[2], bytes[3]])
}
}
}
}
impl AsULE for GenericPatternItem {
type ULE = GenericPatternItemULE;
#[inline]
fn to_unaligned(self) -> Self::ULE {
self.to_unaligned_const()
}
#[inline]
fn from_unaligned(unaligned: Self::ULE) -> Self {
let value = unaligned.0;
if GenericPatternItemULE::determine_field_from_u8(value[0]) {
Self::Placeholder(value[2])
} else {
#[expect(clippy::unwrap_used)] Self::Literal(
char::try_from(u32::from_be_bytes([0x00, value[0], value[1], value[2]])).unwrap(),
)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::provider::fields::{FieldLength, FieldSymbol, Second, Year};
use zerovec::ule::{AsULE, ULE};
#[test]
fn test_pattern_item_as_ule() {
let samples = [
(
PatternItem::from((FieldSymbol::Minute, FieldLength::Two)),
[0x80, FieldSymbol::Minute.idx(), FieldLength::Two.idx()],
),
(
PatternItem::from((FieldSymbol::Year(Year::Calendar), FieldLength::Four)),
[
0x80,
FieldSymbol::Year(Year::Calendar).idx(),
FieldLength::Four.idx(),
],
),
(
PatternItem::from((FieldSymbol::Year(Year::Cyclic), FieldLength::Four)),
[
0x80,
FieldSymbol::Year(Year::Cyclic).idx(),
FieldLength::Four.idx(),
],
),
(
PatternItem::from((FieldSymbol::Second(Second::MillisInDay), FieldLength::One)),
[
0x80,
FieldSymbol::Second(Second::MillisInDay).idx(),
FieldLength::One.idx(),
],
),
(PatternItem::from('z'), [0x00, 0x00, 0x7a]),
];
for (ref_pattern, ref_bytes) in samples {
let ule = ref_pattern.to_unaligned();
assert_eq!(ULE::slice_as_bytes(&[ule]), ref_bytes);
let pattern = PatternItem::from_unaligned(ule);
assert_eq!(pattern, ref_pattern);
}
}
#[test]
fn test_pattern_item_ule() {
let samples = [(
[
PatternItem::from((FieldSymbol::Year(Year::Calendar), FieldLength::Four)),
PatternItem::from('z'),
PatternItem::from((FieldSymbol::Second(Second::MillisInDay), FieldLength::One)),
],
[
[
0x80,
FieldSymbol::Year(Year::Calendar).idx(),
FieldLength::Four.idx(),
],
[0x00, 0x00, 0x7a],
[
0x80,
FieldSymbol::Second(Second::MillisInDay).idx(),
FieldLength::One.idx(),
],
],
)];
for (ref_pattern, ref_bytes) in samples {
let mut bytes: Vec<u8> = vec![];
for item in ref_pattern.iter() {
let ule = item.to_unaligned();
bytes.extend(ULE::slice_as_bytes(&[ule]));
}
let mut bytes2: Vec<u8> = vec![];
for seq in ref_bytes.iter() {
bytes2.extend_from_slice(seq);
}
assert!(PatternItemULE::validate_bytes(&bytes).is_ok());
assert_eq!(bytes, bytes2);
}
}
#[test]
fn test_generic_pattern_item_as_ule() {
let samples = [
(GenericPatternItem::Placeholder(4), [0x80, 0x00, 4]),
(GenericPatternItem::Placeholder(0), [0x80, 0x00, 0]),
(GenericPatternItem::from('z'), [0x00, 0x00, 0x7a]),
];
for (ref_pattern, ref_bytes) in samples {
let ule = ref_pattern.to_unaligned();
assert_eq!(ULE::slice_as_bytes(&[ule]), ref_bytes);
let pattern = GenericPatternItem::from_unaligned(ule);
assert_eq!(pattern, ref_pattern);
}
}
}