use std::collections::HashSet;
use std::sync::OnceLock;
use crate::pinyin::syllables::intact_pinyin_set;
use crate::types::ShuangpinProfileKind;
#[derive(Debug, PartialEq, Eq)]
pub struct ShuangpinProfile {
pub kind: ShuangpinProfileKind,
pub initials: &'static [(&'static str, &'static str)],
pub zero_initials: &'static [(&'static str, &'static str)],
pub finals: &'static [(&'static str, &'static str)],
}
static XIAOHE: ShuangpinProfile = ShuangpinProfile {
kind: ShuangpinProfileKind::Xiaohe,
initials: &[("sh", "u"), ("ch", "i"), ("zh", "v")],
zero_initials: &[
("a", "aa"),
("ai", "ai"),
("an", "an"),
("ao", "ao"),
("ang", "ah"),
("e", "ee"),
("ei", "ei"),
("en", "en"),
("eng", "eg"),
("er", "er"),
("o", "oo"),
("ou", "ou"),
],
finals: &[
("iu", "q"),
("ei", "w"),
("e", "e"),
("uan", "r"),
("ue", "t"),
("ve", "t"),
("un", "y"),
("u", "u"),
("i", "i"),
("uo", "o"),
("o", "o"),
("ie", "p"),
("a", "a"),
("ong", "s"),
("iong", "s"),
("ai", "d"),
("en", "f"),
("eng", "g"),
("ang", "h"),
("an", "j"),
("uai", "k"),
("ing", "k"),
("uang", "l"),
("iang", "l"),
("ou", "z"),
("ua", "x"),
("ia", "x"),
("ao", "c"),
("ui", "v"),
("v", "v"),
("in", "b"),
("iao", "n"),
("ian", "m"),
],
};
static ZIRANMA: ShuangpinProfile = ShuangpinProfile {
kind: ShuangpinProfileKind::Ziranma,
initials: &[("sh", "u"), ("ch", "i"), ("zh", "v")],
zero_initials: &[
("a", "aa"),
("ai", "ai"),
("an", "an"),
("ao", "ao"),
("ang", "ah"),
("e", "ee"),
("ei", "ei"),
("en", "en"),
("eng", "eg"),
("er", "er"),
("o", "oo"),
("ou", "ou"),
],
finals: &[
("iu", "q"),
("ia", "w"),
("ua", "w"),
("e", "e"),
("uan", "r"),
("ue", "t"),
("ve", "t"),
("ing", "y"),
("uai", "y"),
("u", "u"),
("i", "i"),
("o", "o"),
("uo", "o"),
("un", "p"),
("a", "a"),
("iong", "s"),
("ong", "s"),
("iang", "d"),
("uang", "d"),
("en", "f"),
("eng", "g"),
("ang", "h"),
("an", "j"),
("ao", "k"),
("ai", "l"),
("ei", "z"),
("ie", "x"),
("iao", "c"),
("ui", "v"),
("v", "v"),
("ou", "b"),
("in", "n"),
("ian", "m"),
],
};
static SHOUDAO: ShuangpinProfile = ShuangpinProfile {
kind: ShuangpinProfileKind::Shoudao,
initials: &[("sh", "e"), ("ch", "i"), ("zh", "v")],
zero_initials: &[
("a", "aa"),
("ai", "ai"),
("an", "an"),
("ao", "ao"),
("ang", "ay"),
("e", "ue"),
("ei", "ui"),
("en", "en"),
("eng", "uf"),
("er", "er"),
("o", "oo"),
("ou", "ou"),
],
finals: &[
("iu", "q"),
("ua", "w"),
("e", "e"),
("ie", "r"),
("uan", "t"),
("ang", "y"),
("u", "u"),
("i", "i"),
("o", "o"),
("uo", "o"),
("iao", "p"),
("a", "a"),
("ou", "s"),
("ao", "d"),
("eng", "f"),
("uai", "g"),
("ing", "g"),
("ong", "h"),
("iong", "h"),
("an", "j"),
("en", "k"),
("ia", "k"),
("ai", "l"),
("ue", "l"),
("un", "z"),
("iang", "x"),
("uang", "x"),
("in", "c"),
("v", "v"),
("ui", "v"),
("ve", "b"),
("ian", "n"),
("ei", "m"),
],
};
static MICROSOFT: ShuangpinProfile = ShuangpinProfile {
kind: ShuangpinProfileKind::Microsoft,
initials: &[("sh", "u"), ("ch", "i"), ("zh", "v")],
zero_initials: &[
("a", "oa"),
("ai", "ol"),
("an", "oj"),
("ang", "oh"),
("ao", "ok"),
("e", "oe"),
("ei", "oz"),
("en", "of"),
("eng", "og"),
("er", "or"),
("o", "oo"),
("ou", "ob"),
],
finals: &[
("iu", "q"),
("ia", "w"),
("ua", "w"),
("e", "e"),
("uan", "r"),
("ue", "t"),
("ve", "v"),
("uai", "y"),
("v", "y"),
("u", "u"),
("i", "i"),
("o", "o"),
("uo", "o"),
("un", "p"),
("a", "a"),
("iong", "s"),
("ong", "s"),
("iang", "d"),
("uang", "d"),
("en", "f"),
("eng", "g"),
("ang", "h"),
("an", "j"),
("ao", "k"),
("ai", "l"),
("ing", ";"),
("ei", "z"),
("ie", "x"),
("iao", "c"),
("ui", "v"),
("ou", "b"),
("in", "n"),
("ian", "m"),
],
};
static SMART_ABC: ShuangpinProfile = ShuangpinProfile {
kind: ShuangpinProfileKind::SmartAbc,
initials: &[("zh", "a"), ("ch", "e"), ("sh", "v")],
zero_initials: &[
("a", "oa"),
("ai", "ol"),
("an", "oj"),
("ang", "oh"),
("ao", "ok"),
("e", "oe"),
("ei", "oq"),
("en", "of"),
("eng", "og"),
("er", "or"),
("o", "oo"),
("ou", "ob"),
],
finals: &[
("a", "a"),
("o", "o"),
("e", "e"),
("i", "i"),
("u", "u"),
("v", "v"),
("ai", "l"),
("ei", "q"),
("ui", "m"),
("ao", "k"),
("ou", "b"),
("iu", "r"),
("ie", "x"),
("ve", "m"),
("ue", "m"),
("an", "j"),
("en", "f"),
("in", "c"),
("un", "n"),
("ang", "h"),
("eng", "g"),
("ing", "y"),
("ong", "s"),
("ia", "d"),
("ua", "d"),
("uo", "o"),
("uai", "c"),
("ian", "w"),
("uan", "p"),
("iao", "z"),
("iang", "t"),
("uang", "t"),
("iong", "s"),
],
};
impl ShuangpinProfile {
pub fn uses_semicolon_key(&self) -> bool {
self.finals.iter().any(|(_, key)| *key == ";")
|| self
.zero_initials
.iter()
.any(|(_, code)| code.ends_with(';'))
}
}
pub fn default_profile() -> &'static ShuangpinProfile {
&XIAOHE
}
pub fn profile(kind: ShuangpinProfileKind) -> Option<&'static ShuangpinProfile> {
match kind {
ShuangpinProfileKind::Xiaohe => Some(&XIAOHE),
ShuangpinProfileKind::Ziranma => Some(&ZIRANMA),
ShuangpinProfileKind::Shoudao => Some(&SHOUDAO),
ShuangpinProfileKind::Microsoft => Some(&MICROSOFT),
ShuangpinProfileKind::SmartAbc => Some(&SMART_ABC),
ShuangpinProfileKind::Custom => None,
}
}
const EXCLUDED_SYLLABLES: [&str; 12] = [
"chua", "fiao", "jve", "lo", "lue", "nou", "nue", "nun", "qve", "xve", "yve", "zhei",
];
pub fn accepted_syllables() -> &'static HashSet<&'static str> {
static SET: OnceLock<HashSet<&'static str>> = OnceLock::new();
SET.get_or_init(|| {
let mut set = intact_pinyin_set().clone();
set.insert("eng");
for syllable in EXCLUDED_SYLLABLES {
set.remove(syllable);
}
set
})
}
#[cfg(test)]
mod tests {
use super::*;
fn key_of(table: &'static [(&'static str, &'static str)], unit: &str) -> Option<&'static str> {
table
.iter()
.find(|(source, _)| *source == unit)
.map(|(_, key)| *key)
}
#[test]
fn smart_abc_uses_its_initials_finals_and_zero_initial_codes() {
let source = ShuangpinProfileKind::from_name("smart_abc")
.and_then(profile)
.expect("智能 ABC 应有内置键位表");
for (code, syllable) in [
("ai", "zhi"),
("ei", "chi"),
("vi", "shi"),
("vk", "shao"),
("ap", "zhuan"),
("et", "chuang"),
("jy", "jing"),
("nw", "nian"),
("nz", "niao"),
("lc", "lin"),
("hc", "huai"),
("lm", "lve"),
("vm", "shui"),
("xm", "xue"),
("lr", "liu"),
("oa", "a"),
("ol", "ai"),
("oj", "an"),
("oh", "ang"),
("ok", "ao"),
("oe", "e"),
("oq", "ei"),
("of", "en"),
("og", "eng"),
("or", "er"),
("oo", "o"),
("ob", "ou"),
] {
assert_eq!(
super::super::utils::cvt_single_sp_to_pinyin(code, source),
syllable,
"{code}"
);
}
assert!(!source.uses_semicolon_key());
}
#[test]
fn profiles_select_by_kind() {
for kind in [
ShuangpinProfileKind::Xiaohe,
ShuangpinProfileKind::Ziranma,
ShuangpinProfileKind::Shoudao,
ShuangpinProfileKind::Microsoft,
ShuangpinProfileKind::SmartAbc,
] {
let selected = profile(kind).unwrap();
assert_eq!(selected.kind, kind);
assert_eq!(selected.zero_initials.len(), 12);
assert_eq!(selected.finals.len(), 33);
assert_eq!(
selected.uses_semicolon_key(),
kind == ShuangpinProfileKind::Microsoft
);
}
assert!(profile(ShuangpinProfileKind::Custom).is_none());
}
#[test]
fn tables_keep_the_profile_specific_keys() {
let shoudao = profile(ShuangpinProfileKind::Shoudao).unwrap();
assert_eq!(key_of(shoudao.initials, "sh"), Some("e"));
assert_eq!(key_of(shoudao.zero_initials, "ang"), Some("ay"));
assert_eq!(key_of(shoudao.zero_initials, "e"), Some("ue"));
assert_eq!(key_of(shoudao.zero_initials, "ei"), Some("ui"));
assert_eq!(key_of(shoudao.zero_initials, "eng"), Some("uf"));
let microsoft = profile(ShuangpinProfileKind::Microsoft).unwrap();
assert_eq!(key_of(microsoft.finals, "ing"), Some(";"));
assert_eq!(key_of(microsoft.finals, "v"), Some("y"));
assert_eq!(key_of(microsoft.finals, "ve"), Some("v"));
assert_eq!(key_of(microsoft.zero_initials, "a"), Some("oa"));
let xiaohe = profile(ShuangpinProfileKind::Xiaohe).unwrap();
assert_eq!(key_of(xiaohe.initials, "zh"), Some("v"));
assert_eq!(key_of(xiaohe.finals, "ing"), Some("k"));
}
#[test]
fn accepted_set_keeps_yo_and_den_and_drops_the_historical_exclusions() {
let set = accepted_syllables();
assert!(set.contains("yo"));
assert!(set.contains("den"));
assert!(set.contains("eng"));
for excluded in EXCLUDED_SYLLABLES {
assert!(!set.contains(excluded), "{excluded} is accepted");
}
assert!(set.contains("shi"));
assert!(set.contains("lve"));
}
}