use std::fmt::Debug;
use daachorse::DoubleArrayAhoCorasick;
use daachorse::DoubleArrayAhoCorasickBuilder;
use serde::Deserialize;
#[derive(Clone, Debug, Deserialize)]
pub struct AddedTokenConfig {
pub id: u32,
pub content: String,
}
#[derive(Debug, PartialEq, Eq)]
pub enum Segment<'a> {
Token(u32),
Text(&'a str),
}
pub struct AddedTokens {
daac: DoubleArrayAhoCorasick<u32>,
token_lens: Vec<u16>,
start_bytes: Vec<u8>,
max_token_len: usize,
}
impl AddedTokens {
pub fn from_configs(configs: &[AddedTokenConfig]) -> Result<Option<Self>, String> {
if configs.is_empty() {
return Ok(None);
}
let max_id = configs.iter().map(|c| c.id).max().unwrap_or(0);
let mut token_lens = vec![0u16; (max_id + 1) as usize];
let mut patterns: Vec<(&str, u32)> = Vec::with_capacity(configs.len());
for c in configs {
token_lens[c.id as usize] = u16::try_from(c.content.len()).map_err(|_| {
format!("AddedToken `{}` is too long (exceed u16::MAX).", c.content)
})?;
patterns.push((c.content.as_str(), c.id));
}
let daac = DoubleArrayAhoCorasickBuilder::new()
.match_kind(daachorse::MatchKind::LeftmostLongest)
.build_with_values(patterns)
.map_err(|e| format!("error building added-tokens DAAC: {e}"))?;
let mut start_set = [false; 256];
let mut max_token_len = 0;
for c in configs {
if let Some(&b) = c.content.as_bytes().first() {
start_set[b as usize] = true;
}
max_token_len = max_token_len.max(c.content.len());
}
let start_bytes: Vec<u8> = start_set
.iter()
.enumerate()
.filter(|&(_, v)| *v)
.map(|(i, _)| i as u8)
.collect();
Ok(Some(Self {
daac,
token_lens,
start_bytes,
max_token_len,
}))
}
pub fn split<'a>(&self, input: &'a str) -> Vec<Segment<'a>> {
match self.start_bytes.len() {
1 => self.split_prefilter(
input,
memchr::memchr_iter(self.start_bytes[0], input.as_bytes()),
),
2 => self.split_prefilter(
input,
memchr::memchr2_iter(self.start_bytes[0], self.start_bytes[1], input.as_bytes()),
),
3 => self.split_prefilter(
input,
memchr::memchr3_iter(
self.start_bytes[0],
self.start_bytes[1],
self.start_bytes[2],
input.as_bytes(),
),
),
_ => self.split_full_scan(input),
}
}
fn split_prefilter<'a>(
&self,
input: &'a str,
candidates: impl Iterator<Item = usize>,
) -> Vec<Segment<'a>> {
let mut segments = Vec::new();
let mut prev_end = 0;
for pos in candidates {
if pos < prev_end {
continue;
}
let mut window_end = (pos + self.max_token_len).min(input.len());
while window_end < input.len() && !input.is_char_boundary(window_end) {
window_end += 1;
}
let window = &input[pos..window_end];
if let Some(m) = self.daac.leftmost_find_iter(window).next()
&& m.start() == 0
{
if pos > prev_end {
segments.push(Segment::Text(&input[prev_end..pos]));
}
segments.push(Segment::Token(m.value()));
prev_end = pos + m.end();
}
}
if prev_end < input.len() {
segments.push(Segment::Text(&input[prev_end..]));
}
if segments.is_empty() && !input.is_empty() {
segments.push(Segment::Text(input));
}
segments
}
fn split_full_scan<'a>(&self, input: &'a str) -> Vec<Segment<'a>> {
let mut segments = Vec::new();
let mut prev_end = 0;
for m in self.daac.leftmost_find_iter(input) {
if m.start() > prev_end {
segments.push(Segment::Text(&input[prev_end..m.start()]));
}
segments.push(Segment::Token(m.value()));
prev_end = m.end();
}
if prev_end < input.len() {
segments.push(Segment::Text(&input[prev_end..]));
}
segments
}
}
impl Debug for AddedTokens {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let count = self.token_lens.iter().filter(|&&len| len > 0).count();
f.debug_struct("AddedTokens")
.field("count", &count)
.finish()
}
}