mod canonical;
mod rules;
mod trie;
use crate::definitions::consonant_value;
use canonical::{
canonical_conjunct_part, is_derived_aspirated_ya_phola, is_derived_aspirated_ya_phola_prefix,
is_special_form_key,
};
use rules::CONJUNCT_RULES;
use trie::ConjunctTrie;
use std::collections::BTreeSet;
use std::sync::OnceLock;
#[derive(Debug)]
pub struct ConjunctDefinitions {
conjunct_trie: ConjunctTrie,
}
impl ConjunctDefinitions {
pub fn new() -> Self {
let conjunct_trie = ConjunctTrie::with_capacity(conjunct_trie_node_capacity());
let mut instance = ConjunctDefinitions { conjunct_trie };
for rule in CONJUNCT_RULES {
instance.add_conjunct(rule.key(), rule.value());
}
instance.conjunct_trie.sort_edges();
instance
}
fn add_conjunct(&mut self, key: &'static str, value: &'static str) {
self.conjunct_trie.insert(key, value);
}
pub fn can_form_conjunct(&self, key: &str) -> bool {
self.create_conjunct(key).is_some()
}
pub fn create_conjunct(&self, key: &str) -> Option<&'static str> {
let node = self.conjunct_trie.advance(self.conjunct_trie.root(), key)?;
self.conjunct_trie.value(node)
}
pub fn create_conjunct_from_parts(&self, parts: &[&str]) -> Option<&'static str> {
if parts.len() < 2 {
return None;
}
let mut node = self.conjunct_trie.root();
for part in parts {
node = self
.conjunct_trie
.advance(node, canonical_conjunct_part(part))?;
}
self.conjunct_trie.value(node)
}
pub fn can_form_conjunct_from_parts(&self, parts: &[&str]) -> bool {
self.create_conjunct_from_parts(parts).is_some() || is_derived_aspirated_ya_phola(parts)
}
pub(crate) fn can_form_derived_conjunct_from_parts(&self, parts: &[&str]) -> bool {
is_derived_aspirated_ya_phola(parts)
}
pub(crate) fn can_match_derived_conjunct_prefix(&self, parts: &[&str]) -> bool {
is_derived_aspirated_ya_phola_prefix(parts)
}
pub(crate) fn conjunct_match_root(&self) -> usize {
self.conjunct_trie.root()
}
pub(crate) fn advance_conjunct_match(&self, node: usize, part: &str) -> Option<usize> {
self.conjunct_trie
.advance(node, canonical_conjunct_part(part))
}
pub(crate) fn conjunct_match_value(&self, node: usize) -> Option<&'static str> {
self.conjunct_trie.value(node)
}
pub fn get_components(&self, conjunct: &str) -> Option<Vec<String>> {
for rule in CONJUNCT_RULES {
if rule.value() == conjunct {
return Some(self.components_for_key(rule.key()));
}
}
None
}
fn components_for_key(&self, key: &str) -> Vec<String> {
let mut components = Vec::new();
let mut i = 0;
while i < key.len() {
let mut found = false;
for len in (1..=key.len() - i).rev() {
let substr = &key[i..i + len];
if consonant_value(substr).is_some() || is_special_form_key(substr) {
components.push(substr.to_string());
i += len;
found = true;
break;
}
}
if !found {
components.push(key[i..i + 1].to_string());
i += 1;
}
}
components
}
pub fn is_valid_conjunct(&self, components: &[String]) -> bool {
if components.is_empty() {
return false;
}
let mut node = self.conjunct_trie.root();
for component in components {
let Some(next_node) = self
.conjunct_trie
.advance(node, canonical_conjunct_part(component))
else {
return false;
};
node = next_node;
}
self.conjunct_trie.value(node).is_some()
}
pub fn get_all_valid_conjuncts(&self) -> BTreeSet<&'static str> {
CONJUNCT_RULES.iter().map(|rule| rule.key()).collect()
}
pub fn is_special_form(&self, form: &str) -> bool {
is_special_form_key(form)
}
}
impl Default for ConjunctDefinitions {
fn default() -> Self {
Self::new()
}
}
pub fn conjuncts() -> &'static ConjunctDefinitions {
static INSTANCE: OnceLock<ConjunctDefinitions> = OnceLock::new();
INSTANCE.get_or_init(ConjunctDefinitions::new)
}
fn conjunct_trie_node_capacity() -> usize {
1 + CONJUNCT_RULES
.iter()
.map(|rule| rule.key().len())
.sum::<usize>()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conjunct_rule_table_has_unique_keys() {
let mut keys = BTreeSet::new();
for rule in CONJUNCT_RULES {
assert!(!rule.key().is_empty());
assert!(!rule.value().is_empty());
assert!(
keys.insert(rule.key()),
"duplicate conjunct rule key: {}",
rule.key()
);
}
}
#[test]
fn conjunct_definitions_load_every_static_rule() {
let definitions = ConjunctDefinitions::new();
assert_eq!(
definitions.get_all_valid_conjuncts().len(),
CONJUNCT_RULES.len()
);
for rule in CONJUNCT_RULES {
assert_eq!(definitions.create_conjunct(rule.key()), Some(rule.value()));
}
}
}