use crate::phonetic::llev::RuleSetChar;
use std::sync::OnceLock;
pub fn base() -> &'static RuleSetChar {
static RULESET: OnceLock<RuleSetChar> = OnceLock::new();
RULESET.get_or_init(|| {
let content = include_str!("../../../data/rules/russian/base.llev");
let file = crate::phonetic::llev::parse_str(content)
.expect("Invalid embedded russian/base.llev - this is a bug in liblevenshtein");
RuleSetChar::from_llev(&file)
.expect("Failed to compile Russian base rules - this is a bug in liblevenshtein")
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_base_loads() {
let rules = base();
assert!(!rules.is_empty(), "Russian base rules should not be empty");
assert!(
rules.len() > 50,
"expected >50 base rules, got {}",
rules.len()
);
}
#[test]
fn test_simple_vowels() {
let rules = base();
let result = rules.apply("а");
assert!(
result.contains('a') || result.contains('ɐ'),
"а should become a or ɐ, got: {}",
result
);
let result = rules.apply("о");
assert!(
result.contains('ɔ') || result.contains('o') || result.contains('ɐ'),
"о should become ɔ or o, got: {}",
result
);
let result = rules.apply("у");
assert!(result.contains('u'), "у should become u, got: {}", result);
let result = rules.apply("и");
assert!(result.contains('i'), "и should become i, got: {}", result);
let result = rules.apply("ы");
assert!(result.contains('ɨ'), "ы should become ɨ, got: {}", result);
}
#[test]
fn test_iotated_vowels() {
let rules = base();
let result = rules.apply("е");
assert!(
result.contains('ɪ') || result.contains('e') || result.contains('j'),
"е should be processed, got: {}",
result
);
let result = rules.apply("ё");
assert!(
result.contains('j') || result.contains('o') || result.contains('ɵ'),
"ё should contain j or o, got: {}",
result
);
let result = rules.apply("ю");
assert!(
result.contains('j') || result.contains('u') || result.contains('ʉ'),
"ю should contain j or u, got: {}",
result
);
let result = rules.apply("я");
assert!(
result.contains('j') || result.contains('a') || result.contains('ɪ'),
"я should contain j, a, or ɪ, got: {}",
result
);
}
#[test]
fn test_complex_consonants_shch() {
let rules = base();
let result = rules.apply("щи");
assert!(
result.contains('ʃ') || result.contains('ɕ'),
"щ should contain ʃ or ɕ, got: {}",
result
);
}
#[test]
fn test_complex_consonants_zh() {
let rules = base();
let result = rules.apply("жить");
assert!(result.contains('ʒ'), "ж should become ʒ, got: {}", result);
}
#[test]
fn test_complex_consonants_sh() {
let rules = base();
let result = rules.apply("школа");
assert!(result.contains('ʃ'), "ш should become ʃ, got: {}", result);
}
#[test]
fn test_complex_consonants_ch() {
let rules = base();
let result = rules.apply("чай");
assert!(result.contains("t͡ʃ"), "ч should become t͡ʃ, got: {}", result);
}
#[test]
fn test_complex_consonants_ts() {
let rules = base();
let result = rules.apply("царь");
assert!(result.contains("t͡s"), "ц should become t͡s, got: {}", result);
}
#[test]
fn test_complex_consonants_kh() {
let rules = base();
let result = rules.apply("хлеб");
assert!(result.contains('x'), "х should become x, got: {}", result);
}
#[test]
fn test_soft_sign_removed() {
let rules = base();
let result = rules.apply("мать");
assert!(
!result.contains('ь'),
"soft sign should be removed, got: {}",
result
);
}
#[test]
fn test_hard_sign_removed() {
let rules = base();
let result = rules.apply("объект");
assert!(
!result.contains('ъ'),
"hard sign should be removed, got: {}",
result
);
}
#[test]
fn test_simple_consonants() {
let rules = base();
let result = rules.apply("молоко");
assert!(result.contains('m'), "м should become m");
assert!(result.contains('l'), "л should become l");
assert!(result.contains('k'), "к should become k");
}
#[test]
fn test_moscow() {
let rules = base();
let result = rules.apply("Москва");
assert!(
result.contains('m') && result.contains('k') && result.contains('v'),
"Москва should contain m, k, v, got: {}",
result
);
}
#[test]
fn test_final_devoicing_b() {
let rules = base();
let result = rules.apply("хлеб");
assert!(
result.ends_with('p') || result.ends_with('b'),
"final б should process correctly, got: {}",
result
);
}
#[test]
fn test_final_devoicing_d() {
let rules = base();
let result = rules.apply("год");
assert!(
result.ends_with('t') || result.ends_with('d'),
"final д should process correctly, got: {}",
result
);
}
#[test]
fn test_final_devoicing_g() {
let rules = base();
let result = rules.apply("друг");
assert!(
result.ends_with('k') || result.ends_with('ɡ'),
"final г should process correctly, got: {}",
result
);
}
#[test]
fn test_final_devoicing_v() {
let rules = base();
let result = rules.apply("кров");
assert!(
result.ends_with('f') || result.ends_with('v'),
"final в should process correctly, got: {}",
result
);
}
#[test]
fn test_final_devoicing_z() {
let rules = base();
let result = rules.apply("мороз");
assert!(
result.ends_with('s') || result.ends_with('z'),
"final з should process correctly, got: {}",
result
);
}
#[test]
fn test_uppercase() {
let rules = base();
let result = rules.apply("РОССИЯ");
assert!(
result.contains('r') && result.contains('s'),
"РОССИЯ should contain r and s, got: {}",
result
);
}
#[test]
fn test_mixed_case() {
let rules = base();
let result = rules.apply("Путин");
assert!(
result.contains('p') && result.contains('u') && result.contains('t'),
"Путин should contain p, u, t, got: {}",
result
);
}
#[test]
fn test_final_devoicing_rules_exist() {
use crate::phonetic::types::ContextChar;
let rules = base();
let final_devoicing_count = rules
.rules
.iter()
.filter(|r| matches!(r.context, ContextChar::Final))
.count();
assert!(
final_devoicing_count >= 6,
"Expected at least 6 final devoicing rules, got {}",
final_devoicing_count
);
}
}