use crate::bytes::{Cur, u32_at};
use crate::format::locale_key;
use crate::number::{Forms, Template, str8};
use crate::reader::Catalog;
pub(crate) const F_WIDTHS: u8 = 0x07;
pub(crate) const F_NAMES: u8 = 0x08;
pub(crate) const B_SAME: u8 = 0x01;
pub(crate) const B_NAME: u8 = 0x02;
pub(crate) const B_PER: u8 = 0x04;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u8)]
pub enum Width {
Long = 0,
Short = 1,
Narrow = 2,
}
impl Width {
pub const ALL: [Width; 3] = [Width::Long, Width::Short, Width::Narrow];
pub const fn bit(self) -> u8 {
1 << self as u8
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Units<'a> {
flags: u8,
per: [Option<Template<'a>>; 3],
index: &'a [u8],
records: &'a [u8],
}
impl<'a> Units<'a> {
pub fn parse(entry: &'a [u8]) -> Option<Units<'a>> {
let mut c = Cur::new(entry, 0);
let flags = c.u8()?;
if flags & !(F_WIDTHS | F_NAMES) != 0 {
return None;
}
let mut per = [None; 3];
for w in Width::ALL {
if flags & w.bit() != 0
&& let Some(slot) = per.get_mut(w as usize)
{
*slot = Some(Template::read(&mut c)?);
}
}
let lo = c.u8()?;
let hi = c.u8()?;
let n = usize::from(u16::from_le_bytes([lo, hi]));
let index = c.take(n.checked_mul(4)?)?;
let records = entry.get(c.pos()..)?;
Some(Units {
flags,
per,
index,
records,
})
}
pub fn of(catalog: &'a Catalog) -> Option<Units<'a>> {
Units::parse(catalog.locale_entry(locale_key::UNIT_DATA)?)
}
pub const fn has_width(&self, width: Width) -> bool {
self.flags & width.bit() != 0
}
pub const fn names_carried(&self) -> bool {
self.flags & F_NAMES != 0
}
pub fn per_pattern(&self, width: Width) -> Option<Template<'a>> {
self.per.get(width as usize).copied().flatten()
}
pub const fn len(&self) -> usize {
self.index.len() / 4
}
pub const fn is_empty(&self) -> bool {
self.index.is_empty()
}
fn offset(&self, i: usize) -> Option<usize> {
u32_at(self.index, i.checked_mul(4)?).map(|o| o as usize)
}
fn id_at(&self, i: usize) -> Option<&'a str> {
let mut c = Cur::new(self.records, self.offset(i)?);
str8(&mut c)
}
pub fn ids(&self) -> impl Iterator<Item = &'a str> + '_ {
(0..self.len()).filter_map(|i| self.id_at(i))
}
pub fn get(&self, id: &str) -> Option<Unit<'a>> {
let (mut lo, mut hi) = (0usize, self.len());
while lo < hi {
let mid = lo + (hi - lo) / 2;
match self.id_at(mid)?.as_bytes().cmp(id.as_bytes()) {
core::cmp::Ordering::Less => lo = mid + 1,
core::cmp::Ordering::Greater => hi = mid,
core::cmp::Ordering::Equal => {
let mut c = Cur::new(self.records, self.offset(mid)?);
return Unit::read(&mut c, self.flags);
}
}
}
None
}
pub fn is_valid(&self) -> bool {
let mut end = 0usize;
let mut prev: Option<&str> = None;
for i in 0..self.len() {
let Some(off) = self.offset(i) else {
return false;
};
if off != end {
return false;
}
let mut c = Cur::new(self.records, off);
let Some(u) = Unit::read(&mut c, self.flags) else {
return false;
};
if prev.is_some_and(|p| p.as_bytes() >= u.id.as_bytes()) {
return false;
}
prev = Some(u.id);
end = c.pos();
}
end == self.records.len()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct Block<'a> {
name: Option<&'a str>,
per_unit: Option<Template<'a>>,
patterns: Forms<'a>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Unit<'a> {
id: &'a str,
blocks: [Option<Block<'a>>; 3],
}
impl<'a> Unit<'a> {
fn read(c: &mut Cur<'a>, flags: u8) -> Option<Unit<'a>> {
let id = str8(c)?;
let mut blocks = [None; 3];
let mut prev: Option<Block<'a>> = None;
for w in Width::ALL {
if flags & w.bit() == 0 {
continue;
}
let head = c.u8()?;
if head & !(B_SAME | B_NAME | B_PER) != 0 {
return None;
}
let block = if head & B_SAME != 0 {
if head != B_SAME {
return None;
}
prev?
} else {
let name = if head & B_NAME != 0 {
if flags & F_NAMES == 0 {
return None;
}
let s = str8(c)?;
if s.bytes().any(|b| b < 0x20) {
return None;
}
Some(s)
} else {
None
};
let per_unit = if head & B_PER != 0 {
Some(Template::read(c)?)
} else {
None
};
Block {
name,
per_unit,
patterns: Forms::read(c, true)?,
}
};
if let Some(slot) = blocks.get_mut(w as usize) {
*slot = Some(block);
}
prev = Some(block);
}
Some(Unit { id, blocks })
}
fn block(&self, width: Width) -> Option<Block<'a>> {
self.blocks.get(width as usize).copied().flatten()
}
pub const fn id(&self) -> &'a str {
self.id
}
pub fn pattern(&self, width: Width, category: u8) -> Option<Template<'a>> {
self.block(width)?
.patterns
.get(category)
.map(Template::trusted)
}
pub fn has_patterns(&self, width: Width) -> bool {
self.block(width).is_some_and(|b| !b.patterns.is_empty())
}
pub fn display_name(&self, width: Width) -> Option<&'a str> {
self.block(width)?.name
}
pub fn per_unit_pattern(&self, width: Width) -> Option<Template<'a>> {
self.block(width)?.per_unit
}
}