1#![no_std]
2#![forbid(unsafe_code)]
3#![warn(missing_docs)]
4extern crate alloc;
11
12use alloc::string::String;
13use alloc::vec;
14use alloc::vec::Vec;
15
16mod diff;
17mod unicode_tables;
18
19#[cfg(test)]
20mod unit_tests;
21
22pub type Alignment = Vec<Vec<usize>>;
24
25pub type CharMap = Vec<Vec<usize>>;
27
28pub trait Normalizer {
65 fn normalize(&self, token: &str, out: &mut String);
67}
68
69impl<F> Normalizer for F
70where
71 F: Fn(&str, &mut String),
72{
73 fn normalize(&self, token: &str, out: &mut String) {
74 self(token, out);
75 }
76}
77
78#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
95pub struct Fold;
96
97impl Normalizer for Fold {
98 fn normalize(&self, token: &str, out: &mut String) {
99 if is_special(token) {
100 return;
101 }
102
103 let token = token.strip_prefix("##").unwrap_or(token);
104 let token = token.strip_suffix("</w>").unwrap_or(token);
105 for c in token.chars() {
106 if !matches!(c, '\u{2581}' | '\u{0120}' | '\u{010A}') {
107 unicode_tables::push_folded(c, out);
108 }
109 }
110 }
111}
112
113#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
115pub struct Exact;
116
117impl Normalizer for Exact {
118 fn normalize(&self, token: &str, out: &mut String) {
119 out.push_str(token);
120 }
121}
122
123pub fn get_alignments<A, B>(a: &[A], b: &[B]) -> (Alignment, Alignment)
141where
142 A: AsRef<str>,
143 B: AsRef<str>,
144{
145 get_alignments_with(a, b, &Fold)
146}
147
148pub fn get_alignments_with<A, B, N>(a: &[A], b: &[B], norm: &N) -> (Alignment, Alignment)
150where
151 A: AsRef<str>,
152 B: AsRef<str>,
153 N: Normalizer + ?Sized,
154{
155 let left = Stream::from_tokens(a, norm);
156 let right = Stream::from_tokens(b, norm);
157 align_streams(&left, &right, a.len(), b.len())
158}
159
160pub fn get_charmap(a: &str, b: &str) -> (CharMap, CharMap) {
170 get_charmap_with(a, b, &Fold)
171}
172
173pub fn get_charmap_with<N>(a: &str, b: &str, norm: &N) -> (CharMap, CharMap)
175where
176 N: Normalizer + ?Sized,
177{
178 let left = Stream::from_chars(a, norm);
179 let right = Stream::from_chars(b, norm);
180 align_streams(&left, &right, a.chars().count(), b.chars().count())
181}
182
183fn is_special(token: &str) -> bool {
184 matches!(
185 token,
186 "[CLS]"
187 | "[SEP]"
188 | "[PAD]"
189 | "[UNK]"
190 | "[MASK]"
191 | "<s>"
192 | "</s>"
193 | "<pad>"
194 | "<unk>"
195 | "<mask>"
196 | "<cls>"
197 | "<sep>"
198 | "<|endoftext|>"
199 | "<|startoftext|>"
200 )
201}
202
203struct Stream {
204 chars: Vec<char>,
205 owners: Vec<usize>,
206 units: Vec<Unit>,
207}
208
209impl Stream {
210 fn from_tokens<T, N>(tokens: &[T], norm: &N) -> Self
211 where
212 T: AsRef<str>,
213 N: Normalizer + ?Sized,
214 {
215 let estimated = tokens.iter().fold(0usize, |sum, token| {
216 sum.saturating_add(token.as_ref().len())
217 });
218 let mut stream = Self {
219 chars: Vec::with_capacity(estimated),
220 owners: Vec::with_capacity(estimated),
221 units: Vec::new(),
222 };
223 let mut scratch = String::new();
224 for (index, token) in tokens.iter().enumerate() {
225 scratch.clear();
226 norm.normalize(token.as_ref(), &mut scratch);
227 stream.extend(&scratch, index);
228 }
229 stream.group_units();
230 stream
231 }
232
233 fn from_chars<N>(text: &str, norm: &N) -> Self
234 where
235 N: Normalizer + ?Sized,
236 {
237 let count = text.chars().count();
238 let mut stream = Self {
239 chars: Vec::with_capacity(count),
240 owners: Vec::with_capacity(count),
241 units: Vec::new(),
242 };
243 let mut scratch = String::new();
244 for (index, (start, c)) in text.char_indices().enumerate() {
245 let end = start + c.len_utf8();
246 scratch.clear();
247 norm.normalize(&text[start..end], &mut scratch);
248 stream.extend(&scratch, index);
249 }
250 stream.group_units();
251 stream
252 }
253
254 fn extend(&mut self, text: &str, owner: usize) {
255 for c in text.chars() {
256 self.chars.push(c);
257 self.owners.push(owner);
258 }
259 }
260
261 fn group_units(&mut self) {
262 for (index, &c) in self.chars.iter().enumerate() {
263 if self.units.is_empty() || !unicode_tables::is_extender(c) {
264 self.units.push(Unit {
265 start: index,
266 end: index + 1,
267 });
268 } else if let Some(unit) = self.units.last_mut() {
269 unit.end = index + 1;
270 }
271 }
272 }
273
274 fn keys(&self) -> impl ExactSizeIterator<Item = char> + '_ {
275 self.units.iter().map(|unit| self.chars[unit.start])
276 }
277}
278
279#[derive(Clone, Copy)]
280struct Unit {
281 start: usize,
282 end: usize,
283}
284
285fn align_streams(
286 left: &Stream,
287 right: &Stream,
288 left_len: usize,
289 right_len: usize,
290) -> (Alignment, Alignment) {
291 let left_keys: Vec<char> = left.keys().collect();
292 let right_keys: Vec<char> = right.keys().collect();
293 let mut unit_pairs = Vec::with_capacity(left_keys.len().min(right_keys.len()));
294 diff::matched_pairs(&left_keys, &right_keys, &mut unit_pairs);
295
296 let mut left_to_right = vec![Vec::new(); left_len];
297 let mut right_to_left = vec![Vec::new(); right_len];
298 for (left_unit, right_unit) in unit_pairs {
299 project_unit(
300 left,
301 left_unit,
302 right,
303 right_unit,
304 &mut left_to_right,
305 &mut right_to_left,
306 );
307 }
308 (left_to_right, right_to_left)
309}
310
311fn project_unit(
312 left: &Stream,
313 left_index: usize,
314 right: &Stream,
315 right_index: usize,
316 left_to_right: &mut Alignment,
317 right_to_left: &mut Alignment,
318) {
319 let left_unit = left.units[left_index];
320 let right_unit = right.units[right_index];
321 project_pair(
322 left,
323 left_unit.start,
324 right,
325 right_unit.start,
326 left_to_right,
327 right_to_left,
328 );
329
330 let mut right_cursor = right_unit.start + 1;
331 for left_char in left_unit.start + 1..left_unit.end {
332 while right_cursor < right_unit.end && right.chars[right_cursor] != left.chars[left_char] {
333 right_cursor += 1;
334 }
335 if right_cursor < right_unit.end {
336 project_pair(
337 left,
338 left_char,
339 right,
340 right_cursor,
341 left_to_right,
342 right_to_left,
343 );
344 right_cursor += 1;
345 }
346 }
347}
348
349fn project_pair(
350 left: &Stream,
351 left_char: usize,
352 right: &Stream,
353 right_char: usize,
354 left_to_right: &mut Alignment,
355 right_to_left: &mut Alignment,
356) {
357 let left_owner = left.owners[left_char];
358 let right_owner = right.owners[right_char];
359 if left_owner >= left_to_right.len() || right_owner >= right_to_left.len() {
360 return;
361 }
362 push_new(&mut left_to_right[left_owner], right_owner);
363 push_new(&mut right_to_left[right_owner], left_owner);
364}
365
366fn push_new(indices: &mut Vec<usize>, index: usize) {
367 if indices.last().copied() != Some(index) {
368 indices.push(index);
369 }
370}