use std::sync::OnceLock;
use regex_syntax::hir::{Class, HirKind};
struct ClassSet {
bits: Box<[u64]>,
}
impl ClassSet {
fn from_pattern(pattern: &str) -> Self {
let hir = regex_syntax::parse(pattern)
.unwrap_or_else(|e| panic!("invalid class pattern {pattern:?}: {e}"));
let mut bits = vec![0u64; 0x110000_usize.div_ceil(64)].into_boxed_slice();
match hir.kind() {
HirKind::Class(Class::Unicode(cls)) => {
for range in cls.iter() {
for cp in (range.start() as u32)..=(range.end() as u32) {
bits[(cp >> 6) as usize] |= 1u64 << (cp & 63);
}
}
}
other => panic!("pattern {pattern:?} is not a unicode class: {other:?}"),
}
Self { bits }
}
#[inline(always)]
fn contains(&self, cp: u32) -> bool {
(self.bits[(cp >> 6) as usize] >> (cp & 63)) & 1 != 0
}
}
pub struct Tables {
letter: ClassSet, number: ClassSet, ws: ClassSet, han: ClassSet, ugroup: ClassSet, lgroup: ClassSet, }
impl Tables {
#[inline(always)]
pub fn is_letter(&self, cp: u32) -> bool {
self.letter.contains(cp)
}
#[inline(always)]
pub fn is_number(&self, cp: u32) -> bool {
self.number.contains(cp)
}
#[inline(always)]
pub fn is_ws(&self, cp: u32) -> bool {
self.ws.contains(cp)
}
#[inline(always)]
pub fn is_han(&self, cp: u32) -> bool {
self.han.contains(cp)
}
#[inline(always)]
pub fn is_ugroup(&self, cp: u32) -> bool {
self.ugroup.contains(cp)
}
#[inline(always)]
pub fn is_lgroup(&self, cp: u32) -> bool {
self.lgroup.contains(cp)
}
}
pub fn tables() -> &'static Tables {
static TABLES: OnceLock<Tables> = OnceLock::new();
TABLES.get_or_init(|| Tables {
letter: ClassSet::from_pattern(r"\p{L}"),
number: ClassSet::from_pattern(r"\p{N}"),
ws: ClassSet::from_pattern(r"\s"),
han: ClassSet::from_pattern(r"\p{Han}"),
ugroup: ClassSet::from_pattern(r"[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]"),
lgroup: ClassSet::from_pattern(r"[\p{Ll}\p{Lm}\p{Lo}\p{M}]"),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii_agrees() {
let t = tables();
for b in b'A'..=b'Z' {
assert!(t.is_letter(b as u32) && t.is_ugroup(b as u32) && !t.is_lgroup(b as u32));
}
for b in b'a'..=b'z' {
assert!(t.is_letter(b as u32) && t.is_lgroup(b as u32) && !t.is_ugroup(b as u32));
}
for b in b'0'..=b'9' {
assert!(t.is_number(b as u32) && !t.is_letter(b as u32));
}
for &b in &[b'\t', b'\n', b'\r', b' ', 0x0b, 0x0c] {
assert!(t.is_ws(b as u32));
}
}
#[test]
fn unicode_samples() {
let t = tables();
assert!(t.is_han('中' as u32) && t.is_letter('中' as u32));
assert!(!t.is_han('a' as u32) && !t.is_han('あ' as u32));
assert!(t.is_letter('é' as u32) && t.is_lgroup('é' as u32) && !t.is_ugroup('é' as u32));
assert!(t.is_ugroup('É' as u32) && !t.is_lgroup('É' as u32));
assert!(t.is_ugroup('中' as u32) && t.is_lgroup('中' as u32));
assert!(t.is_ugroup('\u{05e2}' as u32) && t.is_lgroup('\u{05e2}' as u32)); assert!(t.is_ugroup('\u{0301}' as u32) && t.is_lgroup('\u{0301}' as u32));
assert!(t.is_ws('\u{3000}' as u32) && t.is_ws('\u{00a0}' as u32)); assert!(t.is_number('٣' as u32)); assert!(t.is_ugroup('Dž' as u32) && !t.is_lgroup('Dž' as u32)); }
}