use crate::keyevent::CskkKeyEvent;
use crate::rule::CskkRule;
use crate::skk_modes::{CommaStyle, PeriodStyle};
use sequence_trie::SequenceTrie;
use std::collections::HashMap;
use xkbcommon::xkb::keysyms;
use xkbcommon::xkb::Keysym;
pub(crate) type Converted = String;
pub(crate) type CarryOver = Vec<Keysym>;
#[derive(Clone, Debug)]
pub(crate) struct KanaBuilder {
process_map: SequenceTrie<Keysym, (Converted, CarryOver)>,
}
impl KanaBuilder {
pub(crate) fn next_unprocessed_state(
&self,
key_event: &CskkKeyEvent,
unprocessed: &[Keysym],
) -> Vec<Keysym> {
let mut combined = vec![];
combined.extend_from_slice(unprocessed);
combined.push(key_event.get_symbol());
if self.can_continue(key_event, unprocessed) {
Self::combine_raw(key_event, unprocessed)
} else if self.can_continue_lower(key_event, unprocessed) {
Self::combine_lower(key_event, unprocessed)
} else if self.can_continue(key_event, &[]) {
vec![key_event.get_symbol()]
} else {
vec![]
}
}
pub(crate) fn convert_greedy(&self, kana: &[Keysym]) -> Option<&(Converted, CarryOver)> {
self.process_map.get(kana)
}
pub(crate) fn convert_non_partial(&self, kana: &[Keysym]) -> Option<&(Converted, CarryOver)> {
if let Some(node) = self.process_map.get_node(kana) {
if node.is_leaf() {
return node.value();
}
}
None
}
pub(crate) fn convert_periods(
&self,
kana: &char,
period_style: PeriodStyle,
comma_style: CommaStyle,
) -> Option<Converted> {
if *kana == '.' {
match period_style {
PeriodStyle::PeriodJa => Some("。".to_string()),
PeriodStyle::PeriodEn => Some(".".to_string()),
}
} else if *kana == ',' {
match comma_style {
CommaStyle::CommaJa => Some("、".to_string()),
CommaStyle::CommaEn => Some(",".to_string()),
}
} else {
None
}
}
pub(crate) fn combine_lower(key_event: &CskkKeyEvent, unprocessed: &[Keysym]) -> Vec<Keysym> {
let mut combined = vec![];
combined.extend_from_slice(unprocessed);
combined.push(Self::uncapitalize(key_event.get_symbol()));
combined
}
pub(crate) fn combine_raw(key_event: &CskkKeyEvent, unprocessed: &[Keysym]) -> Vec<Keysym> {
let mut combined = vec![];
combined.extend_from_slice(unprocessed);
combined.push(key_event.get_symbol());
combined
}
fn uncapitalize(keysym: Keysym) -> Keysym {
if (keysyms::KEY_A..=keysyms::KEY_Z).contains(&keysym) {
keysym + 0x0020
} else {
keysym
}
}
pub(crate) fn can_continue(&self, key_event: &CskkKeyEvent, unprocessed: &[Keysym]) -> bool {
self.get_node_raw(key_event, unprocessed).is_some()
}
pub(crate) fn can_continue_lower(
&self,
key_event: &CskkKeyEvent,
unprocessed: &[Keysym],
) -> bool {
self.get_node_lower(key_event, unprocessed).is_some()
}
fn get_node_lower(
&self,
key_event: &CskkKeyEvent,
unprocessed: &[Keysym],
) -> Option<&SequenceTrie<Keysym, (Converted, CarryOver)>> {
let key = KanaBuilder::combine_lower(key_event, unprocessed);
self.process_map.get_node(&key)
}
fn get_node_raw(
&self,
key_event: &CskkKeyEvent,
unprocessed: &[Keysym],
) -> Option<&SequenceTrie<Keysym, (Converted, CarryOver)>> {
let key = KanaBuilder::combine_raw(key_event, unprocessed);
self.process_map.get_node(&key)
}
fn converter_from_hashmap(map: &HashMap<String, (String, String)>) -> Self {
let mut process_map = SequenceTrie::new();
for (k, (carry, conv)) in map {
let key = CskkKeyEvent::keysyms_from_str(k);
let carry_over = CskkKeyEvent::keysyms_from_str(carry);
let converted = conv.to_owned();
process_map.insert(&key, (converted, carry_over));
}
Self { process_map }
}
pub(crate) fn new(rule: &CskkRule) -> Self {
KanaBuilder::converter_from_hashmap(rule.get_conversion_rule())
}
pub(crate) fn new_empty() -> Self {
Self {
process_map: SequenceTrie::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use xkbcommon::xkb::keysyms::{
KEY_a, KEY_at, KEY_b, KEY_bracketleft, KEY_e, KEY_i, KEY_k, KEY_n, KEY_o, KEY_s, KEY_t,
KEY_u, KEY_7, KEY_A, KEY_B,
};
impl KanaBuilder {
pub fn test_converter() -> Self {
let mut process_list = SequenceTrie::new();
process_list.insert(&[KEY_a], ("あ".to_string(), vec![]));
process_list.insert(&[KEY_i], ("い".to_string(), vec![]));
process_list.insert(&[KEY_u], ("う".to_string(), vec![]));
process_list.insert(&[KEY_e], ("え".to_string(), vec![]));
process_list.insert(&[KEY_o], ("お".to_string(), vec![]));
process_list.insert(&[KEY_k, KEY_a], ("か".to_string(), vec![]));
process_list.insert(&[KEY_k, KEY_i], ("き".to_string(), vec![]));
process_list.insert(&[KEY_k, KEY_u], ("く".to_string(), vec![]));
process_list.insert(&[KEY_k, KEY_e], ("け".to_string(), vec![]));
process_list.insert(&[KEY_k, KEY_o], ("こ".to_string(), vec![]));
process_list.insert(&[KEY_t, KEY_s, KEY_u], ("つ".to_string(), vec![]));
KanaBuilder {
process_map: process_list,
}
}
fn test_ant_converter() -> Self {
let mut process_list = SequenceTrie::new();
process_list.insert(&[KEY_a], ("あ".to_string(), vec![]));
process_list.insert(&[KEY_n], ("ん".to_string(), vec![]));
process_list.insert(&[KEY_n, KEY_n], ("ん".to_string(), vec![]));
process_list.insert(&[KEY_n, KEY_n], ("な".to_string(), vec![]));
process_list.insert(&[KEY_t, KEY_a], ("た".to_string(), vec![]));
process_list.insert(&[KEY_t, KEY_t], ("っ".to_string(), vec![KEY_t]));
KanaBuilder {
process_map: process_list,
}
}
}
#[test]
fn combine_with_unprocessed() {
let combined = KanaBuilder::combine_lower(
&CskkKeyEvent::from_string_representation("a").unwrap(),
&[KEY_b],
);
assert_eq!(vec![KEY_b, KEY_a], combined);
}
#[test]
fn combine_no_unprocessed() {
let combined = KanaBuilder::combine_lower(
&CskkKeyEvent::from_string_representation("k").unwrap(),
&[],
);
assert_eq!(vec![KEY_k], combined);
}
#[test]
fn combine_capital() {
let combined = KanaBuilder::combine_lower(
&CskkKeyEvent::from_string_representation("B").unwrap(),
&[],
);
assert_eq!(vec![KEY_b], combined);
}
#[test]
fn uncapitalize() {
assert_eq!(KEY_a, KanaBuilder::uncapitalize(KEY_A));
assert_eq!(KEY_b, KanaBuilder::uncapitalize(KEY_B));
assert_eq!(KEY_7, KanaBuilder::uncapitalize(KEY_7));
assert_eq!(KEY_at, KanaBuilder::uncapitalize(KEY_at));
assert_eq!(KEY_bracketleft, KanaBuilder::uncapitalize(KEY_bracketleft));
}
#[test]
fn convert() {
let converter = KanaBuilder::test_converter();
let result = converter.convert_greedy(&[KEY_k]);
assert_eq!(result, None);
}
#[test]
fn ant_tree_convert() {
let converter = KanaBuilder::test_ant_converter();
let result = converter.convert_greedy(&[KEY_t]);
assert_eq!(result, None);
let (kana, carry_over) = converter.convert_greedy(&[KEY_t, KEY_t]).unwrap();
assert_eq!("っ", kana);
assert_eq!(*carry_over, vec![KEY_t])
}
#[test]
fn can_continue() {
let converter = KanaBuilder::test_converter();
let unprocessed = vec![];
let actual = converter.can_continue(
&CskkKeyEvent::from_string_representation("Q").unwrap(),
&unprocessed,
);
assert!(!actual);
}
#[test]
fn can_continue_2of3letter() {
let converter = KanaBuilder::test_converter();
let unprocessed = vec![KEY_t];
let actual = converter.can_continue(
&CskkKeyEvent::from_string_representation("s").unwrap(),
&unprocessed,
);
assert!(actual);
}
#[test]
fn can_continue_na() {
let converter = KanaBuilder::test_ant_converter();
let unprocessed = vec![KEY_n];
let actual = converter.can_continue(
&CskkKeyEvent::from_string_representation("a").unwrap(),
&unprocessed,
);
assert!(!actual);
}
#[test]
fn can_not_continue() {
let converter = KanaBuilder::test_ant_converter();
let unprocessed = vec![];
let actual = converter.can_continue(
&CskkKeyEvent::from_string_representation("space").unwrap(),
&unprocessed,
);
assert!(!actual);
}
#[test]
fn next_unprocessed_state() {
let converter = KanaBuilder::test_ant_converter();
let unprocessed = vec![];
let actual = converter.next_unprocessed_state(
&CskkKeyEvent::from_string_representation("space").unwrap(),
&unprocessed,
);
assert_eq!(actual, Vec::<Keysym>::new());
}
}