use crate::phonetic::nfa::thompson::{ThompsonBuilder, ThompsonBuilderChar};
use crate::phonetic::nfa::{NFAChar, NFA};
use crate::phonetic::rules::{zompist_rules, zompist_rules_char};
use crate::phonetic::types::{
ContextByte, ContextChar, PhoneByte, PhoneChar, RewriteRuleByte, RewriteRuleChar,
};
#[allow(dead_code)]
fn phone_to_char(phone: &PhoneChar) -> Option<char> {
match phone {
PhoneChar::Vowel(c) | PhoneChar::Consonant(c) => Some(*c),
PhoneChar::Digraph(c1, _) => Some(*c1),
PhoneChar::Trigraph(c1, _, _) => Some(*c1),
PhoneChar::Tetragraph(c1, _, _, _) => Some(*c1),
PhoneChar::Pentagraph(c1, _, _, _, _) => Some(*c1),
PhoneChar::Hexagraph(c1, _, _, _, _, _) => Some(*c1),
PhoneChar::Heptagraph(c1, _, _, _, _, _, _) => Some(*c1),
PhoneChar::Sequence(s) => s.first().copied(),
PhoneChar::Silent => None,
}
}
fn pattern_to_string_char(pattern: &[PhoneChar]) -> String {
let mut result = String::new();
for phone in pattern {
match phone {
PhoneChar::Vowel(c) | PhoneChar::Consonant(c) => result.push(*c),
PhoneChar::Digraph(c1, c2) => {
result.push(*c1);
result.push(*c2);
}
PhoneChar::Trigraph(c1, c2, c3) => {
result.push(*c1);
result.push(*c2);
result.push(*c3);
}
PhoneChar::Tetragraph(c1, c2, c3, c4) => {
result.push(*c1);
result.push(*c2);
result.push(*c3);
result.push(*c4);
}
PhoneChar::Pentagraph(c1, c2, c3, c4, c5) => {
result.push(*c1);
result.push(*c2);
result.push(*c3);
result.push(*c4);
result.push(*c5);
}
PhoneChar::Hexagraph(c1, c2, c3, c4, c5, c6) => {
result.push(*c1);
result.push(*c2);
result.push(*c3);
result.push(*c4);
result.push(*c5);
result.push(*c6);
}
PhoneChar::Heptagraph(c1, c2, c3, c4, c5, c6, c7) => {
result.push(*c1);
result.push(*c2);
result.push(*c3);
result.push(*c4);
result.push(*c5);
result.push(*c6);
result.push(*c7);
}
PhoneChar::Sequence(s) => {
for c in s {
result.push(*c);
}
}
PhoneChar::Silent => {}
}
}
result
}
pub fn rule_to_nfa_char(rule: &RewriteRuleChar) -> NFAChar {
let builder = ThompsonBuilderChar::new();
let pattern_str = pattern_to_string_char(&rule.pattern);
if pattern_str.is_empty() {
return builder.epsilon();
}
let pattern_nfa = builder.literal(&pattern_str);
pattern_nfa
}
pub fn rules_to_nfa_char(rules: &[RewriteRuleChar]) -> NFAChar {
if rules.is_empty() {
let builder = ThompsonBuilderChar::new();
return builder.epsilon();
}
let builder = ThompsonBuilderChar::new();
let nfas: Vec<NFAChar> = rules.iter().map(rule_to_nfa_char).collect();
builder.union_all(nfas)
}
pub fn zompist_nfa_char() -> NFAChar {
rules_to_nfa_char(&zompist_rules_char())
}
#[allow(dead_code)]
fn phone_to_byte(phone: &PhoneByte) -> Option<u8> {
match phone {
PhoneByte::Vowel(b) | PhoneByte::Consonant(b) => Some(*b),
PhoneByte::Digraph(b1, _) => Some(*b1),
PhoneByte::Trigraph(b1, _, _) => Some(*b1),
PhoneByte::Tetragraph(b1, _, _, _) => Some(*b1),
PhoneByte::Pentagraph(b1, _, _, _, _) => Some(*b1),
PhoneByte::Hexagraph(b1, _, _, _, _, _) => Some(*b1),
PhoneByte::Heptagraph(b1, _, _, _, _, _, _) => Some(*b1),
PhoneByte::Sequence(s) => s.first().copied(),
PhoneByte::Silent => None,
}
}
fn pattern_to_bytes(pattern: &[PhoneByte]) -> Vec<u8> {
let mut result = Vec::new();
for phone in pattern {
match phone {
PhoneByte::Vowel(b) | PhoneByte::Consonant(b) => result.push(*b),
PhoneByte::Digraph(b1, b2) => {
result.push(*b1);
result.push(*b2);
}
PhoneByte::Trigraph(b1, b2, b3) => {
result.push(*b1);
result.push(*b2);
result.push(*b3);
}
PhoneByte::Tetragraph(b1, b2, b3, b4) => {
result.push(*b1);
result.push(*b2);
result.push(*b3);
result.push(*b4);
}
PhoneByte::Pentagraph(b1, b2, b3, b4, b5) => {
result.push(*b1);
result.push(*b2);
result.push(*b3);
result.push(*b4);
result.push(*b5);
}
PhoneByte::Hexagraph(b1, b2, b3, b4, b5, b6) => {
result.push(*b1);
result.push(*b2);
result.push(*b3);
result.push(*b4);
result.push(*b5);
result.push(*b6);
}
PhoneByte::Heptagraph(b1, b2, b3, b4, b5, b6, b7) => {
result.push(*b1);
result.push(*b2);
result.push(*b3);
result.push(*b4);
result.push(*b5);
result.push(*b6);
result.push(*b7);
}
PhoneByte::Sequence(s) => {
result.extend_from_slice(s);
}
PhoneByte::Silent => {}
}
}
result
}
pub fn rule_to_nfa(rule: &RewriteRuleByte) -> NFA {
let builder = ThompsonBuilder::new();
let pattern_bytes = pattern_to_bytes(&rule.pattern);
if pattern_bytes.is_empty() {
return builder.epsilon();
}
builder.literal(&pattern_bytes)
}
pub fn rules_to_nfa(rules: &[RewriteRuleByte]) -> NFA {
if rules.is_empty() {
let builder = ThompsonBuilder::new();
return builder.epsilon();
}
let builder = ThompsonBuilder::new();
let nfas: Vec<NFA> = rules.iter().map(rule_to_nfa).collect();
builder.union_all(nfas)
}
pub fn zompist_nfa() -> NFA {
rules_to_nfa(&zompist_rules())
}
#[derive(Debug, Clone)]
pub struct RuleContextInfoChar {
pub pattern_nfa: NFAChar,
pub context: ContextChar,
pub weight: f64,
pub rule_name: String,
}
#[derive(Debug, Clone)]
pub struct RuleContextInfo {
pub pattern_nfa: NFA,
pub context: ContextByte,
pub weight: f64,
pub rule_name: String,
}
pub fn rule_to_nfa_with_context_char(rule: &RewriteRuleChar) -> RuleContextInfoChar {
RuleContextInfoChar {
pattern_nfa: rule_to_nfa_char(rule),
context: rule.context.clone(),
weight: rule.weight,
rule_name: rule.rule_name.clone(),
}
}
pub fn rule_to_nfa_with_context(rule: &RewriteRuleByte) -> RuleContextInfo {
RuleContextInfo {
pattern_nfa: rule_to_nfa(rule),
context: rule.context.clone(),
weight: rule.weight,
rule_name: rule.rule_name.clone(),
}
}
pub fn rules_to_nfa_with_context_char(rules: &[RewriteRuleChar]) -> Vec<RuleContextInfoChar> {
rules.iter().map(rule_to_nfa_with_context_char).collect()
}
pub fn rules_to_nfa_with_context(rules: &[RewriteRuleByte]) -> Vec<RuleContextInfo> {
rules.iter().map(rule_to_nfa_with_context).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::phonetic::rules::{
orthography_rules, orthography_rules_char, phonetic_rules, phonetic_rules_char, test_rules,
test_rules_char,
};
#[test]
fn test_pattern_to_string_simple() {
let pattern = vec![PhoneChar::Consonant('p'), PhoneChar::Consonant('h')];
assert_eq!(pattern_to_string_char(&pattern), "ph");
}
#[test]
fn test_pattern_to_string_digraph() {
let pattern = vec![PhoneChar::Digraph('c', 'h')];
assert_eq!(pattern_to_string_char(&pattern), "ch");
}
#[test]
fn test_pattern_to_string_mixed() {
let pattern = vec![
PhoneChar::Consonant('a'),
PhoneChar::Digraph('c', 'h'),
PhoneChar::Vowel('e'),
];
assert_eq!(pattern_to_string_char(&pattern), "ache");
}
#[test]
fn test_pattern_to_string_silent() {
let pattern = vec![PhoneChar::Consonant('a'), PhoneChar::Silent];
assert_eq!(pattern_to_string_char(&pattern), "a");
}
#[test]
fn test_rule_to_nfa_char_ph_to_f() {
let rule = RewriteRuleChar {
rule_id: 3,
rule_name: "ph → f".to_string(),
pattern: vec![PhoneChar::Consonant('p'), PhoneChar::Consonant('h')],
replacement: vec![PhoneChar::Consonant('f')],
context: ContextChar::Anywhere,
weight: 0.0,
syllable_condition: None,
};
let nfa = rule_to_nfa_char(&rule);
assert!(nfa.accepts("ph"));
assert!(!nfa.accepts("p"));
assert!(!nfa.accepts("h"));
assert!(!nfa.accepts("f"));
assert!(!nfa.accepts(""));
}
#[test]
fn test_rule_to_nfa_char_single_char() {
let rule = RewriteRuleChar {
rule_id: 20,
rule_name: "c → s".to_string(),
pattern: vec![PhoneChar::Consonant('c')],
replacement: vec![PhoneChar::Consonant('s')],
context: ContextChar::BeforeVowel(vec!['e', 'i']),
weight: 0.0,
syllable_condition: None,
};
let nfa = rule_to_nfa_char(&rule);
assert!(nfa.accepts("c"));
assert!(!nfa.accepts("s"));
assert!(!nfa.accepts(""));
}
#[test]
fn test_rules_to_nfa_char_orthography() {
let rules = orthography_rules_char();
let nfa = rules_to_nfa_char(&rules);
assert!(nfa.accepts("ch")); assert!(nfa.accepts("sh")); assert!(nfa.accepts("ph")); assert!(nfa.accepts("c")); assert!(nfa.accepts("g")); assert!(nfa.accepts("e")); assert!(nfa.accepts("gh"));
assert!(!nfa.accepts("xyz"));
assert!(!nfa.accepts("abc"));
}
#[test]
fn test_rules_to_nfa_char_phonetic() {
let rules = phonetic_rules_char();
let nfa = rules_to_nfa_char(&rules);
assert!(nfa.accepts("th")); assert!(nfa.accepts("qu")); assert!(nfa.accepts("kw"));
assert!(!nfa.accepts("xyz"));
}
#[test]
fn test_rules_to_nfa_char_test_rules() {
let rules = test_rules_char();
let nfa = rules_to_nfa_char(&rules);
assert!(nfa.accepts("x")); assert!(nfa.accepts("y"));
assert!(!nfa.accepts("z"));
assert!(!nfa.accepts("a"));
}
#[test]
fn test_zompist_nfa_char() {
let nfa = zompist_nfa_char();
assert!(nfa.accepts("ch"));
assert!(nfa.accepts("sh"));
assert!(nfa.accepts("ph"));
assert!(nfa.accepts("c"));
assert!(nfa.accepts("g"));
assert!(nfa.accepts("e"));
assert!(nfa.accepts("gh"));
assert!(nfa.accepts("th"));
assert!(nfa.accepts("qu"));
assert!(nfa.accepts("kw"));
assert!(nfa.accepts("x"));
assert!(nfa.accepts("y"));
assert!(!nfa.accepts("xyz"));
assert!(!nfa.accepts("hello"));
}
#[test]
fn test_rules_to_nfa_char_empty() {
let rules: Vec<RewriteRuleChar> = vec![];
let nfa = rules_to_nfa_char(&rules);
assert!(nfa.accepts(""));
}
#[test]
fn test_rule_context_info_char() {
let rule = RewriteRuleChar {
rule_id: 20,
rule_name: "c → s / _[ie]".to_string(),
pattern: vec![PhoneChar::Consonant('c')],
replacement: vec![PhoneChar::Consonant('s')],
context: ContextChar::BeforeVowel(vec!['e', 'i']),
weight: 0.0,
syllable_condition: None,
};
let info = rule_to_nfa_with_context_char(&rule);
assert!(info.pattern_nfa.accepts("c"));
assert_eq!(info.weight, 0.0);
assert_eq!(info.rule_name, "c → s / _[ie]");
assert!(matches!(info.context, ContextChar::BeforeVowel(_)));
}
#[test]
fn test_pattern_to_bytes_simple() {
let pattern = vec![PhoneByte::Consonant(b'p'), PhoneByte::Consonant(b'h')];
assert_eq!(pattern_to_bytes(&pattern), vec![b'p', b'h']);
}
#[test]
fn test_pattern_to_bytes_digraph() {
let pattern = vec![PhoneByte::Digraph(b'c', b'h')];
assert_eq!(pattern_to_bytes(&pattern), vec![b'c', b'h']);
}
#[test]
fn test_rule_to_nfa_ph_to_f() {
let rule = RewriteRuleByte {
rule_id: 3,
rule_name: "ph → f".to_string(),
pattern: vec![PhoneByte::Consonant(b'p'), PhoneByte::Consonant(b'h')],
replacement: vec![PhoneByte::Consonant(b'f')],
context: ContextByte::Anywhere,
weight: 0.0,
syllable_condition: None,
};
let nfa = rule_to_nfa(&rule);
assert!(nfa.accepts_str("ph"));
assert!(!nfa.accepts_str("p"));
assert!(!nfa.accepts_str("f"));
}
#[test]
fn test_rules_to_nfa_orthography() {
let rules = orthography_rules();
let nfa = rules_to_nfa(&rules);
assert!(nfa.accepts_str("ch"));
assert!(nfa.accepts_str("sh"));
assert!(nfa.accepts_str("ph"));
assert!(nfa.accepts_str("c"));
assert!(nfa.accepts_str("g"));
assert!(nfa.accepts_str("e"));
assert!(nfa.accepts_str("gh"));
}
#[test]
fn test_rules_to_nfa_phonetic() {
let rules = phonetic_rules();
let nfa = rules_to_nfa(&rules);
assert!(nfa.accepts_str("th"));
assert!(nfa.accepts_str("qu"));
assert!(nfa.accepts_str("kw"));
}
#[test]
fn test_zompist_nfa() {
let nfa = zompist_nfa();
assert!(nfa.accepts_str("ch"));
assert!(nfa.accepts_str("sh"));
assert!(nfa.accepts_str("ph"));
assert!(nfa.accepts_str("c"));
assert!(nfa.accepts_str("g"));
assert!(nfa.accepts_str("e"));
assert!(nfa.accepts_str("gh"));
assert!(nfa.accepts_str("th"));
assert!(nfa.accepts_str("qu"));
assert!(nfa.accepts_str("kw"));
assert!(nfa.accepts_str("x"));
assert!(nfa.accepts_str("y"));
}
#[test]
fn test_rules_to_nfa_empty() {
let rules: Vec<RewriteRuleByte> = vec![];
let nfa = rules_to_nfa(&rules);
assert!(nfa.accepts_str(""));
}
#[test]
fn test_rule_counts() {
let ortho_char = orthography_rules_char();
let phonetic_char = phonetic_rules_char();
let test_char = test_rules_char();
let zompist_char = zompist_rules_char();
assert_eq!(ortho_char.len(), 45);
assert_eq!(phonetic_char.len(), 3);
assert_eq!(test_char.len(), 2);
assert_eq!(zompist_char.len(), 62);
let ortho = orthography_rules();
let phonetic = phonetic_rules();
let test = test_rules();
let zompist = zompist_rules();
assert_eq!(ortho.len(), 45);
assert_eq!(phonetic.len(), 3);
assert_eq!(test.len(), 2);
assert_eq!(zompist.len(), 62);
}
}