use std::collections::HashMap;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy)]
pub struct CuratedGloss {
pub gloss: &'static str,
pub note: Option<&'static str>,
}
pub fn vocab_key(surface: &str) -> String {
let mut out = String::new();
let mut marks: Vec<u32> = Vec::new();
for c in surface.chars() {
let u = c as u32;
let is_mark = (0x0591..=0x05C7).contains(&u);
if !is_mark {
flush_marks(&mut out, &mut marks);
out.push(c);
} else if u != 0x05BC && u != 0x05BD && !(0x0591..=0x05AF).contains(&u) {
marks.push(u);
}
}
flush_marks(&mut out, &mut marks);
out
}
fn flush_marks(out: &mut String, marks: &mut Vec<u32>) {
marks.sort_unstable();
for &m in marks.iter() {
if let Some(ch) = char::from_u32(m) {
out.push(ch);
}
}
marks.clear();
}
pub fn curated_gloss(surface: &str) -> Option<CuratedGloss> {
index().get(&vocab_key(surface)).copied()
}
pub fn curated_name(surface: &str) -> bool {
static NAMES: OnceLock<std::collections::HashSet<String>> = OnceLock::new();
NAMES
.get_or_init(|| {
crate::lexicon_overlay::word_glosses()
.filter(|entry| entry.is_name)
.map(|entry| vocab_key(entry.surface))
.collect()
})
.contains(&vocab_key(surface))
}
fn index() -> &'static HashMap<String, CuratedGloss> {
static INDEX: OnceLock<HashMap<String, CuratedGloss>> = OnceLock::new();
INDEX.get_or_init(|| {
crate::lexicon_overlay::word_glosses()
.filter(|entry| !entry.gloss.is_empty())
.map(|entry| {
(
vocab_key(entry.surface),
CuratedGloss {
gloss: entry.gloss,
note: entry.note,
},
)
})
.collect()
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vocab_key_drops_dagesh_and_sorts_marks() {
assert_eq!(vocab_key("בֶּן"), vocab_key("בֶן"));
assert_eq!(vocab_key("כָּל"), vocab_key("כָל"));
assert_eq!(vocab_key("דָּבָר"), vocab_key("דָּבָר\u{0591}"));
let nfc: String = ['\u{05DE}', '\u{05B6}', '\u{05BC}'].iter().collect();
let traditional: String = ['\u{05DE}', '\u{05BC}', '\u{05B6}'].iter().collect();
assert_eq!(vocab_key(&nfc), vocab_key(&traditional));
assert_ne!(vocab_key("עַם"), vocab_key("עִם"));
assert_ne!(vocab_key("שׁ"), vocab_key("שׂ"));
}
#[test]
fn curated_gloss_matches_dagesh_variants() {
assert!(curated_gloss("הַדָּבָר").is_some());
assert_eq!(curated_gloss("אֶת").unwrap().gloss, "←");
assert_eq!(curated_gloss("אֵת").unwrap().gloss, "←");
assert_eq!(curated_gloss("וְאֶת").unwrap().gloss, "and ←");
assert_eq!(curated_gloss("וְאֵת").unwrap().gloss, "and ←");
assert!(curated_gloss("כִּי").unwrap().note.is_none());
assert!(curated_gloss("בָּרָא").is_none());
}
}