use obadh_engine::{ObadhEngine, PhoneticUnitType, Tokenizer};
#[test]
fn test_explicit_hasant_notation() {
let tokenizer = Tokenizer::new();
let units = tokenizer.tokenize_word("n,,d,,r");
assert!(!units.is_empty());
assert_eq!(units.len(), 1);
assert_eq!(units[0].unit_type, PhoneticUnitType::Conjunct);
let units = tokenizer.tokenize_word("n,,d,,r,,");
assert!(!units.is_empty());
assert_eq!(units.len(), 2);
assert_eq!(units[0].unit_type, PhoneticUnitType::Conjunct);
assert_eq!(units[0].text, "n,,d,,r");
assert_eq!(units[1].unit_type, PhoneticUnitType::ConsonantWithHasant);
assert_eq!(units[1].text, ",,");
let engine = ObadhEngine::new();
assert_eq!(engine.transliterate("n,,d,,r n,,d,,r,,"), "ন্দ্র ন্দ্র্");
}
#[test]
fn test_explicit_hasant_with_vowel() {
let tokenizer = Tokenizer::new();
let units = tokenizer.tokenize_word("n,,d,,rA");
assert!(!units.is_empty());
assert_eq!(units.len(), 1);
assert_eq!(units[0].unit_type, PhoneticUnitType::ConjunctWithVowel);
let units = tokenizer.tokenize_word("n,,d,,ro");
assert!(!units.is_empty());
assert_eq!(units.len(), 1);
assert_eq!(units[0].unit_type, PhoneticUnitType::ConjunctWithTerminator);
}
#[test]
fn test_mixed_hasant_notation() {
let tokenizer = Tokenizer::new();
let units = tokenizer.tokenize_word("k,,lr");
assert!(!units.is_empty());
let units = tokenizer.tokenize_word("n,,dr");
assert!(!units.is_empty());
}
const HASANT: char = '\u{09CD}';
const CONSONANTS: &[&str] = &[
"k", "kh", "g", "gh", "Ng", "c", "ch", "j", "jh", "NG", "T", "Th", "D", "Dh", "N", "t", "th",
"d", "dh", "n", "p", "ph", "b", "bh", "m", "z", "r", "l", "sh", "Sh", "s", "h", "x", "R",
];
const VOWELS: &[&str] = &[
"", "a", "A", "i", "I", "u", "U", "e", "E", "o", "O", "OI", "OU",
];
const MARKERS: &[&str] = &["", "^", ":", "ng", "M", "1", "rr", "t``"];
fn bears_hasant(character: char) -> bool {
matches!(
character,
'\u{0995}'..='\u{09B9}' | '\u{09BC}' | '\u{09DC}' | '\u{09DD}' | '\u{09DF}'
)
}
#[test]
fn test_hasant_is_dropped_when_it_has_no_consonant_to_sit_on() {
let engine = ObadhEngine::new();
for (input, expected) in [
("ka,,", "কা"),
("ki,,", "কি"),
("kOI,,", "কৈ"),
("ka,,k", "কাক"),
("pxa,,", "পক্সা"),
("krrka,,", "কর্কা"),
("k^,,", "কঁ"),
("k:,,", "কঃ"),
("kng,,", "কং"),
("kt``,,", "কৎ"),
("k1,,", "ক১"),
("rr,,", "র্"),
] {
assert_eq!(engine.transliterate(input), expected, "{input}");
}
}
#[test]
fn test_hasant_still_attaches_to_a_consonant() {
let engine = ObadhEngine::new();
for (input, expected) in [
("k,,", "ক্"),
("ko,,", "ক্"),
("k,,k", "ক্ক"),
("kk,,", "ক্ক্"),
(",,", "্"),
(",,k", "্ক"),
("n,,d,,r,,", "ন্দ্র্"),
("kR,,", "কড়্"),
("rr,,ka", "র্কা"),
("rrk,,Sh", "র্ক্ষ"),
] {
assert_eq!(engine.transliterate(input), expected, "{input}");
}
}
#[test]
fn test_engine_never_emits_a_hasant_without_a_consonant_before_it() {
let engine = ObadhEngine::new();
let mut inputs: Vec<String> = Vec::new();
for consonant in CONSONANTS {
for vowel in VOWELS {
for marker in MARKERS {
inputs.push(format!("{consonant}{vowel}{marker},,"));
inputs.push(format!("{consonant}{vowel}{marker},,k"));
}
}
}
assert!(inputs.len() > 3_000, "sweep too small: {}", inputs.len());
let mut violations: Vec<String> = Vec::new();
for input in &inputs {
let output = engine.transliterate(input);
let characters: Vec<char> = output.chars().collect();
for (index, &character) in characters.iter().enumerate() {
if character != HASANT {
continue;
}
let Some(&previous) = index.checked_sub(1).and_then(|i| characters.get(i)) else {
continue;
};
if !bears_hasant(previous) {
violations.push(format!(
"{input:?} -> {output:?}: hasant on U+{:04X}",
previous as u32
));
}
}
}
assert!(
violations.is_empty(),
"{} of {} inputs put a hasant on a sign that cannot carry it, first 10:\n{}",
violations.len(),
inputs.len(),
violations
.iter()
.take(10)
.cloned()
.collect::<Vec<_>>()
.join("\n")
);
}