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/korean/base.llev");
let file = crate::phonetic::llev::parse_str(content)
.expect("Invalid embedded korean/base.llev - this is a bug in liblevenshtein");
RuleSetChar::from_llev(&file)
.expect("Failed to compile Korean base rules - this is a bug in liblevenshtein")
})
}
pub fn romanization() -> &'static RuleSetChar {
static RULESET: OnceLock<RuleSetChar> = OnceLock::new();
RULESET.get_or_init(|| {
let content = include_str!("../../../data/rules/korean/romanization.llev");
let file = crate::phonetic::llev::parse_str(content)
.expect("Invalid embedded korean/romanization.llev - this is a bug in liblevenshtein");
RuleSetChar::from_llev(&file)
.expect("Failed to compile Korean romanization 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(), "Korean base rules should not be empty");
assert!(
rules.len() > 35,
"expected >35 base rules, got {}",
rules.len()
);
}
#[test]
fn test_double_consonants() {
let rules = base();
let result = rules.apply("ㄲ");
assert!(result.contains("k͈"), "ㄲ should become k͈, got: {}", result);
let result = rules.apply("ㄸ");
assert!(result.contains("t͈"), "ㄸ should become t͈, got: {}", result);
let result = rules.apply("ㅃ");
assert!(
result.contains("p͈") || result.contains("pp"),
"ㅃ should become p͈, got: {}",
result
);
let result = rules.apply("ㅆ");
assert!(
result.contains("s͈") || result.contains("ss"),
"ㅆ should become s͈, got: {}",
result
);
let result = rules.apply("ㅉ");
assert!(
result.contains("t͡ɕ͈") || result.contains("jj"),
"ㅉ should become t͡ɕ͈, got: {}",
result
);
}
#[test]
fn test_aspirated_consonants() {
let rules = base();
let result = rules.apply("ㅋ");
assert!(result.contains('k'), "ㅋ should contain k, got: {}", result);
let result = rules.apply("ㅌ");
assert!(result.contains('t'), "ㅌ should contain t, got: {}", result);
let result = rules.apply("ㅍ");
assert!(result.contains('p'), "ㅍ should contain p, got: {}", result);
let result = rules.apply("ㅊ");
assert!(
result.contains("t͡ɕ"),
"ㅊ should become t͡ɕʰ, got: {}",
result
);
}
#[test]
fn test_basic_consonants() {
let rules = base();
let result = rules.apply("ㄱ");
assert!(result.contains('k'), "ㄱ should become k, got: {}", result);
let result = rules.apply("ㄴ");
assert!(result.contains('n'), "ㄴ should become n, got: {}", result);
let result = rules.apply("ㄷ");
assert!(
result.contains('t') || result.contains('d'),
"ㄷ should become t or d, got: {}",
result
);
let result = rules.apply("ㄹ");
assert!(
result.contains('r') || result.contains('l'),
"ㄹ should become r or l, got: {}",
result
);
let result = rules.apply("ㅁ");
assert!(result.contains('m'), "ㅁ should become m, got: {}", result);
let result = rules.apply("ㅂ");
assert!(
result.contains('p') || result.contains('b'),
"ㅂ should become p or b, got: {}",
result
);
let result = rules.apply("ㅅ");
assert!(
result.contains('s') || result.contains('t'),
"ㅅ should become s or t, got: {}",
result
);
let result = rules.apply("ㅎ");
assert!(
result.contains('h') || result.is_empty() || result == "ㅎ",
"ㅎ should become h or be dropped, got: {}",
result
);
}
#[test]
fn test_basic_vowels() {
let rules = base();
let result = rules.apply("ㅏ");
assert!(result.contains('a'), "ㅏ should become a, got: {}", result);
let result = rules.apply("ㅗ");
assert!(result.contains('o'), "ㅗ should become 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);
}
#[test]
fn test_y_vowels() {
let rules = base();
let result = rules.apply("ㅑ");
assert!(
result.contains("ya") || result.contains("ja"),
"ㅑ should become ya, got: {}",
result
);
let result = rules.apply("ㅛ");
assert!(
result.contains("yo") || result.contains("jo"),
"ㅛ should become yo, got: {}",
result
);
let result = rules.apply("ㅠ");
assert!(
result.contains("yu") || result.contains("ju"),
"ㅠ should become yu, got: {}",
result
);
}
#[test]
fn test_w_vowels() {
let rules = base();
let result = rules.apply("ㅘ");
assert!(
result.contains("wa"),
"ㅘ should become wa, got: {}",
result
);
let result = rules.apply("ㅟ");
assert!(
result.contains("wi"),
"ㅟ should become wi, got: {}",
result
);
}
#[test]
fn test_compound_finals() {
let rules = base();
let result = rules.apply("ㄳ");
assert!(
result.contains("kt") || result.contains("ks"),
"ㄳ should become kt or ks, got: {}",
result
);
let result = rules.apply("ㄻ");
assert!(
result.contains("lm"),
"ㄻ should become lm, got: {}",
result
);
}
#[test]
fn test_ieung_ng() {
let rules = base();
let result = rules.apply("ㅇ");
assert!(result.contains("ŋ"), "ㅇ should become ng, got: {}", result);
}
#[test]
fn test_jamo_sequence() {
let rules = base();
let result = rules.apply("ㄱㅏ");
assert!(
(result.contains('k') || result.contains('ɡ')) && result.contains('a'),
"ㄱㅏ should become ka or ɡa, got: {}",
result
);
}
#[test]
fn test_romanization_loads() {
let rules = romanization();
assert!(
!rules.is_empty(),
"Korean romanization rules should not be empty"
);
assert!(
rules.len() >= 25,
"expected >=25 romanization rules, got {}",
rules.len()
);
}
#[test]
fn test_romanization_breve_o() {
let rules = romanization();
let result = rules.apply("ŏ");
assert!(
result.contains('ʌ') || result.contains("eo") || result.contains('O'),
"ŏ should become ʌ or eo, got: {}",
result
);
}
#[test]
fn test_romanization_breve_u() {
let rules = romanization();
let result = rules.apply("ŭ");
assert!(
result.contains('ɯ') || result.contains("eu") || result.contains('U'),
"ŭ should become ɯ or eu, got: {}",
result
);
}
#[test]
fn test_romanization_aspirate_k() {
let rules = romanization();
let result = rules.apply("k'");
assert!(
result.contains('k') && !result.contains('\''),
"k' should become k, got: {}",
result
);
}
#[test]
fn test_romanization_aspirate_p() {
let rules = romanization();
let result = rules.apply("p'");
assert!(
result.contains('p') && !result.contains('\''),
"p' should become p, got: {}",
result
);
}
#[test]
fn test_romanization_aspirate_t() {
let rules = romanization();
let result = rules.apply("t'");
assert!(
result.contains('t') && !result.contains('\''),
"t' should become t, got: {}",
result
);
}
#[test]
fn test_romanization_aspirate_ch() {
let rules = romanization();
let result = rules.apply("ch'");
assert!(
(result.contains("t͡ɕ") || result.contains("ch") || result.contains('C'))
&& !result.contains('\''),
"ch' should become t͡ɕ or ch, got: {}",
result
);
}
#[test]
fn test_romanization_seoul_mr() {
let rules = romanization();
let result = rules.apply("Sŏul");
assert!(
result.contains('ʌ') || result.contains("eo") || result.contains('O'),
"Sŏul should have ʌ or eo, got: {}",
result
);
}
#[test]
fn test_romanization_hyphen_removal() {
let rules = romanization();
let result = rules.apply("Tae-gu");
assert!(
!result.contains('-'),
"Hyphen should be removed, got: {}",
result
);
}
}