use phf::{phf_map, Map};
use crate::{
processor::{
add_tone, modify_letter, remove_tone, AccentStyle, LetterModification, ToneMark,
Transformation,
},
syllable::Syllable,
validation::is_valid_syllable,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Action {
AddTonemark(ToneMark),
ModifyLetter(LetterModification),
ModifyLetterOnCharacterFamily(LetterModification, char),
InsertƯ,
ResetInsertedƯ,
RemoveToneMark,
}
pub type Definition = Map<char, &'static [Action]>;
#[derive(Debug, Clone, Default)]
pub struct TransformResult {
pub tone_mark_removed: bool,
pub letter_modification_removed: bool,
}
pub static VNI: Definition = phf_map! {
'1' => &[Action::AddTonemark(ToneMark::Acute)],
'2' => &[Action::AddTonemark(ToneMark::Grave)],
'3' => &[Action::AddTonemark(ToneMark::HookAbove)],
'4' => &[Action::AddTonemark(ToneMark::Tilde)],
'5' => &[Action::AddTonemark(ToneMark::Underdot)],
'6' => &[Action::ModifyLetter(LetterModification::Circumflex)],
'7' => &[Action::ModifyLetter(LetterModification::Horn)],
'8' => &[Action::ModifyLetter(LetterModification::Breve)],
'9' => &[Action::ModifyLetter(LetterModification::Dyet)],
'0' => &[Action::RemoveToneMark],
};
pub static TELEX: Definition = phf_map! {
's' => &[Action::AddTonemark(ToneMark::Acute)],
'f' => &[Action::AddTonemark(ToneMark::Grave)],
'r' => &[Action::AddTonemark(ToneMark::HookAbove)],
'x' => &[Action::AddTonemark(ToneMark::Tilde)],
'j' => &[Action::AddTonemark(ToneMark::Underdot)],
'a' => &[Action::ModifyLetterOnCharacterFamily(LetterModification::Circumflex, 'a')],
'e' => &[Action::ModifyLetterOnCharacterFamily(LetterModification::Circumflex, 'e')],
'o' => &[Action::ModifyLetterOnCharacterFamily(LetterModification::Circumflex, 'o')],
'w' => &[Action::ResetInsertedƯ, Action::ModifyLetter(LetterModification::Horn), Action::ModifyLetter(LetterModification::Breve), Action::InsertƯ],
'd' => &[Action::ModifyLetter(LetterModification::Dyet)],
'z' => &[Action::RemoveToneMark],
};
pub fn transform_buffer_with_style<I>(
definition: &Definition,
accent_style: AccentStyle,
buffer: I,
output: &mut String,
) -> TransformResult
where
I: IntoIterator<Item = char>,
{
let mut incremental_buffer = IncrementalBuffer::new_with_style(definition, accent_style);
for ch in buffer {
let _ = incremental_buffer.push(ch);
}
output.push_str(incremental_buffer.view());
incremental_buffer.result().clone()
}
pub fn transform_buffer<I>(
definition: &Definition,
buffer: I,
output: &mut String,
) -> TransformResult
where
I: IntoIterator<Item = char>,
{
transform_buffer_with_style(definition, AccentStyle::default(), buffer, output)
}
#[derive(Debug, Clone)]
pub struct IncrementalBuffer<'def> {
definition: &'def Definition,
syllable: Syllable,
input: Vec<char>,
output: String,
result: TransformResult,
last_executed_action: Option<Action>,
}
impl<'def> IncrementalBuffer<'def> {
#[inline]
#[must_use]
pub fn new(definition: &'def Definition) -> Self {
Self::new_with_style(definition, AccentStyle::default())
}
#[inline]
#[must_use]
pub fn new_with_style(definition: &'def Definition, accent_style: AccentStyle) -> Self {
Self {
definition,
syllable: Syllable {
accent_style,
..Default::default()
},
input: Vec::new(),
output: String::new(),
result: TransformResult::default(),
last_executed_action: None,
}
}
pub fn push(&mut self, ch: char) -> TransformResult {
self.input.push(ch);
let lowercase_ch = ch.to_ascii_lowercase();
if !self.definition.contains_key(&lowercase_ch) {
self.syllable.push(ch);
self.update_output();
return TransformResult::default();
}
let fallback = format!("{}{ch}", self.syllable);
let actions = self.definition.get(&lowercase_ch).unwrap();
let mut action_iter = actions.iter();
let mut action = action_iter.next().unwrap();
let mut char_result = TransformResult::default();
loop {
let transformation = match action {
Action::AddTonemark(tonemark) => add_tone(&mut self.syllable, tonemark),
Action::ModifyLetter(modification) => {
modify_letter(&mut self.syllable, modification)
}
Action::ModifyLetterOnCharacterFamily(modification, family_char)
if self
.syllable
.vowel
.to_ascii_lowercase()
.contains(*family_char) =>
{
modify_letter(&mut self.syllable, modification)
}
Action::RemoveToneMark => remove_tone(&mut self.syllable),
Action::InsertƯ => {
if self.syllable.vowel.is_empty() || self.syllable.to_string() == "gi" {
self.syllable
.push(if ch.is_lowercase() { 'u' } else { 'U' });
let last_index = self.syllable.len() - 1;
self.syllable
.letter_modifications
.push((last_index, LetterModification::Horn));
Transformation::LetterModificationAdded
} else {
Transformation::Ignored
}
}
Action::ResetInsertedƯ
if matches!(self.last_executed_action, Some(Action::InsertƯ)) =>
{
self.syllable.replace_last_char(ch);
Transformation::LetterModificationRemoved
}
_ => Transformation::Ignored,
};
if transformation == Transformation::Ignored {
if let Some(next_action) = action_iter.next() {
action = next_action;
continue;
}
}
if transformation == Transformation::ToneMarkRemoved {
char_result.tone_mark_removed = true;
self.result.tone_mark_removed = true;
}
if transformation == Transformation::LetterModificationRemoved {
char_result.letter_modification_removed = true;
self.result.letter_modification_removed = true;
}
let action_performed = match transformation {
Transformation::Ignored | Transformation::LetterModificationRemoved => false,
Transformation::ToneMarkRemoved => *action == Action::RemoveToneMark,
_ => true,
};
if *action == Action::ResetInsertedƯ {
self.last_executed_action = Some(action.clone());
break;
}
if !action_performed {
self.syllable.push(ch);
self.last_executed_action = None;
} else if !is_valid_syllable(&self.syllable.to_string()) {
self.syllable.set(fallback);
self.last_executed_action = None;
} else {
self.last_executed_action = Some(action.clone());
}
break;
}
self.update_output();
char_result
}
#[inline]
#[must_use]
pub fn view(&self) -> &str {
&self.output
}
#[inline]
#[must_use]
pub const fn result(&self) -> &TransformResult {
&self.result
}
#[inline]
#[must_use]
pub fn input(&self) -> &[char] {
&self.input
}
pub fn clear(&mut self) {
let accent_style = self.syllable.accent_style.clone();
self.syllable = Syllable {
accent_style,
..Default::default()
};
self.input.clear();
self.output.clear();
self.result = TransformResult::default();
self.last_executed_action = None;
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.input.is_empty()
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.input.len()
}
fn update_output(&mut self) {
self.output.clear();
self.output.push_str(&self.syllable.to_string());
}
}
#[inline]
#[must_use]
pub fn transform_buffer_incremental(definition: &Definition) -> IncrementalBuffer<'_> {
IncrementalBuffer::new(definition)
}
#[inline]
#[must_use]
pub fn transform_buffer_incremental_with_style(
definition: &Definition,
accent_style: AccentStyle,
) -> IncrementalBuffer<'_> {
IncrementalBuffer::new_with_style(definition, accent_style)
}