toklen 0.2.0

A single-threaded, lightweight, and fast token counter.
Documentation
use serde::Deserialize;

use crate::pre_tokenized::PreTokenizedString;
use crate::pre_tokenized::PtSplit;

use super::Error;

/// CharDelimiterSplit pre-tokenizer: splits on a single delimiter character,
/// removing the delimiter itself, matching the HuggingFace
/// `CharDelimiterSplit` pre-tokenizer.
///
/// Empty pieces (leading, trailing, or between consecutive delimiters) are
/// skipped, so `"a,,b"` with delimiter `,` yields `["a", "b"]`.
#[derive(Clone, Debug, Deserialize)]
pub struct CharDelimiterSplit {
    delimiter: char,
}

impl CharDelimiterSplit {
    /// Refine the splits of a [`PreTokenizedString`] in place.
    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; // Vec::len <= isize::MAX
        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 mut seg_start: Option<usize> = None;

            for (i, ch) in text.char_indices() {
                if ch == self.delimiter {
                    // Close current segment (if any).
                    if let Some(start) = seg_start.take() {
                        let range = (base + start)..(base + i);
                        new_splits.push(PtSplit::from_range(range));
                    }
                } else if seg_start.is_none() {
                    // Start a new non-delimiter segment.
                    seg_start = Some(i);
                }
            }

            // Trailing segment.
            if let Some(start) = seg_start {
                let range = (base + start)..(base + text.len());
                new_splits.push(PtSplit::from_range(range));
            }
        }

        pts.splits = new_splits;
        Ok(())
    }
}