use std::sync::LazyLock;
use super::Rune;
use super::tables_gen as tables;
#[cfg(test)]
pub(crate) use super::tables_gen::VERSION;
pub(crate) const MAX_RUNE: Rune = 0x10FFFF;
pub(crate) struct Table {
pub(super) r16: &'static [u16],
pub(super) r32: &'static [u32],
}
impl Table {
pub(crate) fn contains(&self, r: Rune) -> bool {
if (r as u32) <= 0xFFFF {
let (ranges, c) = (self.r16, r as u16);
let (mut lo, mut hi) = (0, ranges.len() / 2);
while lo < hi {
let mid = (lo + hi) >> 1;
if c > ranges[2 * mid + 1] {
lo = mid + 1;
} else if c < ranges[2 * mid] {
hi = mid;
} else {
return true;
}
}
return false;
}
let (ranges, c) = (self.r32, r as u32);
let (mut lo, mut hi) = (0, ranges.len() / 2);
while lo < hi {
let mid = (lo + hi) >> 1;
if c > ranges[2 * mid + 1] {
lo = mid + 1;
} else if c < ranges[2 * mid] {
hi = mid;
} else {
return true;
}
}
false
}
pub(crate) fn append_ranges(&self, dst: &mut Vec<Rune>) {
dst.reserve(self.r16.len() + self.r32.len());
dst.extend(self.r16.iter().map(|&c| c as Rune));
let mut high = self.r32;
if !self.r16.is_empty() && !high.is_empty() && self.r16[self.r16.len() - 1] == 0xFFFF && high[0] == 0x10000 {
let last = dst.len() - 1;
dst[last] = high[1] as Rune;
high = &high[2..];
}
dst.extend(high.iter().map(|&c| c as Rune));
}
pub(crate) fn ranges(&self) -> Vec<Rune> {
let mut pairs = Vec::new();
self.append_ranges(&mut pairs);
pairs
}
}
pub(crate) struct Built(Vec<Rune>);
pub(crate) enum Ranges {
Table(&'static Table),
Built(&'static Built),
}
impl Ranges {
pub(crate) fn pairs(&self) -> Vec<Rune> {
match self {
Ranges::Table(t) => t.ranges(),
Ranges::Built(b) => b.0.clone(),
}
}
}
fn union(tables: &[&Table]) -> Built {
let mut all = Vec::new();
for t in tables {
t.append_ranges(&mut all);
}
let mut order: Vec<usize> = (0..all.len() / 2).collect();
order.sort_by_key(|&i| all[2 * i]);
let mut merged: Vec<Rune> = Vec::with_capacity(all.len());
for i in order {
let (lo, hi) = (all[2 * i], all[2 * i + 1]);
if let Some(last) = merged.last_mut()
&& lo <= *last + 1
{
if hi > *last {
*last = hi;
}
continue;
}
merged.push(lo);
merged.push(hi);
}
Built(merged)
}
fn complement(t: &Table) -> Built {
let pairs = t.ranges();
let mut inverse = Vec::with_capacity(pairs.len() + 2);
let mut next: Rune = 0;
for &[lo, hi] in pairs.as_chunks::<2>().0 {
if lo > next {
inverse.push(next);
inverse.push(lo - 1);
}
next = hi + 1;
}
if next <= MAX_RUNE {
inverse.push(next);
inverse.push(MAX_RUNE);
}
Built(inverse)
}
static LETTER: LazyLock<Built> =
LazyLock::new(|| union(&[&tables::GC_LU, &tables::GC_LL, &tables::GC_LT, &tables::GC_LM, &tables::GC_LO]));
static CASED_LETTER: LazyLock<Built> = LazyLock::new(|| union(&[&tables::GC_LU, &tables::GC_LL, &tables::GC_LT]));
static MARK: LazyLock<Built> = LazyLock::new(|| union(&[&tables::GC_MN, &tables::GC_MC, &tables::GC_ME]));
static NUMBER: LazyLock<Built> = LazyLock::new(|| union(&[&tables::GC_ND, &tables::GC_NL, &tables::GC_NO]));
static PUNCTUATION: LazyLock<Built> = LazyLock::new(|| {
union(&[
&tables::GC_PC,
&tables::GC_PD,
&tables::GC_PS,
&tables::GC_PE,
&tables::GC_PI,
&tables::GC_PF,
&tables::GC_PO,
])
});
static SYMBOL: LazyLock<Built> =
LazyLock::new(|| union(&[&tables::GC_SM, &tables::GC_SC, &tables::GC_SK, &tables::GC_SO]));
static SEPARATOR: LazyLock<Built> = LazyLock::new(|| union(&[&tables::GC_ZS, &tables::GC_ZL, &tables::GC_ZP]));
static OTHER: LazyLock<Built> =
LazyLock::new(|| union(&[&tables::GC_CC, &tables::GC_CF, &tables::GC_CS, &tables::GC_CO, &tables::GC_CN]));
static ASSIGNED: LazyLock<Built> = LazyLock::new(|| complement(&tables::GC_CN));
static UNKNOWN_SCRIPT: LazyLock<Built> = LazyLock::new(|| union(&[&tables::GC_CN, &tables::GC_CO, &tables::GC_CS]));
static KATAKANA_OR_HIRAGANA: Table = Table { r16: &[], r32: &[] };
pub(crate) fn category(name: &str) -> Option<Ranges> {
let built = match name {
"L" => &LETTER,
"LC" => &CASED_LETTER,
"M" => &MARK,
"N" => &NUMBER,
"P" => &PUNCTUATION,
"S" => &SYMBOL,
"Z" => &SEPARATOR,
"C" => &OTHER,
_ => return tables::category_table(name).map(Ranges::Table),
};
Some(Ranges::Built(built))
}
pub(crate) fn binary(name: &str) -> Option<Ranges> {
if name == "Assigned" {
return Some(Ranges::Built(&ASSIGNED));
}
tables::binary_table(name).map(Ranges::Table)
}
pub(crate) fn binary_table(name: &str) -> Option<&'static Table> {
tables::binary_table(name)
}
pub(crate) fn script(name: &str) -> Option<Ranges> {
match name {
"Unknown" | "Zzzz" => Some(Ranges::Built(&UNKNOWN_SCRIPT)),
"Katakana_Or_Hiragana" | "Hrkt" => Some(Ranges::Table(&KATAKANA_OR_HIRAGANA)),
_ => tables::script_table(name).map(Ranges::Table),
}
}
pub(crate) fn script_extensions(name: &str) -> Option<Ranges> {
match name {
"Unknown" | "Zzzz" => Some(Ranges::Built(&UNKNOWN_SCRIPT)),
"Katakana_Or_Hiragana" | "Hrkt" => Some(Ranges::Table(&KATAKANA_OR_HIRAGANA)),
_ => tables::script_extensions_table(name).map(Ranges::Table),
}
}
#[cfg(test)]
pub(crate) fn script_names() -> &'static [&'static [&'static str]] {
tables::SCRIPT_NAMES
}
#[cfg(test)]
pub(crate) fn binary_names() -> &'static [&'static str] {
tables::BINARY_NAMES
}
pub(crate) struct CaseRange {
pub(super) first: Rune,
pub(super) delta: i32,
pub(super) length: u16,
pub(super) mask: u16,
}
fn map_case(table: &[CaseRange], r: Rune) -> Rune {
let (mut lo, mut hi) = (0, table.len());
while lo < hi {
let mid = (lo + hi) >> 1;
let row = &table[mid];
if r < row.first {
hi = mid;
} else {
let offset = r - row.first;
if offset >= row.length as Rune {
lo = mid + 1;
} else if offset & row.mask as Rune == 0 {
return r + row.delta;
} else {
return r;
}
}
}
r
}
pub(crate) fn fold(r: Rune) -> Rune {
if r < 0x80 {
if (b'A' as Rune..=b'Z' as Rune).contains(&r) {
return r + (b'a' - b'A') as Rune;
}
return r;
}
map_case(tables::CASE_FOLDS, r)
}
pub(crate) fn to_upper(r: Rune) -> Rune {
if r < 0x80 {
if (b'a' as Rune..=b'z' as Rune).contains(&r) {
return r - (b'a' - b'A') as Rune;
}
return r;
}
map_case(tables::UPPER_CASES, r)
}
pub(crate) fn folding() -> Vec<Rune> {
let mut dst = Vec::new();
for row in tables::CASE_FOLDS {
for offset in 0..row.length as Rune {
if offset & row.mask as Rune == 0 && row.delta != 0 {
dst.push(row.first + offset);
}
}
}
dst
}