use unicode_segmentation::UnicodeSegmentation;
pub fn orp_index(word: &str) -> usize {
let len = word.chars().count();
if len <= 2 {
0
} else {
((len + 1) / 2).min(4) - 1
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Token {
pub word: String,
pub orp_index: usize,
pub byte_offset: usize,
pub byte_len: usize,
pub is_sentence_end: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TokenizeError {
EmptyText,
}
pub fn tokenize(text: &str) -> Result<Vec<Token>, TokenizeError> {
if text.is_empty() {
return Err(TokenizeError::EmptyText);
}
let mut tokens = Vec::new();
let mut byte_offset = 0;
let mut prev_char_was_sentence_end = false;
for word in text.split_word_bounds() {
let trimmed = word.trim();
if trimmed.is_empty() {
byte_offset += word.len();
if word.contains('\n') {
prev_char_was_sentence_end = true;
}
continue;
}
let is_sentence_end = matches!(
trimmed.chars().last(),
Some('.') | Some('!') | Some('?')
);
let display_word = if prev_char_was_sentence_end {
let mut chars: Vec<char> = trimmed.chars().collect();
if let Some(c) = chars.first_mut() {
if c.is_alphabetic() {
*c = c.to_ascii_uppercase();
}
}
chars.into_iter().collect()
} else {
trimmed.to_string()
};
let orp = orp_index(&display_word);
tokens.push(Token {
word: display_word,
orp_index: orp,
byte_offset,
byte_len: word.len(),
is_sentence_end,
});
byte_offset += word.len();
prev_char_was_sentence_end = is_sentence_end;
}
if tokens.is_empty() {
return Err(TokenizeError::EmptyText);
}
Ok(tokens)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_orp_short_word() {
assert_eq!(orp_index("a"), 0);
assert_eq!(orp_index("an"), 0);
assert_eq!(orp_index("and"), 1);
}
#[test]
fn test_orp_medium_word() {
assert_eq!(orp_index("hello"), 2);
assert_eq!(orp_index("world"), 2);
assert_eq!(orp_index("speed"), 2);
}
#[test]
fn test_orp_long_word() {
assert_eq!(orp_index("reading"), 3);
assert_eq!(orp_index("presentation"), 3);
}
#[test]
fn test_tokenize_simple() {
let tokens = tokenize("Hello world").unwrap();
assert_eq!(tokens.len(), 2);
assert_eq!(tokens[0].word, "Hello");
assert_eq!(tokens[0].orp_index, 2);
assert_eq!(tokens[1].word, "world");
assert_eq!(tokens[1].orp_index, 2);
}
#[test]
fn test_tokenize_empty() {
assert_eq!(tokenize(""), Err(TokenizeError::EmptyText));
}
#[test]
fn test_tokenize_sentence_end() {
let tokens = tokenize("Go").unwrap();
assert_eq!(tokens.len(), 1);
}
#[test]
fn test_tokenize_byte_offsets() {
let tokens = tokenize("Hello world").unwrap();
assert_eq!(tokens[0].byte_offset, 0);
assert_eq!(tokens[1].byte_offset, 6); }
#[test]
fn test_tokenize_unicode() {
let tokens = tokenize("Привет мир").unwrap();
assert_eq!(tokens.len(), 2);
assert_eq!(tokens[0].word, "Привет");
assert_eq!(tokens[1].word, "мир");
}
}