use crate::config::Language;
const ENGLISH_G2: &str = include_str!("../../assets/character/english_g2.txt");
const LATIN_G2: &str = include_str!("../../assets/character/latin_g2.txt");
const ZH_SIM_G2: &str = include_str!("../../assets/character/zh_sim_g2.txt");
const JAPANESE_G2: &str = include_str!("../../assets/character/japanese_g2.txt");
const KOREAN_G2: &str = include_str!("../../assets/character/korean_g2.txt");
const CYRILLIC_G2: &str = include_str!("../../assets/character/cyrillic_g2.txt");
const TELUGU_G2: &str = include_str!("../../assets/character/telugu_g2.txt");
const KANNADA_G2: &str = include_str!("../../assets/character/kannada_g2.txt");
#[derive(Debug, Clone)]
pub(crate) struct Charset {
characters: Vec<char>,
}
impl Charset {
pub(crate) fn for_language(language: Language) -> Self {
let raw = match language {
Language::English => ENGLISH_G2,
Language::Latin => LATIN_G2,
Language::ChineseSimplified => ZH_SIM_G2,
Language::Japanese => JAPANESE_G2,
Language::Korean => KOREAN_G2,
Language::Cyrillic => CYRILLIC_G2,
Language::Telugu => TELUGU_G2,
Language::Kannada => KANNADA_G2,
};
let raw = raw.trim_end_matches(['\n', '\r']);
Self {
characters: raw.chars().collect(),
}
}
pub(crate) fn num_classes(&self) -> usize {
self.characters.len() + 1
}
pub(crate) fn char_at_class(&self, class: usize) -> Option<char> {
if class == 0 {
return None;
}
self.characters.get(class - 1).copied()
}
pub(crate) fn class_of(&self, ch: char) -> Option<usize> {
self.characters.iter().position(|&c| c == ch).map(|index| index + 1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_load_english_with_ninety_seven_classes() {
let charset = Charset::for_language(Language::English);
assert_eq!(charset.num_classes(), 97);
}
#[test]
fn should_map_class_zero_to_blank() {
let charset = Charset::for_language(Language::English);
assert_eq!(charset.char_at_class(0), None);
}
#[test]
fn should_map_first_class_to_first_character() {
let charset = Charset::for_language(Language::English);
assert_eq!(charset.char_at_class(1), Some('0'));
assert_eq!(charset.class_of('0'), Some(1));
}
#[test]
fn should_round_trip_class_and_character() {
let charset = Charset::for_language(Language::English);
for class in 1..charset.num_classes() {
let ch = charset.char_at_class(class).expect("class in range has a char");
assert_eq!(charset.class_of(ch), Some(class), "round-trip class {class}");
}
}
#[test]
fn should_return_none_for_out_of_range_class() {
let charset = Charset::for_language(Language::English);
assert_eq!(charset.char_at_class(charset.num_classes()), None);
}
#[test]
fn should_load_every_language_with_expected_class_counts() {
let expected = [
(Language::English, 97),
(Language::Latin, 352),
(Language::ChineseSimplified, 6719),
(Language::Japanese, 2215),
(Language::Korean, 1009),
(Language::Cyrillic, 208),
(Language::Telugu, 166),
(Language::Kannada, 168),
];
for (language, classes) in expected {
assert_eq!(Charset::for_language(language).num_classes(), classes, "{language:?}");
}
}
}