use icu_properties::CodePointSetData;
use icu_properties::props::{
EnumeratedProperty, GeneralCategory, GeneralCategoryGroup, WhiteSpace,
};
#[inline]
pub(crate) fn get_general_category(c: char) -> GeneralCategory {
GeneralCategory::for_char(c)
}
#[inline]
pub(crate) fn is_gc_letter(gc: GeneralCategory) -> bool {
GeneralCategoryGroup::Letter.contains(gc)
}
#[inline]
pub(crate) fn is_gc_number(gc: GeneralCategory) -> bool {
GeneralCategoryGroup::Number.contains(gc)
}
#[inline]
pub(crate) fn is_whitespace(c: char) -> bool {
static WS: std::sync::LazyLock<icu_properties::CodePointSetDataBorrowed<'static>> =
std::sync::LazyLock::new(CodePointSetData::new::<WhiteSpace>);
WS.contains(c)
}
#[inline]
pub(crate) fn is_letter(c: char) -> bool {
is_gc_letter(get_general_category(c))
}
#[inline]
pub(crate) fn is_number(c: char) -> bool {
is_gc_number(get_general_category(c))
}
#[inline]
pub(crate) fn is_other_complete(c: char) -> bool {
if c.is_ascii() {
return !c.is_ascii_alphanumeric() && !c.is_ascii_whitespace();
}
let gc = get_general_category(c);
!is_gc_letter(gc) && !is_gc_number(gc) && !is_whitespace(c)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum CharClass {
Letter = 0,
Number = 1,
Whitespace = 2,
Other = 3,
}
static CLASS_TABLE: std::sync::LazyLock<Box<[u8]>> = std::sync::LazyLock::new(build_class_table);
fn build_class_table() -> Box<[u8]> {
use icu_properties::CodePointMapData;
const N: usize = 0x110000;
let mut classes = vec![CharClass::Other as u8; N];
let gc = CodePointMapData::<GeneralCategory>::new();
for (group, class) in [
(GeneralCategoryGroup::Letter, CharClass::Letter),
(GeneralCategoryGroup::Number, CharClass::Number),
] {
for range in gc.iter_ranges_for_group(group) {
classes[*range.start() as usize..=*range.end() as usize].fill(class as u8);
}
}
for range in CodePointSetData::new::<WhiteSpace>().iter_ranges() {
classes[*range.start() as usize..=*range.end() as usize].fill(CharClass::Whitespace as u8);
}
classes
.as_chunks::<4>()
.0
.iter()
.map(|c| c[0] | (c[1] << 2) | (c[2] << 4) | (c[3] << 6))
.collect()
}
#[derive(Clone, Copy)]
pub(crate) struct ClassTable(&'static [u8]);
impl ClassTable {
#[inline]
pub(crate) fn get() -> Self {
Self(&CLASS_TABLE)
}
#[inline(always)]
pub(crate) fn class_of(self, cp: u32) -> CharClass {
debug_assert!(cp < 0x110000);
let byte = unsafe { *self.0.get_unchecked((cp >> 2) as usize) };
match (byte >> ((cp & 3) << 1)) & 3 {
0 => CharClass::Letter,
1 => CharClass::Number,
2 => CharClass::Whitespace,
_ => CharClass::Other,
}
}
}
#[inline(always)]
pub(crate) fn class_of(cp: u32) -> CharClass {
ClassTable::get().class_of(cp)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum DsCharClass {
Letter = 0,
Number = 1,
Whitespace = 2,
Mark = 3,
PunctSym = 4,
Other = 5,
}
#[inline(always)]
pub(crate) fn class_of_marks_join(cp: u32) -> CharClass {
DsClassTable::get().class_of_marks_join(cp)
}
static DS_CLASS_TABLE: std::sync::LazyLock<Box<[u8]>> =
std::sync::LazyLock::new(build_ds_class_table);
fn build_ds_class_table() -> Box<[u8]> {
use icu_properties::CodePointMapData;
const N: usize = 0x110000;
let mut classes = vec![DsCharClass::Other as u8; N];
let gc = CodePointMapData::<GeneralCategory>::new();
for (group, class) in [
(GeneralCategoryGroup::Letter, DsCharClass::Letter),
(GeneralCategoryGroup::Number, DsCharClass::Number),
(GeneralCategoryGroup::Mark, DsCharClass::Mark),
(GeneralCategoryGroup::Punctuation, DsCharClass::PunctSym),
(GeneralCategoryGroup::Symbol, DsCharClass::PunctSym),
] {
for range in gc.iter_ranges_for_group(group) {
classes[*range.start() as usize..=*range.end() as usize].fill(class as u8);
}
}
for range in CodePointSetData::new::<WhiteSpace>().iter_ranges() {
classes[*range.start() as usize..=*range.end() as usize]
.fill(DsCharClass::Whitespace as u8);
}
classes
.as_chunks::<2>()
.0
.iter()
.map(|c| c[0] | (c[1] << 4))
.collect()
}
#[derive(Clone, Copy)]
pub(crate) struct DsClassTable(&'static [u8]);
impl DsClassTable {
#[inline]
pub(crate) fn get() -> Self {
Self(&DS_CLASS_TABLE)
}
#[inline(always)]
pub(crate) fn ds_class_of(self, cp: u32) -> DsCharClass {
debug_assert!(cp < 0x110000);
let byte = unsafe { *self.0.get_unchecked((cp >> 1) as usize) };
match (byte >> ((cp & 1) << 2)) & 0xF {
0 => DsCharClass::Letter,
1 => DsCharClass::Number,
2 => DsCharClass::Whitespace,
3 => DsCharClass::Mark,
4 => DsCharClass::PunctSym,
_ => DsCharClass::Other,
}
}
#[inline(always)]
pub(crate) fn class_of_marks_join(self, cp: u32) -> CharClass {
match self.ds_class_of(cp) {
DsCharClass::Letter | DsCharClass::Mark => CharClass::Letter,
DsCharClass::Number => CharClass::Number,
DsCharClass::Whitespace => CharClass::Whitespace,
DsCharClass::PunctSym | DsCharClass::Other => CharClass::Other,
}
}
}
#[inline(always)]
pub(crate) fn ds_class_of(cp: u32) -> DsCharClass {
DsClassTable::get().ds_class_of(cp)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum O200kCharClass {
Upper = 0,
Lower = 1,
Caseless = 2,
Mark = 3,
Number = 4,
Whitespace = 5,
Other = 6,
}
static O200K_CLASS_TABLE: std::sync::LazyLock<Box<[u8]>> =
std::sync::LazyLock::new(build_o200k_class_table);
fn build_o200k_class_table() -> Box<[u8]> {
pack_nibbles(&o200k_classes_unpacked())
}
fn o200k_classes_unpacked() -> Vec<u8> {
use icu_properties::CodePointMapData;
const N: usize = 0x110000;
let mut classes = vec![O200kCharClass::Other as u8; N];
let gc = CodePointMapData::<GeneralCategory>::new();
for (category, class) in [
(GeneralCategory::UppercaseLetter, O200kCharClass::Upper),
(GeneralCategory::TitlecaseLetter, O200kCharClass::Upper),
(GeneralCategory::LowercaseLetter, O200kCharClass::Lower),
(GeneralCategory::ModifierLetter, O200kCharClass::Caseless),
(GeneralCategory::OtherLetter, O200kCharClass::Caseless),
] {
for range in gc.iter_ranges_for_value(category) {
classes[*range.start() as usize..=*range.end() as usize].fill(class as u8);
}
}
for (group, class) in [
(GeneralCategoryGroup::Mark, O200kCharClass::Mark),
(GeneralCategoryGroup::Number, O200kCharClass::Number),
] {
for range in gc.iter_ranges_for_group(group) {
classes[*range.start() as usize..=*range.end() as usize].fill(class as u8);
}
}
for range in CodePointSetData::new::<WhiteSpace>().iter_ranges() {
classes[*range.start() as usize..=*range.end() as usize]
.fill(O200kCharClass::Whitespace as u8);
}
classes
}
fn pack_nibbles(classes: &[u8]) -> Box<[u8]> {
classes
.as_chunks::<2>()
.0
.iter()
.map(|c| c[0] | (c[1] << 4))
.collect()
}
#[inline(always)]
pub(crate) fn o200k_class_of(cp: u32) -> O200kCharClass {
debug_assert!(cp < 0x110000);
let byte = unsafe { *O200K_CLASS_TABLE.get_unchecked((cp >> 1) as usize) };
match (byte >> ((cp & 1) << 2)) & 0xF {
0 => O200kCharClass::Upper,
1 => O200kCharClass::Lower,
2 => O200kCharClass::Caseless,
3 => O200kCharClass::Mark,
4 => O200kCharClass::Number,
5 => O200kCharClass::Whitespace,
_ => O200kCharClass::Other,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum KimiCharClass {
Upper = 0,
Lower = 1,
Caseless = 2,
Mark = 3,
Number = 4,
Whitespace = 5,
Other = 6,
Han = 7,
HanNumber = 8,
HanOther = 9,
}
impl KimiCharClass {
#[inline(always)]
pub(crate) fn base(self) -> O200kCharClass {
match self {
KimiCharClass::Upper => O200kCharClass::Upper,
KimiCharClass::Lower => O200kCharClass::Lower,
KimiCharClass::Caseless | KimiCharClass::Han => O200kCharClass::Caseless,
KimiCharClass::Mark => O200kCharClass::Mark,
KimiCharClass::Number | KimiCharClass::HanNumber => O200kCharClass::Number,
KimiCharClass::Whitespace => O200kCharClass::Whitespace,
KimiCharClass::Other | KimiCharClass::HanOther => O200kCharClass::Other,
}
}
#[inline(always)]
pub(crate) fn is_han(self) -> bool {
self as u8 >= KimiCharClass::Han as u8
}
}
static KIMI_CLASS_TABLE: std::sync::LazyLock<Box<[u8]>> =
std::sync::LazyLock::new(build_kimi_class_table);
fn build_kimi_class_table() -> Box<[u8]> {
use icu_properties::CodePointMapData;
use icu_properties::props::Script;
let mut classes = o200k_classes_unpacked();
let script = CodePointMapData::<Script>::new();
for range in script.iter_ranges_for_value(Script::Han) {
for cp in *range.start()..=*range.end() {
let slot = &mut classes[cp as usize];
*slot = match *slot {
c if c == O200kCharClass::Number as u8 => KimiCharClass::HanNumber as u8,
c if c == O200kCharClass::Other as u8 || c == O200kCharClass::Mark as u8 => {
KimiCharClass::HanOther as u8
}
_ => KimiCharClass::Han as u8,
};
}
}
pack_nibbles(&classes)
}
#[inline(always)]
pub(crate) fn kimi_class_of(cp: u32) -> KimiCharClass {
debug_assert!(cp < 0x110000);
let byte = unsafe { *KIMI_CLASS_TABLE.get_unchecked((cp >> 1) as usize) };
match (byte >> ((cp & 1) << 2)) & 0xF {
0 => KimiCharClass::Upper,
1 => KimiCharClass::Lower,
2 => KimiCharClass::Caseless,
3 => KimiCharClass::Mark,
4 => KimiCharClass::Number,
5 => KimiCharClass::Whitespace,
6 => KimiCharClass::Other,
7 => KimiCharClass::Han,
8 => KimiCharClass::HanNumber,
_ => KimiCharClass::HanOther,
}
}
#[inline(always)]
pub(crate) fn is_deepseek_cjk(cp: u32) -> bool {
(0x4E00..=0x9FA5).contains(&cp) || (0x3040..=0x30FF).contains(&cp)
}