#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Kind {
Initial,
Medial,
Rime,
}
pub const DACHEN: [(u8, char, Kind); 37] = [
(b'1', 'ㄅ', Kind::Initial),
(b'q', 'ㄆ', Kind::Initial),
(b'a', 'ㄇ', Kind::Initial),
(b'z', 'ㄈ', Kind::Initial),
(b'2', 'ㄉ', Kind::Initial),
(b'w', 'ㄊ', Kind::Initial),
(b's', 'ㄋ', Kind::Initial),
(b'x', 'ㄌ', Kind::Initial),
(b'e', 'ㄍ', Kind::Initial),
(b'd', 'ㄎ', Kind::Initial),
(b'c', 'ㄏ', Kind::Initial),
(b'r', 'ㄐ', Kind::Initial),
(b'f', 'ㄑ', Kind::Initial),
(b'v', 'ㄒ', Kind::Initial),
(b'5', 'ㄓ', Kind::Initial),
(b't', 'ㄔ', Kind::Initial),
(b'g', 'ㄕ', Kind::Initial),
(b'b', 'ㄖ', Kind::Initial),
(b'y', 'ㄗ', Kind::Initial),
(b'h', 'ㄘ', Kind::Initial),
(b'n', 'ㄙ', Kind::Initial),
(b'u', 'ㄧ', Kind::Medial),
(b'j', 'ㄨ', Kind::Medial),
(b'm', 'ㄩ', Kind::Medial),
(b'8', 'ㄚ', Kind::Rime),
(b'i', 'ㄛ', Kind::Rime),
(b'k', 'ㄜ', Kind::Rime),
(b',', 'ㄝ', Kind::Rime),
(b'9', 'ㄞ', Kind::Rime),
(b'o', 'ㄟ', Kind::Rime),
(b'l', 'ㄠ', Kind::Rime),
(b'.', 'ㄡ', Kind::Rime),
(b'0', 'ㄢ', Kind::Rime),
(b'p', 'ㄣ', Kind::Rime),
(b';', 'ㄤ', Kind::Rime),
(b'/', 'ㄥ', Kind::Rime),
(b'-', 'ㄦ', Kind::Rime),
];
pub const DACHEN_SYMBOLS: &str = "1234567890,./;- ";
pub const TONE_KEYS: [u8; 5] = *b" 6347";
pub const TONE_MARKS: [&str; 5] = ["", "ˊ", "ˇ", "ˋ", "˙"];
pub const IDLE_SYMBOLS: &str = "125890,./;-";
pub const SHIFT_PUNCTUATION: [(u8, char); 8] = [
(b'<', ','),
(b'>', '。'),
(b'?', '?'),
(b':', ':'),
(b'[', '「'),
(b']', '」'),
(b'{', '『'),
(b'}', '』'),
];
pub fn symbol(key: u8) -> Option<(char, Kind)> {
DACHEN
.iter()
.find(|(dachen_key, _, _)| *dachen_key == key)
.map(|(_, symbol, kind)| (*symbol, *kind))
}
pub fn tone_mark(key: u8) -> Option<&'static str> {
TONE_KEYS
.iter()
.position(|tone_key| *tone_key == key)
.map(|index| TONE_MARKS[index])
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
#[test]
fn dachen_has_37_unique_keys_and_symbols() {
let keys: HashSet<u8> = DACHEN.iter().map(|(key, _, _)| *key).collect();
let symbols: HashSet<char> = DACHEN.iter().map(|(_, symbol, _)| *symbol).collect();
assert_eq!(keys.len(), 37);
assert_eq!(symbols.len(), 37);
let count = |kind| DACHEN.iter().filter(|entry| entry.2 == kind).count();
assert_eq!(
(count(Kind::Initial), count(Kind::Medial), count(Kind::Rime)),
(21, 3, 13)
);
let block: HashSet<char> = ('\u{3105}'..='\u{3129}').collect();
assert_eq!(symbols, block);
}
#[test]
fn tone_keys_are_not_phonetic_keys() {
for key in TONE_KEYS {
assert_eq!(symbol(key), None, "{}", key as char);
}
assert_eq!(tone_mark(b' '), Some(""));
assert_eq!(tone_mark(b'6'), Some("ˊ"));
assert_eq!(tone_mark(b'7'), Some("˙"));
assert_eq!(tone_mark(b'1'), None);
assert_eq!(symbol(b'1'), Some(('ㄅ', Kind::Initial)));
assert_eq!(symbol(b'-'), Some(('ㄦ', Kind::Rime)));
}
#[test]
fn symbol_strings_follow_the_layout() {
let non_letters: HashSet<u8> = DACHEN
.iter()
.map(|(key, _, _)| *key)
.filter(|key| !key.is_ascii_alphabetic())
.chain(TONE_KEYS)
.collect();
assert_eq!(DACHEN_SYMBOLS.len(), non_letters.len());
assert!(DACHEN_SYMBOLS.bytes().all(|key| non_letters.contains(&key)));
let idle: String = DACHEN_SYMBOLS
.chars()
.filter(|key| !TONE_KEYS.contains(&(*key as u8)))
.collect();
assert_eq!(IDLE_SYMBOLS, idle);
for excluded in *b"3467 " {
assert!(!IDLE_SYMBOLS.as_bytes().contains(&excluded));
}
assert!(IDLE_SYMBOLS.bytes().all(|key| symbol(key).is_some()));
}
#[test]
fn shift_punctuation_keys_are_not_phonetic() {
for (key, _) in SHIFT_PUNCTUATION {
assert_eq!(symbol(key), None);
assert_eq!(tone_mark(key), None);
}
}
}