use serde::Deserialize;
use crate::pre_tokenized::PreTokenizedString;
use crate::pre_tokenized::PtSplit;
use super::Error;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum Behavior {
#[default]
Isolated,
Removed,
MergedWithPrevious,
MergedWithNext,
Contiguous,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Punctuation {
#[serde(default)]
behavior: Behavior,
}
impl Punctuation {
pub fn pre_tokenize(&self, pts: &mut PreTokenizedString) -> Result<(), Error> {
let buffer = pts.buffer.as_str();
let old_splits = &pts.splits;
let hint: usize = old_splits.len() << 1; let mut new_splits = Vec::with_capacity(hint);
for split in old_splits {
if split.token_id.is_some() {
new_splits.push(split.clone());
continue;
}
let text = &buffer[split.range.clone()];
if text.is_empty() {
continue;
}
let base = split.range.start;
let segments = find_punctuation_segments(text);
let ranges = apply_behavior(self.behavior, &segments);
for (s, e) in ranges {
if s < e {
new_splits.push(PtSplit {
range: (base + s)..(base + e),
token_id: None,
});
}
}
}
pts.splits = new_splits;
Ok(())
}
}
fn find_punctuation_segments(input: &str) -> Vec<(usize, usize, bool)> {
let mut segments = Vec::new();
let mut current_start = 0;
let mut current_is_punct = is_punctuation_char(input.chars().next());
for (i, ch) in input.char_indices() {
let is_punct = is_punctuation_char(Some(ch));
if is_punct != current_is_punct {
if i > current_start {
segments.push((current_start, i, current_is_punct));
}
current_start = i;
current_is_punct = is_punct;
}
}
if current_start < input.len() {
segments.push((current_start, input.len(), current_is_punct));
}
segments
}
fn is_punctuation_char(c: Option<char>) -> bool {
match c {
Some(ch) => {
if ch.is_ascii_punctuation() {
return true;
}
if ch.is_alphabetic() || ch.is_numeric() || ch.is_whitespace() {
return false;
}
true
}
None => false,
}
}
fn apply_behavior(behavior: Behavior, segments: &[(usize, usize, bool)]) -> Vec<(usize, usize)> {
match behavior {
Behavior::Removed => segments
.iter()
.filter(|&&(_, _, is_punct)| !is_punct)
.map(|&(s, e, _)| (s, e))
.collect(),
Behavior::Isolated => segments.iter().map(|&(s, e, _)| (s, e)).collect(),
Behavior::Contiguous => {
let mut result: Vec<(usize, usize)> = Vec::new();
let mut prev_is_punct = None;
for &(s, e, is_punct) in segments {
if prev_is_punct == Some(is_punct) {
if let Some(last) = result.last_mut() {
last.1 = e;
}
} else {
result.push((s, e));
}
prev_is_punct = Some(is_punct);
}
result
}
Behavior::MergedWithPrevious => {
let mut result: Vec<(usize, usize)> = Vec::new();
let mut prev_was_punct = false;
for &(s, e, is_punct) in segments {
if is_punct && !prev_was_punct {
if let Some(last) = result.last_mut() {
last.1 = e;
} else {
result.push((s, e));
}
} else {
result.push((s, e));
}
prev_was_punct = is_punct;
}
result
}
Behavior::MergedWithNext => {
let mut result: Vec<(usize, usize)> = Vec::new();
let mut prev_was_punct = false;
for &(s, e, is_punct) in segments.iter().rev() {
if is_punct && !prev_was_punct {
if let Some(last) = result.last_mut() {
last.0 = s;
} else {
result.push((s, e));
}
} else {
result.push((s, e));
}
prev_was_punct = is_punct;
}
result.reverse();
result
}
}
}