mod conjunct_runs;
mod diacritics;
mod explicit_hasant;
mod forms;
mod long_iya;
mod normalization;
mod parts;
mod patterns;
mod scan_hints;
mod scanner;
use diacritics::{append_trailing_diacritics, split_trailing_diacritics};
use forms::identify_complex_forms;
use patterns::{phonetic_pattern_trie_static, PatternTrie};
use scan_hints::WordScanHints;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum TokenType {
Word,
Punctuation,
Whitespace,
Number,
Symbol,
}
#[derive(Debug, Clone)]
pub struct Token {
pub content: String,
pub token_type: TokenType,
pub position: usize,
}
#[derive(Debug, Clone)]
pub struct PhoneticUnit {
pub text: String,
pub unit_type: PhoneticUnitType,
pub position: usize,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum PhoneticUnitType {
Consonant,
Vowel,
TerminatingVowel,
ConsonantWithVowel,
ConsonantWithTerminator,
ConsonantWithHasant,
Conjunct,
ConjunctWithVowel,
ConjunctWithTerminator,
RephOverConsonant,
RephOverConsonantWithVowel,
RephOverConsonantWithTerminator,
SpecialForm,
Numeral,
Symbol,
Unknown,
}
pub struct Tokenizer {
phonetic_patterns: &'static PatternTrie,
}
impl Tokenizer {
pub fn new() -> Self {
Tokenizer {
phonetic_patterns: phonetic_pattern_trie_static(),
}
}
pub fn tokenize_text(&self, text: &str) -> Vec<Token> {
scanner::tokenize_text(text)
}
pub fn tokenize_word(&self, word: &str) -> Vec<PhoneticUnit> {
let mut units = Vec::new();
self.tokenize_word_into(word, &mut units);
units
}
pub fn tokenize_word_into(&self, word: &str, units: &mut Vec<PhoneticUnit>) {
units.clear();
reserve_word_unit_capacity(units, word);
let mut _i = 0;
let (processed_word, trailing_diacritics) = split_trailing_diacritics(word);
if processed_word.is_empty() && !trailing_diacritics.is_empty() {
append_trailing_diacritics(units, trailing_diacritics);
return;
}
let mut scan_hints = WordScanHints::default();
while _i < processed_word.len() {
if let Some(rule_match) = self.phonetic_patterns.match_at(processed_word, _i) {
let unit = PhoneticUnit {
text: rule_match.text.to_string(),
unit_type: rule_match.unit_type,
position: _i,
};
scan_hints.observe_unit(&unit, units.last());
units.push(unit);
_i += rule_match.byte_len;
continue;
}
if _i < processed_word.len() {
let char_len = processed_word[_i..]
.chars()
.next()
.map_or(1, |c| c.len_utf8());
let unknown_text = &processed_word[_i.._i + char_len];
scan_hints.observe_unknown_text(unknown_text, processed_word, _i);
units.push(PhoneticUnit {
text: unknown_text.to_string(),
unit_type: unmatched_unit_type(unknown_text),
position: _i,
});
_i += char_len;
}
}
identify_complex_forms(units, scan_hints);
append_trailing_diacritics(units, trailing_diacritics);
}
}
impl Default for Tokenizer {
fn default() -> Self {
Self::new()
}
}
fn unmatched_unit_type(text: &str) -> PhoneticUnitType {
if text.len() == 1 && text.as_bytes()[0].is_ascii_digit() {
PhoneticUnitType::Numeral
} else {
PhoneticUnitType::Unknown
}
}
fn reserve_word_unit_capacity(units: &mut Vec<PhoneticUnit>, word: &str) {
let max_units = word.len();
if units.capacity() < max_units {
units.reserve(max_units);
}
}
fn move_unit(units: &mut [PhoneticUnit], read: usize, write: usize) {
if write != read {
units[write] = PhoneticUnit {
text: std::mem::take(&mut units[read].text),
unit_type: units[read].unit_type,
position: units[read].position,
};
}
}
fn reph_base_part(unit: &PhoneticUnit) -> Option<&str> {
if unit.unit_type == PhoneticUnitType::RephOverConsonant {
unit.text.strip_prefix("rr").filter(|part| !part.is_empty())
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tokenize_word_into_reserves_for_byte_bounded_unit_count() {
let tokenizer = Tokenizer::new();
let mut units = Vec::new();
let word = "rrkShkShmyntrngghya";
tokenizer.tokenize_word_into(word, &mut units);
assert!(units.capacity() >= word.len());
}
#[test]
fn tokenize_word_into_reuses_larger_existing_capacity() {
let tokenizer = Tokenizer::new();
let mut units = Vec::with_capacity(64);
tokenizer.tokenize_word_into("kk", &mut units);
assert_eq!(units.capacity(), 64);
}
}