#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
use super::common::phonetic_unit::PhoneticUnit;
use super::common::syllable::SyllableExpr;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serialization",
serde(bound = "U: Serialize + for<'a> Deserialize<'a>")
)]
pub enum Phone<U: PhoneticUnit> {
Vowel(U),
Consonant(U),
Digraph(U, U),
Trigraph(U, U, U),
Tetragraph(U, U, U, U),
Pentagraph(U, U, U, U, U),
Hexagraph(U, U, U, U, U, U),
Heptagraph(U, U, U, U, U, U, U),
Sequence(Vec<U>),
Silent,
}
impl<U: PhoneticUnit> Phone<U> {
#[inline]
pub fn is_vowel(&self) -> bool {
matches!(self, Phone::Vowel(_))
}
#[inline]
pub fn is_consonant(&self) -> bool {
matches!(
self,
Phone::Consonant(_)
| Phone::Digraph(_, _)
| Phone::Trigraph(_, _, _)
| Phone::Tetragraph(_, _, _, _)
| Phone::Pentagraph(_, _, _, _, _)
| Phone::Hexagraph(_, _, _, _, _, _)
| Phone::Heptagraph(_, _, _, _, _, _, _)
| Phone::Sequence(_)
)
}
#[inline]
pub fn is_silent(&self) -> bool {
matches!(self, Phone::Silent)
}
pub fn first_char(&self) -> Option<U> {
match self {
Phone::Vowel(c)
| Phone::Consonant(c)
| Phone::Digraph(c, _)
| Phone::Trigraph(c, _, _)
| Phone::Tetragraph(c, _, _, _)
| Phone::Pentagraph(c, _, _, _, _)
| Phone::Hexagraph(c, _, _, _, _, _)
| Phone::Heptagraph(c, _, _, _, _, _, _) => Some(*c),
Phone::Sequence(s) => s.first().copied(),
Phone::Silent => None,
}
}
pub fn chars(&self) -> Vec<U> {
match self {
Phone::Vowel(c) | Phone::Consonant(c) => vec![*c],
Phone::Digraph(c1, c2) => vec![*c1, *c2],
Phone::Trigraph(c1, c2, c3) => vec![*c1, *c2, *c3],
Phone::Tetragraph(c1, c2, c3, c4) => vec![*c1, *c2, *c3, *c4],
Phone::Pentagraph(c1, c2, c3, c4, c5) => vec![*c1, *c2, *c3, *c4, *c5],
Phone::Hexagraph(c1, c2, c3, c4, c5, c6) => vec![*c1, *c2, *c3, *c4, *c5, *c6],
Phone::Heptagraph(c1, c2, c3, c4, c5, c6, c7) => {
vec![*c1, *c2, *c3, *c4, *c5, *c6, *c7]
}
Phone::Sequence(s) => s.clone(),
Phone::Silent => vec![],
}
}
}
impl<U: PhoneticUnit> std::fmt::Display for Phone<U> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Phone::Vowel(c) => write!(f, "V({})", U::to_char(*c)),
Phone::Consonant(c) => write!(f, "C({})", U::to_char(*c)),
Phone::Digraph(c1, c2) => write!(f, "D({},{})", U::to_char(*c1), U::to_char(*c2)),
Phone::Trigraph(c1, c2, c3) => {
write!(
f,
"T({},{},{})",
U::to_char(*c1),
U::to_char(*c2),
U::to_char(*c3)
)
}
Phone::Tetragraph(c1, c2, c3, c4) => {
write!(
f,
"Q({},{},{},{})",
U::to_char(*c1),
U::to_char(*c2),
U::to_char(*c3),
U::to_char(*c4)
)
}
Phone::Pentagraph(c1, c2, c3, c4, c5) => {
write!(
f,
"P5({},{},{},{},{})",
U::to_char(*c1),
U::to_char(*c2),
U::to_char(*c3),
U::to_char(*c4),
U::to_char(*c5)
)
}
Phone::Hexagraph(c1, c2, c3, c4, c5, c6) => {
write!(
f,
"H6({},{},{},{},{},{})",
U::to_char(*c1),
U::to_char(*c2),
U::to_char(*c3),
U::to_char(*c4),
U::to_char(*c5),
U::to_char(*c6)
)
}
Phone::Heptagraph(c1, c2, c3, c4, c5, c6, c7) => {
write!(
f,
"H7({},{},{},{},{},{},{})",
U::to_char(*c1),
U::to_char(*c2),
U::to_char(*c3),
U::to_char(*c4),
U::to_char(*c5),
U::to_char(*c6),
U::to_char(*c7)
)
}
Phone::Sequence(s) => {
write!(f, "S(")?;
for (i, c) in s.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{}", U::to_char(*c))?;
}
write!(f, ")")
}
Phone::Silent => write!(f, "Silent"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serialization",
serde(bound = "U: Serialize + for<'a> Deserialize<'a>")
)]
pub enum Context<U: PhoneticUnit> {
Initial,
Final,
BeforeVowel(Vec<U>),
AfterConsonant(Vec<U>),
BeforeConsonant(Vec<U>),
AfterVowel(Vec<U>),
Anywhere,
And(Box<Context<U>>, Box<Context<U>>),
Or(Box<Context<U>>, Box<Context<U>>),
Not(Box<Context<U>>),
}
impl<U: PhoneticUnit> Context<U> {
#[inline]
pub fn is_position_dependent(&self) -> bool {
match self {
Context::Final => true,
Context::And(a, b) => a.is_position_dependent() || b.is_position_dependent(),
Context::Or(a, b) => a.is_position_dependent() || b.is_position_dependent(),
Context::Not(inner) => inner.is_position_dependent(),
_ => false,
}
}
}
impl<U: PhoneticUnit> std::fmt::Display for Context<U> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Context::Initial => write!(f, "Initial"),
Context::Final => write!(f, "Final"),
Context::BeforeVowel(cs) => {
write!(f, "BeforeVowel({})", U::units_to_string(cs))
}
Context::AfterConsonant(cs) => {
write!(f, "AfterConsonant({})", U::units_to_string(cs))
}
Context::BeforeConsonant(cs) => {
write!(f, "BeforeConsonant({})", U::units_to_string(cs))
}
Context::AfterVowel(cs) => {
write!(f, "AfterVowel({})", U::units_to_string(cs))
}
Context::Anywhere => write!(f, "Anywhere"),
Context::And(a, b) => write!(f, "And({}, {})", a, b),
Context::Or(a, b) => write!(f, "Or({}, {})", a, b),
Context::Not(inner) => write!(f, "Not({})", inner),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serialization",
serde(bound = "U: Serialize + for<'a> Deserialize<'a>")
)]
pub struct RewriteRule<U: PhoneticUnit> {
pub rule_id: usize,
pub rule_name: String,
pub pattern: Vec<Phone<U>>,
pub replacement: Vec<Phone<U>>,
pub context: Context<U>,
pub weight: f64,
pub syllable_condition: Option<SyllableExpr>,
}
pub type PhoneByte = Phone<u8>;
pub type PhoneChar = Phone<char>;
pub type ContextByte = Context<u8>;
pub type ContextChar = Context<char>;
pub type RewriteRuleByte = RewriteRule<u8>;
pub type RewriteRuleChar = RewriteRule<char>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_phone_display_byte() {
assert_eq!(Phone::<u8>::Vowel(b'a').to_string(), "V(a)");
assert_eq!(Phone::<u8>::Consonant(b'k').to_string(), "C(k)");
assert_eq!(Phone::<u8>::Digraph(b'c', b'h').to_string(), "D(c,h)");
assert_eq!(Phone::<u8>::Silent.to_string(), "Silent");
}
#[test]
fn test_phone_display_char() {
assert_eq!(Phone::<char>::Vowel('a').to_string(), "V(a)");
assert_eq!(Phone::<char>::Consonant('k').to_string(), "C(k)");
assert_eq!(Phone::<char>::Digraph('c', 'h').to_string(), "D(c,h)");
assert_eq!(Phone::<char>::Silent.to_string(), "Silent");
}
#[test]
fn test_phone_equality_byte() {
assert_eq!(Phone::<u8>::Vowel(b'a'), Phone::<u8>::Vowel(b'a'));
assert_ne!(Phone::<u8>::Vowel(b'a'), Phone::<u8>::Vowel(b'e'));
assert_ne!(Phone::<u8>::Vowel(b'a'), Phone::<u8>::Consonant(b'a'));
assert_eq!(Phone::<u8>::Silent, Phone::<u8>::Silent);
}
#[test]
fn test_phone_equality_char() {
assert_eq!(Phone::<char>::Vowel('a'), Phone::<char>::Vowel('a'));
assert_ne!(Phone::<char>::Vowel('a'), Phone::<char>::Vowel('e'));
assert_ne!(Phone::<char>::Vowel('a'), Phone::<char>::Consonant('a'));
assert_eq!(Phone::<char>::Silent, Phone::<char>::Silent);
}
#[test]
fn test_phone_is_vowel() {
assert!(Phone::<u8>::Vowel(b'a').is_vowel());
assert!(!Phone::<u8>::Consonant(b'k').is_vowel());
assert!(!Phone::<u8>::Silent.is_vowel());
}
#[test]
fn test_phone_is_consonant() {
assert!(Phone::<u8>::Consonant(b'k').is_consonant());
assert!(Phone::<u8>::Digraph(b'c', b'h').is_consonant());
assert!(!Phone::<u8>::Vowel(b'a').is_consonant());
assert!(!Phone::<u8>::Silent.is_consonant());
}
#[test]
fn test_phone_first_char() {
assert_eq!(Phone::<u8>::Vowel(b'a').first_char(), Some(b'a'));
assert_eq!(Phone::<u8>::Digraph(b'c', b'h').first_char(), Some(b'c'));
assert_eq!(Phone::<u8>::Silent.first_char(), None);
}
#[test]
fn test_phone_chars() {
assert_eq!(Phone::<u8>::Vowel(b'a').chars(), vec![b'a']);
assert_eq!(Phone::<u8>::Digraph(b'c', b'h').chars(), vec![b'c', b'h']);
assert_eq!(Phone::<u8>::Silent.chars(), Vec::<u8>::new());
}
#[test]
fn test_context_display_byte() {
assert_eq!(Context::<u8>::Initial.to_string(), "Initial");
assert_eq!(Context::<u8>::Final.to_string(), "Final");
assert_eq!(Context::<u8>::Anywhere.to_string(), "Anywhere");
assert_eq!(
Context::<u8>::BeforeVowel(vec![b'a', b'e', b'i']).to_string(),
"BeforeVowel(aei)"
);
}
#[test]
fn test_context_display_char() {
assert_eq!(Context::<char>::Initial.to_string(), "Initial");
assert_eq!(Context::<char>::Final.to_string(), "Final");
assert_eq!(Context::<char>::Anywhere.to_string(), "Anywhere");
assert_eq!(
Context::<char>::BeforeVowel(vec!['a', 'e', 'i']).to_string(),
"BeforeVowel(aei)"
);
}
#[test]
fn test_context_equality_byte() {
assert_eq!(Context::<u8>::Initial, Context::<u8>::Initial);
assert_ne!(Context::<u8>::Initial, Context::<u8>::Final);
assert_eq!(
Context::<u8>::BeforeVowel(vec![b'a', b'e']),
Context::<u8>::BeforeVowel(vec![b'a', b'e'])
);
assert_ne!(
Context::<u8>::BeforeVowel(vec![b'a']),
Context::<u8>::BeforeVowel(vec![b'e'])
);
}
#[test]
fn test_context_is_position_dependent() {
assert!(Context::<u8>::Final.is_position_dependent());
assert!(!Context::<u8>::Initial.is_position_dependent());
assert!(!Context::<u8>::Anywhere.is_position_dependent());
assert!(!Context::<u8>::BeforeVowel(vec![b'a']).is_position_dependent());
let ctx = Context::<u8>::And(Box::new(Context::Initial), Box::new(Context::Final));
assert!(ctx.is_position_dependent());
let ctx = Context::<u8>::Or(Box::new(Context::Initial), Box::new(Context::Final));
assert!(ctx.is_position_dependent());
let ctx = Context::<u8>::Not(Box::new(Context::Final));
assert!(ctx.is_position_dependent());
}
#[test]
fn test_rewrite_rule_creation_byte() {
let rule: RewriteRule<u8> = RewriteRule {
rule_id: 1,
rule_name: "Test Rule".to_string(),
pattern: vec![Phone::Consonant(b'g'), Phone::Consonant(b'h')],
replacement: vec![Phone::Consonant(b'f')],
context: Context::Anywhere,
weight: 1.0,
syllable_condition: None,
};
assert_eq!(rule.rule_id, 1);
assert_eq!(rule.pattern.len(), 2);
assert_eq!(rule.replacement.len(), 1);
}
#[test]
fn test_rewrite_rule_creation_char() {
let rule: RewriteRule<char> = RewriteRule {
rule_id: 1,
rule_name: "Test Rule".to_string(),
pattern: vec![Phone::Consonant('g'), Phone::Consonant('h')],
replacement: vec![Phone::Consonant('f')],
context: Context::Anywhere,
weight: 1.0,
syllable_condition: None,
};
assert_eq!(rule.rule_id, 1);
assert_eq!(rule.pattern.len(), 2);
assert_eq!(rule.replacement.len(), 1);
}
#[test]
fn test_type_aliases() {
let _phone_byte: PhoneByte = Phone::Vowel(b'a');
let _phone_char: PhoneChar = Phone::Vowel('a');
let _context_byte: ContextByte = Context::Initial;
let _context_char: ContextChar = Context::Initial;
}
#[test]
fn test_compound_context_and_byte() {
let ctx: Context<u8> = Context::And(
Box::new(Context::AfterVowel(vec![b'a', b'e', b'i', b'o', b'u'])),
Box::new(Context::BeforeVowel(vec![b'a', b'e', b'i', b'o', b'u'])),
);
assert_eq!(
ctx.to_string(),
"And(AfterVowel(aeiou), BeforeVowel(aeiou))"
);
assert!(!ctx.is_position_dependent());
}
#[test]
fn test_compound_context_or_byte() {
let ctx: Context<u8> = Context::Or(Box::new(Context::Initial), Box::new(Context::Final));
assert_eq!(ctx.to_string(), "Or(Initial, Final)");
assert!(ctx.is_position_dependent());
}
#[test]
fn test_compound_context_not_byte() {
let ctx: Context<u8> = Context::Not(Box::new(Context::BeforeVowel(vec![
b'a', b'e', b'i', b'o', b'u',
])));
assert_eq!(ctx.to_string(), "Not(BeforeVowel(aeiou))");
assert!(!ctx.is_position_dependent());
}
#[test]
fn test_nested_compound_context() {
let ctx: Context<u8> = Context::And(
Box::new(Context::Not(Box::new(Context::BeforeVowel(vec![
b'a', b'e',
])))),
Box::new(Context::Or(
Box::new(Context::AfterVowel(vec![b'a', b'e'])),
Box::new(Context::Final),
)),
);
assert!(ctx.is_position_dependent());
}
#[test]
fn test_compound_context_and_char() {
let ctx: Context<char> = Context::And(
Box::new(Context::AfterVowel(vec!['a', 'e', 'i', 'o', 'u'])),
Box::new(Context::BeforeVowel(vec!['a', 'e', 'i', 'o', 'u'])),
);
assert_eq!(
ctx.to_string(),
"And(AfterVowel(aeiou), BeforeVowel(aeiou))"
);
assert!(!ctx.is_position_dependent());
}
#[test]
fn test_compound_context_or_char() {
let ctx: Context<char> = Context::Or(Box::new(Context::Initial), Box::new(Context::Final));
assert_eq!(ctx.to_string(), "Or(Initial, Final)");
assert!(ctx.is_position_dependent());
}
#[test]
fn test_compound_context_not_char() {
let ctx: Context<char> = Context::Not(Box::new(Context::BeforeVowel(vec![
'a', 'e', 'i', 'o', 'u',
])));
assert_eq!(ctx.to_string(), "Not(BeforeVowel(aeiou))");
assert!(!ctx.is_position_dependent());
}
#[test]
fn test_compound_context_equality() {
let ctx1: Context<u8> = Context::And(
Box::new(Context::Initial),
Box::new(Context::BeforeVowel(vec![b'a'])),
);
let ctx2: Context<u8> = Context::And(
Box::new(Context::Initial),
Box::new(Context::BeforeVowel(vec![b'a'])),
);
let ctx3: Context<u8> = Context::And(
Box::new(Context::Initial),
Box::new(Context::BeforeVowel(vec![b'e'])),
);
assert_eq!(ctx1, ctx2);
assert_ne!(ctx1, ctx3);
}
}