use smallvec::SmallVec;
use std::fmt::Display;
use crate::{
editing::{add_modification_char, add_tone_char, get_tone_mark_placement, replace_nth_char},
parsing::{extract_letter_modifications, extract_tone, parse_syllable},
processor::{modify_letter, AccentStyle, LetterModification, ToneMark},
util::clean_char,
};
#[derive(Default, Debug, Clone, PartialEq)]
pub struct Syllable {
pub initial_consonant: String,
pub vowel: String,
pub final_consonant: String,
pub tone_mark: Option<ToneMark>,
pub accent_style: AccentStyle,
pub letter_modifications: SmallVec<[(usize, LetterModification); 2]>,
}
impl Syllable {
pub fn new(input: &str) -> Self {
let mut syllable = Self::default();
syllable.set(input.to_string());
syllable
}
#[inline]
pub fn len(&self) -> usize {
self.initial_consonant.chars().count()
+ self.vowel.chars().count()
+ self.final_consonant.chars().count()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.initial_consonant.is_empty()
&& self.vowel.is_empty()
&& self.final_consonant.is_empty()
}
pub fn push(&mut self, ch: char) {
let clean_syllable = format!(
"{}{}{}{}",
self.initial_consonant, self.vowel, self.final_consonant, ch
);
let (_, syllable) = parse_syllable(&clean_syllable).unwrap();
self.initial_consonant = syllable.initial_consonant.chars().map(clean_char).collect();
self.vowel = syllable.vowel.chars().map(clean_char).collect();
self.final_consonant = syllable.final_consonant.to_string();
self.recalculate_modifications();
}
pub fn recalculate_modifications(&mut self) {
if self.initial_consonant.is_empty()
&& self.final_consonant.is_empty()
&& !self.vowel.eq_ignore_ascii_case("uoi")
{
return;
}
if self.vowel.eq_ignore_ascii_case("uo")
&& !self.initial_consonant.is_empty()
&& self.final_consonant.is_empty()
{
return;
}
let mut modifications = std::mem::take(&mut self.letter_modifications);
modifications.dedup_by_key(|(_, modification)| *modification);
for (_, modification) in modifications {
let _ = modify_letter(self, &modification);
}
}
pub fn set(&mut self, raw: String) {
let (_, syllable) = parse_syllable(&raw).unwrap();
self.initial_consonant = syllable.initial_consonant.chars().map(clean_char).collect();
self.vowel = syllable.vowel.chars().map(clean_char).collect();
self.final_consonant = syllable.final_consonant.to_string();
self.letter_modifications = extract_letter_modifications(&raw).into();
self.tone_mark = extract_tone(&raw);
}
pub fn replace_last_char(&mut self, ch: char) {
let mut raw = self.to_string();
let last_index = raw.chars().count() - 1;
replace_nth_char(&mut raw, last_index, ch);
self.set(raw);
}
pub fn contains_modification(&self, modification: &LetterModification) -> bool {
self.letter_modifications
.iter()
.any(|(_, m)| m == modification)
}
}
impl Display for Syllable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut result = format!(
"{}{}{}",
self.initial_consonant, self.vowel, self.final_consonant
);
for (position, modification) in &self.letter_modifications {
let ch = result.chars().nth(*position).unwrap();
let replace_char = add_modification_char(ch, modification);
replace_nth_char(&mut result, *position, replace_char);
}
if let Some(tone_mark) = &self.tone_mark {
let tone_mark_position = get_tone_mark_placement(&result, &self.accent_style);
let ch = result.chars().nth(tone_mark_position).unwrap();
let replace_char = add_tone_char(ch, tone_mark);
replace_nth_char(&mut result, tone_mark_position, replace_char);
}
write!(f, "{}", result)
}
}
impl From<&str> for Syllable {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl From<String> for Syllable {
fn from(value: String) -> Self {
Self::new(&value)
}
}