use unicode_general_category::{get_general_category, GeneralCategory};
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
enum Class {
Punct = 0,
Letter = 1,
Digit = 2,
Space = 3,
Newline = 4,
Lead = 5,
}
const CLASS: [Class; 256] = {
let mut table = [Class::Punct; 256];
let mut b = 0usize;
while b < 256 {
table[b] = if b >= 0x80 {
Class::Lead
} else if (b >= b'a' as usize && b <= b'z' as usize)
|| (b >= b'A' as usize && b <= b'Z' as usize)
{
Class::Letter
} else if b >= b'0' as usize && b <= b'9' as usize {
Class::Digit
} else if b == b'\r' as usize || b == b'\n' as usize {
Class::Newline
} else if b == b' ' as usize || b == b'\t' as usize || b == 0x0b || b == 0x0c {
Class::Space
} else {
Class::Punct
};
b += 1;
}
table
};
const ONES: u64 = 0x0101_0101_0101_0101;
const HIGH: u64 = 0x8080_8080_8080_8080;
#[inline(always)]
fn lanes_below(x: u64, n: u8) -> u64 {
let guarded = x | HIGH;
!guarded.wrapping_sub(ONES.wrapping_mul(n as u64)) & HIGH
}
#[inline(always)]
fn ascii_letter_lanes(word: u64) -> u64 {
let lowered = word | 0x2020_2020_2020_2020;
let below_a = lanes_below(lowered, b'a');
let at_most_z = lanes_below(lowered, b'z' + 1);
at_most_z & !below_a & HIGH
}
#[inline(always)]
fn word_at(bytes: &[u8], pos: usize) -> Option<u64> {
bytes
.get(pos..pos + 8)
.map(|chunk| u64::from_le_bytes(chunk.try_into().expect("slice of exactly eight bytes")))
}
#[inline]
fn swar_skip_ascii_letters(bytes: &[u8], mut pos: usize) -> usize {
if bytes
.get(pos)
.is_none_or(|&b| (b | 0x20).wrapping_sub(b'a') >= 26)
{
return pos;
}
while let Some(word) = word_at(bytes, pos) {
if word & HIGH != 0 {
break;
}
let not_letters = !ascii_letter_lanes(word) & HIGH;
if not_letters != 0 {
return pos + (not_letters.trailing_zeros() / 8) as usize;
}
pos += 8;
}
while pos < bytes.len() && (bytes[pos] | 0x20).wrapping_sub(b'a') < 26 {
pos += 1;
}
pos
}
#[inline]
fn swar_skip_ascii_upper(bytes: &[u8], mut pos: usize) -> usize {
while let Some(word) = word_at(bytes, pos) {
if word & HIGH != 0 {
break;
}
let in_run = lanes_below(word, b'Z' + 1) & !lanes_below(word, b'A') & HIGH;
let out = !in_run & HIGH;
if out != 0 {
return pos + (out.trailing_zeros() / 8) as usize;
}
pos += 8;
}
while pos < bytes.len() && bytes[pos].is_ascii_uppercase() {
pos += 1;
}
pos
}
#[inline]
fn swar_skip_ascii_lower(bytes: &[u8], mut pos: usize) -> usize {
while let Some(word) = word_at(bytes, pos) {
if word & HIGH != 0 {
break;
}
let in_run = lanes_below(word, b'z' + 1) & !lanes_below(word, b'a') & HIGH;
let out = !in_run & HIGH;
if out != 0 {
return pos + (out.trailing_zeros() / 8) as usize;
}
pos += 8;
}
while pos < bytes.len() && bytes[pos].is_ascii_lowercase() {
pos += 1;
}
pos
}
#[inline]
fn swar_skip_ascii_space(bytes: &[u8], mut pos: usize) -> usize {
while let Some(word) = word_at(bytes, pos) {
if word & HIGH != 0 {
break;
}
let control = lanes_below(word, 0x0E) & !lanes_below(word, 0x09);
let space = lanes_below(word, b' ' + 1) & !lanes_below(word, b' ');
let out = !(control | space) & HIGH;
if out != 0 {
return pos + (out.trailing_zeros() / 8) as usize;
}
pos += 8;
}
while pos < bytes.len() && matches!(bytes[pos], 0x09..=0x0D | b' ') {
pos += 1;
}
pos
}
#[inline]
fn swar_skip_ascii(bytes: &[u8], mut pos: usize) -> usize {
while let Some(word) = word_at(bytes, pos) {
let high = word & HIGH;
if high != 0 {
return pos + (high.trailing_zeros() / 8) as usize;
}
pos += 8;
}
while pos < bytes.len() && bytes[pos] < 0x80 {
pos += 1;
}
pos
}
#[inline]
fn swar_skip_to_number(bytes: &[u8], mut pos: usize) -> usize {
while let Some(word) = word_at(bytes, pos) {
if word & HIGH != 0 {
break;
}
let digits = lanes_below(word, b'9' + 1) & !lanes_below(word, b'0') & HIGH;
if digits != 0 {
return pos + (digits.trailing_zeros() / 8) as usize;
}
pos += 8;
}
while pos < bytes.len() && bytes[pos] < 0x80 && !bytes[pos].is_ascii_digit() {
pos += 1;
}
pos
}
#[inline]
fn is_letter_char(c: char) -> bool {
matches!(
get_general_category(c),
GeneralCategory::UppercaseLetter
| GeneralCategory::LowercaseLetter
| GeneralCategory::TitlecaseLetter
| GeneralCategory::ModifierLetter
| GeneralCategory::OtherLetter
)
}
#[inline]
fn is_number_char(c: char) -> bool {
matches!(
get_general_category(c),
GeneralCategory::DecimalNumber
| GeneralCategory::LetterNumber
| GeneralCategory::OtherNumber
)
}
#[inline]
fn is_mark_char(c: char) -> bool {
matches!(
get_general_category(c),
GeneralCategory::NonspacingMark
| GeneralCategory::SpacingMark
| GeneralCategory::EnclosingMark
)
}
#[inline]
fn is_punct_or_symbol_char(c: char) -> bool {
matches!(
get_general_category(c),
GeneralCategory::ConnectorPunctuation
| GeneralCategory::DashPunctuation
| GeneralCategory::OpenPunctuation
| GeneralCategory::ClosePunctuation
| GeneralCategory::InitialPunctuation
| GeneralCategory::FinalPunctuation
| GeneralCategory::OtherPunctuation
| GeneralCategory::MathSymbol
| GeneralCategory::CurrencySymbol
| GeneralCategory::ModifierSymbol
| GeneralCategory::OtherSymbol
)
}
#[inline]
fn is_upper_run_char(c: char) -> bool {
matches!(
get_general_category(c),
GeneralCategory::UppercaseLetter
| GeneralCategory::TitlecaseLetter
| GeneralCategory::ModifierLetter
| GeneralCategory::OtherLetter
) || is_mark_char(c)
}
#[inline]
fn is_lower_run_char(c: char) -> bool {
matches!(
get_general_category(c),
GeneralCategory::LowercaseLetter
| GeneralCategory::ModifierLetter
| GeneralCategory::OtherLetter
) || is_mark_char(c)
}
#[inline]
fn char_at(text: &str, pos: usize) -> (char, usize) {
let c = text[pos..]
.chars()
.next()
.expect("pos is a char boundary inside the string");
(c, c.len_utf8())
}
#[inline]
fn char_len_if(
text: &str,
bytes: &[u8],
pos: usize,
ascii: impl Fn(Class) -> bool,
pred: impl Fn(char) -> bool,
) -> Option<usize> {
let class = CLASS[bytes[pos] as usize];
if class == Class::Lead {
let (c, len) = char_at(text, pos);
pred(c).then_some(len)
} else {
ascii(class).then_some(1)
}
}
#[inline]
fn letter_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
char_len_if(text, bytes, pos, |c| c == Class::Letter, is_letter_char)
}
#[inline]
fn number_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
char_len_if(text, bytes, pos, |c| c == Class::Digit, is_number_char)
}
#[inline]
fn space_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
char_len_if(
text,
bytes,
pos,
|c| matches!(c, Class::Space | Class::Newline),
char::is_whitespace,
)
}
#[inline]
fn punct_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
char_len_if(
text,
bytes,
pos,
|c| c == Class::Punct,
|c| !c.is_whitespace() && !is_letter_char(c) && !is_number_char(c),
)
}
#[inline]
fn scan_letter_or_mark_run(text: &str, bytes: &[u8], pos: usize, n: usize) -> usize {
if n == 1 {
scan_run_of(text, bytes, pos + n, LETTER_OR_MARK)
} else {
scan_run(text, bytes, pos + n, letter_or_mark_at)
}
}
const LETTER_OR_MARK: Run = Run {
at: letter_or_mark_at,
skip_ascii: swar_skip_ascii_letters,
};
#[inline]
fn letter_or_mark_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
char_len_if(
text,
bytes,
pos,
|c| c == Class::Letter,
|c| is_letter_char(c) || is_mark_char(c),
)
}
#[inline]
fn punct_or_symbol_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
let class = CLASS[bytes[pos] as usize];
if class == Class::Lead {
let (c, len) = char_at(text, pos);
is_punct_or_symbol_char(c).then_some(len)
} else {
let b = bytes[pos];
(class == Class::Punct && b.is_ascii_graphic()).then_some(1)
}
}
#[inline]
fn scan_letters(text: &str, bytes: &[u8], mut pos: usize) -> usize {
loop {
pos = swar_skip_ascii_letters(bytes, pos);
if pos >= bytes.len() || bytes[pos] < 0x80 {
return pos;
}
match letter_at(text, bytes, pos) {
Some(n) => pos += n,
None => return pos,
}
}
}
type RunFn = fn(&str, &[u8], usize) -> Option<usize>;
#[derive(Clone, Copy)]
struct Run {
at: RunFn,
skip_ascii: fn(&[u8], usize) -> usize,
}
const IDEOGRAPH_LEAD: std::ops::RangeInclusive<u8> = 0xE5..=0xE9;
#[inline]
fn skip_ideographs(bytes: &[u8], mut pos: usize) -> usize {
while pos + 3 <= bytes.len() && IDEOGRAPH_LEAD.contains(&bytes[pos]) {
pos += 3;
}
pos
}
macro_rules! ascii_or_ideograph_skip {
($name:ident, $ascii:ident) => {
fn $name(bytes: &[u8], mut pos: usize) -> usize {
loop {
let advanced = skip_ideographs(bytes, $ascii(bytes, pos));
if advanced == pos {
return pos;
}
pos = advanced;
}
}
};
}
ascii_or_ideograph_skip!(skip_upper_or_ideograph, swar_skip_ascii_upper);
ascii_or_ideograph_skip!(skip_lower_or_ideograph, swar_skip_ascii_lower);
#[inline]
fn scan_run_of(text: &str, bytes: &[u8], mut pos: usize, run: Run) -> usize {
loop {
pos = (run.skip_ascii)(bytes, pos);
if pos >= bytes.len() {
return pos;
}
match (run.at)(text, bytes, pos) {
Some(n) => pos += n,
None => return pos,
}
}
}
#[inline]
fn scan_run(
text: &str,
bytes: &[u8],
mut pos: usize,
at: impl Fn(&str, &[u8], usize) -> Option<usize>,
) -> usize {
while pos < bytes.len() {
match at(text, bytes, pos) {
Some(n) => pos += n,
None => break,
}
}
pos
}
#[inline]
fn contraction_len(bytes: &[u8], pos: usize) -> Option<usize> {
if bytes.get(pos) != Some(&b'\'') {
return None;
}
let lower = |i: usize| bytes.get(i).map(|b| b | 0x20);
match lower(pos + 1)? {
b's' | b'd' | b'm' | b't' => Some(2),
b'l' if lower(pos + 2) == Some(b'l') => Some(3),
b'v' | b'r' if lower(pos + 2) == Some(b'e') => Some(3),
_ => None,
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum WhitespaceOrder {
Plain,
EndOfTextFirst,
NewlineFirst,
}
#[inline]
fn whitespace_span(text: &str, bytes: &[u8], pos: usize, order: WhitespaceOrder) -> (usize, usize) {
let len = bytes.len();
let run_end = scan_run_of(text, bytes, pos, SPACE_RUN);
match order {
WhitespaceOrder::EndOfTextFirst if run_end == len => return (pos, run_end),
WhitespaceOrder::Plain => {}
_ => {
let last_newline = bytes[pos..run_end]
.iter()
.rposition(|&b| b == b'\r' || b == b'\n')
.map(|offset| pos + offset + 1);
if let Some(end) = last_newline {
return (pos, end);
}
}
}
if run_end == len {
return (pos, run_end);
}
let last_char_len = text[pos..run_end]
.chars()
.next_back()
.map(char::len_utf8)
.unwrap_or(1);
if run_end - pos > last_char_len {
return (pos, run_end - last_char_len);
}
(pos, run_end)
}
#[derive(Clone, Copy)]
struct Family {
max_digits: u32,
whitespace: WhitespaceOrder,
}
const CL100K: Family = Family {
max_digits: 3,
whitespace: WhitespaceOrder::EndOfTextFirst,
};
const LLAMA3: Family = Family {
max_digits: 3,
whitespace: WhitespaceOrder::NewlineFirst,
};
const QWEN2: Family = Family {
max_digits: 1,
whitespace: WhitespaceOrder::NewlineFirst,
};
fn family_spans(text: &str, out: &mut Vec<(usize, usize)>, scheme: Family) {
let bytes = text.as_bytes();
let len = bytes.len();
let mut pos = 0usize;
while pos < len {
let start = pos;
let byte = bytes[pos];
match CLASS[byte as usize] {
Class::Letter => {
pos = scan_letters(text, bytes, pos + 1);
}
Class::Digit => {
pos += 1;
for _ in 1..scheme.max_digits {
match (pos < len).then(|| number_at(text, bytes, pos)).flatten() {
Some(n) => pos += n,
None => break,
}
}
}
Class::Punct => {
if let Some(n) = contraction_len(bytes, pos) {
pos += n;
} else if let Some(n) = prefixed_letters(text, bytes, pos + 1, len) {
pos = n;
} else {
pos = punct_run(text, bytes, pos, len);
}
}
Class::Space => {
if let Some(n) = prefixed_letters(text, bytes, pos + 1, len) {
pos = n;
} else if byte == b' ' && pos + 1 < len && punct_at(text, bytes, pos + 1).is_some()
{
pos = punct_run(text, bytes, pos + 1, len);
} else {
let (s, e) = whitespace_span(text, bytes, pos, scheme.whitespace);
pos = e;
out.push((s, e));
continue;
}
}
Class::Newline => {
let (s, e) = whitespace_span(text, bytes, pos, scheme.whitespace);
pos = e;
out.push((s, e));
continue;
}
Class::Lead => {
let (c, l) = char_at(text, pos);
if is_letter_char(c) {
pos = scan_letters(text, bytes, pos + l);
} else if is_number_char(c) {
pos += l;
for _ in 1..scheme.max_digits {
match (pos < len).then(|| number_at(text, bytes, pos)).flatten() {
Some(n) => pos += n,
None => break,
}
}
} else if let Some(n) = prefixed_letters(text, bytes, pos + l, len) {
pos = n;
} else if !c.is_whitespace() {
pos = punct_run(text, bytes, pos, len);
} else {
let (s, e) = whitespace_span(text, bytes, pos, scheme.whitespace);
pos = e;
out.push((s, e));
continue;
}
}
}
out.push((start, pos));
}
}
#[inline]
fn prefixed_letters(text: &str, bytes: &[u8], pos: usize, len: usize) -> Option<usize> {
if pos >= len {
return None;
}
let n = letter_at(text, bytes, pos)?;
Some(scan_letters(text, bytes, pos + n))
}
#[inline]
fn punct_run(text: &str, bytes: &[u8], pos: usize, len: usize) -> usize {
let mut end = scan_run(text, bytes, pos, punct_at);
while end < len && (bytes[end] == b'\r' || bytes[end] == b'\n') {
end += 1;
}
end
}
#[inline]
fn prefix_len(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
match CLASS[bytes[pos] as usize] {
Class::Newline | Class::Digit | Class::Letter => None,
Class::Lead => {
let (c, l) = char_at(text, pos);
(!is_letter_char(c) && !is_number_char(c)).then_some(l)
}
_ => Some(1),
}
}
pub(super) fn cl100k_spans(text: &str, out: &mut Vec<(usize, usize)>) {
family_spans(text, out, CL100K)
}
pub(super) fn llama3_spans(text: &str, out: &mut Vec<(usize, usize)>) {
family_spans(text, out, LLAMA3)
}
pub(super) fn qwen2_spans(text: &str, out: &mut Vec<(usize, usize)>) {
family_spans(text, out, QWEN2)
}
pub(super) fn o200k_spans(text: &str, out: &mut Vec<(usize, usize)>) {
case_family_spans(text, out, 3, true)
}
pub(super) fn mistral_v3_spans(text: &str, out: &mut Vec<(usize, usize)>) {
case_family_spans(text, out, 1, false)
}
fn case_family_spans(text: &str, out: &mut Vec<(usize, usize)>, digits: usize, contractions: bool) {
let bytes = text.as_bytes();
let len = bytes.len();
let mut pos = 0usize;
while pos < len {
let start = pos;
if let Some(end) = o200k_letter_branches(text, bytes, pos, contractions) {
pos = end;
out.push((start, pos));
continue;
}
if let Some(n) = number_at(text, bytes, pos) {
pos += n;
for _ in 1..digits {
match (pos < len).then(|| number_at(text, bytes, pos)).flatten() {
Some(n) => pos += n,
None => break,
}
}
out.push((start, pos));
continue;
}
let after_space = if bytes[pos] == b' ' { pos + 1 } else { pos };
if after_space < len && punct_at(text, bytes, after_space).is_some() {
pos = scan_run(text, bytes, after_space, punct_at);
while pos < len && matches!(bytes[pos], b'\r' | b'\n' | b'/') {
pos += 1;
}
out.push((start, pos));
continue;
}
if space_at(text, bytes, pos).is_some() {
let (s, e) = whitespace_span(text, bytes, pos, WhitespaceOrder::NewlineFirst);
pos = e;
out.push((s, e));
continue;
}
let (_, l) = char_at(text, pos);
pos += l;
}
}
#[inline(always)]
fn o200k_letter_branches(
text: &str,
bytes: &[u8],
pos: usize,
contractions: bool,
) -> Option<usize> {
case_split_branches(
text,
bytes,
pos,
Run {
at: upper_run_at,
skip_ascii: skip_upper_or_ideograph,
},
Run {
at: lower_run_at,
skip_ascii: skip_lower_or_ideograph,
},
contractions,
)
}
#[inline(always)]
fn case_split_branches(
text: &str,
bytes: &[u8],
pos: usize,
upper: Run,
lower: Run,
contractions: bool,
) -> Option<usize> {
match ascii_case_split(bytes, pos, contractions) {
AsciiCase::Match(end) => return Some(end),
AsciiCase::NoMatch => return None,
AsciiCase::Undecided => {}
}
let with_prefix = prefix_len(text, bytes, pos).map(|p| pos + p);
for &q in [with_prefix, Some(pos)].iter().flatten() {
if q < bytes.len() {
if let Some(end) = case_split_branch_a(text, bytes, q, upper, lower, contractions) {
return Some(end);
}
}
}
for &q in [with_prefix, Some(pos)].iter().flatten() {
if q < bytes.len() {
if let Some(end) = case_split_branch_b(text, bytes, q, upper, lower, contractions) {
return Some(end);
}
}
}
None
}
enum AsciiCase {
Match(usize),
NoMatch,
Undecided,
}
#[inline(always)]
fn ascii_case_split(bytes: &[u8], pos: usize, contractions: bool) -> AsciiCase {
let Some(&first) = bytes.get(pos) else {
return AsciiCase::Undecided;
};
if first >= 0x80 {
return AsciiCase::Undecided;
}
let start =
pos + usize::from(!first.is_ascii_alphanumeric() && first != b'\r' && first != b'\n');
let mut i = start;
while i < bytes.len() && bytes[i].is_ascii_uppercase() {
i += 1;
}
while i < bytes.len() && bytes[i].is_ascii_lowercase() {
i += 1;
}
if i == start {
return match bytes.get(start) {
Some(&b) if b < 0x80 => AsciiCase::NoMatch,
None => AsciiCase::NoMatch,
Some(_) => AsciiCase::Undecided,
};
}
if bytes.get(i).is_some_and(|&b| b >= 0x80) {
return AsciiCase::Undecided;
}
AsciiCase::Match(match contractions {
true => i + trailing_contraction(bytes, i),
false => i,
})
}
fn case_split_branch_a(
text: &str,
bytes: &[u8],
start: usize,
upper: Run,
lower: Run,
contractions: bool,
) -> Option<usize> {
let upper_end = scan_run_of(text, bytes, start, upper);
let mut boundary = upper_end;
loop {
if boundary < bytes.len() {
if let Some(n) = (lower.at)(text, bytes, boundary) {
let lower_end = scan_run_of(text, bytes, boundary + n, lower);
return Some(match contractions {
true => lower_end + trailing_contraction(bytes, lower_end),
false => lower_end,
});
}
}
if boundary <= start {
return None;
}
boundary = prev_char_boundary(text, start, boundary);
}
}
fn case_split_branch_b(
text: &str,
bytes: &[u8],
start: usize,
upper: Run,
lower: Run,
contractions: bool,
) -> Option<usize> {
let upper_end = scan_run_of(text, bytes, start, upper);
if upper_end == start {
return None;
}
let lower_end = scan_run_of(text, bytes, upper_end, lower);
Some(match contractions {
true => lower_end + trailing_contraction(bytes, lower_end),
false => lower_end,
})
}
#[inline]
fn upper_run_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
if bytes[pos] < 0x80 {
bytes[pos].is_ascii_uppercase().then_some(1)
} else {
let (c, l) = char_at(text, pos);
is_upper_run_char(c).then_some(l)
}
}
#[inline]
fn lower_run_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
if bytes[pos] < 0x80 {
bytes[pos].is_ascii_lowercase().then_some(1)
} else {
let (c, l) = char_at(text, pos);
is_lower_run_char(c).then_some(l)
}
}
#[inline]
fn trailing_contraction(bytes: &[u8], pos: usize) -> usize {
contraction_len(bytes, pos).unwrap_or(0)
}
#[inline]
fn prev_char_boundary(text: &str, floor: usize, pos: usize) -> usize {
let mut p = pos.saturating_sub(1);
while p > floor && !text.is_char_boundary(p) {
p -= 1;
}
p
}
const HAN_RANGES: [(char, char); 21] = [
('\u{2E80}', '\u{2E99}'),
('\u{2E9B}', '\u{2EF3}'),
('\u{2F00}', '\u{2FD5}'),
('\u{3005}', '\u{3005}'),
('\u{3007}', '\u{3007}'),
('\u{3021}', '\u{3029}'),
('\u{3038}', '\u{303B}'),
('\u{3400}', '\u{4DBF}'),
('\u{4E00}', '\u{9FFF}'),
('\u{F900}', '\u{FA6D}'),
('\u{FA70}', '\u{FAD9}'),
('\u{16FE2}', '\u{16FE3}'),
('\u{16FF0}', '\u{16FF6}'),
('\u{20000}', '\u{2A6DF}'),
('\u{2A700}', '\u{2B81D}'),
('\u{2B820}', '\u{2CEAD}'),
('\u{2CEB0}', '\u{2EBE0}'),
('\u{2EBF0}', '\u{2EE5D}'),
('\u{2F800}', '\u{2FA1D}'),
('\u{30000}', '\u{3134A}'),
('\u{31350}', '\u{33479}'),
];
#[inline]
fn is_han_char(c: char) -> bool {
HAN_RANGES
.binary_search_by(|&(lo, hi)| {
if c < lo {
core::cmp::Ordering::Greater
} else if c > hi {
core::cmp::Ordering::Less
} else {
core::cmp::Ordering::Equal
}
})
.is_ok()
}
#[inline]
fn han_run_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
if bytes[pos] < 0x80 {
return None;
}
let (c, l) = char_at(text, pos);
is_han_char(c).then_some(l)
}
#[inline]
fn kimi_upper_run_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
let n = upper_run_at(text, bytes, pos)?;
let (c, _) = char_at(text, pos);
(!is_han_char(c)).then_some(n)
}
#[inline]
fn kimi_lower_run_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
let n = lower_run_at(text, bytes, pos)?;
let (c, _) = char_at(text, pos);
(!is_han_char(c)).then_some(n)
}
pub(super) fn kimi_spans(text: &str, out: &mut Vec<(usize, usize)>) {
let bytes = text.as_bytes();
let len = bytes.len();
let mut pos = 0usize;
while pos < len {
let start = pos;
if han_run_at(text, bytes, pos).is_some() {
pos = scan_run(text, bytes, pos, han_run_at);
out.push((start, pos));
continue;
}
if let Some(end) =
case_split_branches(
text,
bytes,
pos,
Run {
at: kimi_upper_run_at,
skip_ascii: swar_skip_ascii_upper,
},
Run {
at: kimi_lower_run_at,
skip_ascii: swar_skip_ascii_lower,
},
true,
)
{
pos = end;
out.push((start, pos));
continue;
}
if let Some(n) = number_at(text, bytes, pos) {
pos += n;
for _ in 1..3 {
match (pos < len).then(|| number_at(text, bytes, pos)).flatten() {
Some(n) => pos += n,
None => break,
}
}
out.push((start, pos));
continue;
}
let after_space = if bytes[pos] == b' ' { pos + 1 } else { pos };
if after_space < len && punct_at(text, bytes, after_space).is_some() {
pos = scan_run(text, bytes, after_space, punct_at);
while pos < len && matches!(bytes[pos], b'\r' | b'\n') {
pos += 1;
}
out.push((start, pos));
continue;
}
if space_at(text, bytes, pos).is_some() {
let (s, e) = whitespace_span(text, bytes, pos, WhitespaceOrder::NewlineFirst);
pos = e;
out.push((s, e));
continue;
}
let (_, l) = char_at(text, pos);
pos += l;
}
}
pub(super) fn deepseek_v3_pass3_spans(text: &str, out: &mut Vec<(usize, usize)>) {
let bytes = text.as_bytes();
let len = bytes.len();
let mut pos = 0usize;
while pos < len {
match deepseek_pass3_match(text, bytes, pos, false) {
Some(end) => {
out.push((pos, end));
pos = end;
}
None => pos += char_at(text, pos).1,
}
}
}
#[inline]
fn deepseek_word_match(text: &str, bytes: &[u8], pos: usize, stop_at_cjk: bool) -> Option<usize> {
let run = |at: usize, n: usize| {
if stop_at_cjk {
scan_run_of(text, bytes, at + n, LETTER_OR_MARK_NOT_CJK)
} else {
scan_letter_or_mark_run(text, bytes, at, n)
}
};
match CLASS[bytes[pos] as usize] {
Class::Letter => Some(run(pos, 1)),
Class::Space if bytes[pos] == b' ' && pos + 1 < bytes.len() => {
let next = bytes[pos + 1];
if CLASS[next as usize] == Class::Letter {
Some(run(pos + 1, 1))
} else if next >= 0x80 {
let at = if stop_at_cjk {
letter_or_mark_not_cjk_at(text, bytes, pos + 1)
} else {
letter_or_mark_at(text, bytes, pos + 1)
};
at.map(|n| run(pos + 1, n))
} else {
None
}
}
_ => None,
}
}
#[inline]
fn deepseek_pass3_match(text: &str, bytes: &[u8], pos: usize, stop_at_cjk: bool) -> Option<usize> {
let len = bytes.len();
let run = |text: &str, bytes: &[u8], at: usize, n: usize| {
if stop_at_cjk {
scan_run_of(text, bytes, at + n, LETTER_OR_MARK_NOT_CJK)
} else {
scan_letter_or_mark_run(text, bytes, at, n)
}
};
let letter_at = |text: &str, bytes: &[u8], at: usize| {
if stop_at_cjk {
letter_or_mark_not_cjk_at(text, bytes, at)
} else {
letter_or_mark_at(text, bytes, at)
}
};
if let Some(end) = deepseek_word_match(text, bytes, pos, stop_at_cjk) {
return Some(end);
}
if bytes[pos].is_ascii_graphic()
&& CLASS[bytes[pos] as usize] == Class::Punct
&& pos + 1 < len
&& bytes[pos + 1].is_ascii_alphabetic()
{
return Some(swar_skip_ascii_letters(bytes, pos + 1));
}
if let Some(n) = letter_at(text, bytes, pos) {
return Some(run(text, bytes, pos, n));
}
if let Some(prefix) = deepseek_prefix_len(text, bytes, pos) {
if let Some(n) = (pos + prefix < len)
.then(|| letter_at(text, bytes, pos + prefix))
.flatten()
{
return Some(run(text, bytes, pos + prefix, n));
}
}
let after_space = if bytes[pos] == b' ' { pos + 1 } else { pos };
if after_space < len && punct_or_symbol_at(text, bytes, after_space).is_some() {
let mut end = scan_run(text, bytes, after_space, punct_or_symbol_at);
while end < len && (bytes[end] == b'\r' || bytes[end] == b'\n') {
end += 1;
}
return Some(end);
}
if space_at(text, bytes, pos).is_some() {
if stop_at_cjk {
let run_end = scan_run_of(text, bytes, pos, SPACE_RUN);
if run_end < len
&& !bytes[pos..run_end]
.iter()
.any(|&b| b == b'\r' || b == b'\n')
&& earlier_pass_starts_at(text, bytes, run_end)
{
return Some(run_end);
}
}
let (_, e) = whitespace_span(text, bytes, pos, WhitespaceOrder::NewlineFirst);
return Some(e);
}
None
}
#[inline]
fn earlier_pass_starts_at(text: &str, bytes: &[u8], pos: usize) -> bool {
if bytes[pos] < 0x80 {
return bytes[pos].is_ascii_digit();
}
let (c, _) = char_at(text, pos);
is_deepseek_cjk(c) || is_number_char(c)
}
#[inline]
fn letter_or_mark_not_cjk_at(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
let class = CLASS[bytes[pos] as usize];
if class != Class::Lead {
return (class == Class::Letter).then_some(1);
}
let (c, len) = char_at(text, pos);
if is_deepseek_cjk(c) {
return None;
}
(is_letter_char(c) || is_mark_char(c)).then_some(len)
}
const SPACE_RUN: Run = Run {
at: space_at,
skip_ascii: swar_skip_ascii_space,
};
const LETTER_OR_MARK_NOT_CJK: Run = Run {
at: letter_or_mark_not_cjk_at,
skip_ascii: swar_skip_ascii_letters,
};
pub(crate) fn deepseek_v3_for_each<'p>(text: &'p str, out: &mut dyn FnMut(&'p str)) {
deepseek_v3_walk(text, |s, e| {
if let Some(piece) = text.get(s..e) {
out(piece);
}
});
}
fn deepseek_v3_walk(text: &str, mut emit: impl FnMut(usize, usize)) {
let bytes = text.as_bytes();
let len = bytes.len();
let mut pos = 0usize;
let mut last = 0usize;
while pos < len {
if let Some(end) = deepseek_word_match(text, bytes, pos, true) {
if pos > last {
emit(last, pos);
}
emit(pos, end);
last = end;
pos = end;
continue;
}
if bytes[pos] >= 0x80 {
let (c, l) = char_at(text, pos);
if is_deepseek_cjk(c) {
let start = pos;
pos += l;
while pos < len && bytes[pos] >= 0x80 {
let (c, l) = char_at(text, pos);
if !is_deepseek_cjk(c) {
break;
}
pos += l;
}
if start > last {
emit(last, start);
}
emit(start, pos);
last = pos;
continue;
}
}
if let Some(n) = number_at(text, bytes, pos) {
let start = pos;
pos += n;
for _ in 1..3 {
match (pos < len).then(|| number_at(text, bytes, pos)).flatten() {
Some(n) => pos += n,
None => break,
}
}
if start > last {
emit(last, start);
}
emit(start, pos);
last = pos;
continue;
}
match deepseek_pass3_match(text, bytes, pos, true) {
Some(end) => {
if pos > last {
emit(last, pos);
}
emit(pos, end);
last = end;
pos = end;
}
None => pos += char_at(text, pos).1,
}
}
if last < len {
emit(last, len);
}
}
#[inline]
fn deepseek_prefix_len(text: &str, bytes: &[u8], pos: usize) -> Option<usize> {
if CLASS[bytes[pos] as usize] == Class::Newline {
return None;
}
let (c, l) = if bytes[pos] < 0x80 {
(bytes[pos] as char, 1)
} else {
char_at(text, pos)
};
(!is_letter_char(c) && !is_punct_or_symbol_char(c)).then_some(l)
}
pub(super) fn deepseek_v3_pass1_spans(text: &str, out: &mut Vec<(usize, usize)>) {
let bytes = text.as_bytes();
let len = bytes.len();
let mut pos = 0usize;
while pos < len {
pos = swar_skip_to_number(bytes, pos);
if pos >= len {
break;
}
let Some(n) = number_at(text, bytes, pos) else {
let (_, l) = char_at(text, pos);
pos += l;
continue;
};
let start = pos;
pos += n;
for _ in 1..3 {
match (pos < len).then(|| number_at(text, bytes, pos)).flatten() {
Some(n) => pos += n,
None => break,
}
}
out.push((start, pos));
}
}
pub(super) fn deepseek_v3_pass2_spans(text: &str, out: &mut Vec<(usize, usize)>) {
let bytes = text.as_bytes();
let len = bytes.len();
let mut pos = 0usize;
while pos < len {
pos = swar_skip_ascii(bytes, pos);
if pos >= len {
break;
}
let (c, l) = char_at(text, pos);
if !is_deepseek_cjk(c) {
pos += l;
continue;
}
let start = pos;
pos += l;
while pos < len && bytes[pos] >= 0x80 {
let (c, l) = char_at(text, pos);
if !is_deepseek_cjk(c) {
break;
}
pos += l;
}
out.push((start, pos));
}
}
#[inline]
fn is_deepseek_cjk(c: char) -> bool {
matches!(c, '\u{4E00}'..='\u{9FA5}' | '\u{3040}'..='\u{30FF}')
}
pub(crate) type SpanScanner = fn(&str, &mut Vec<(usize, usize)>);
pub(crate) type PieceScanner = for<'p> fn(&'p str, &mut dyn FnMut(&'p str));
pub(crate) fn streaming_for_pattern(pattern: &str) -> Option<PieceScanner> {
match pattern == crate::core::tokenizer::patterns::GPT2_PATTERN {
true => Some(gpt2_for_each),
false => None,
}
}
#[inline]
fn gpt2_contraction_len(bytes: &[u8], pos: usize) -> Option<usize> {
if bytes.get(pos) != Some(&b'\'') {
return None;
}
match *bytes.get(pos + 1)? {
b's' | b'd' | b'm' | b't' => Some(2),
b'l' if bytes.get(pos + 2) == Some(&b'l') => Some(3),
b'v' | b'r' if bytes.get(pos + 2) == Some(&b'e') => Some(3),
_ => None,
}
}
pub(super) fn gpt2_spans(text: &str, out: &mut Vec<(usize, usize)>) {
gpt2_walk(text, |start, end| out.push((start, end)));
}
pub(crate) fn gpt2_for_each<'p>(text: &'p str, out: &mut dyn FnMut(&'p str)) {
let mut last = 0usize;
gpt2_walk(text, |start, end| {
if start > last {
if let Some(gap) = text.get(last..start) {
out(gap);
}
}
if end > start {
if let Some(piece) = text.get(start..end) {
out(piece);
}
}
last = end;
});
if last < text.len() {
if let Some(gap) = text.get(last..) {
out(gap);
}
}
}
fn gpt2_walk(text: &str, mut emit: impl FnMut(usize, usize)) {
let bytes = text.as_bytes();
let len = bytes.len();
let mut pos = 0usize;
while pos < len {
let start = pos;
if let Some(n) = gpt2_contraction_len(bytes, pos) {
pos += n;
emit(start, pos);
continue;
}
let after_space = match bytes[pos] {
b' ' => pos + 1,
_ => pos,
};
if after_space < len {
match CLASS[bytes[after_space] as usize] {
Class::Letter => {
pos = scan_letters(text, bytes, after_space + 1);
emit(start, pos);
continue;
}
Class::Digit => {
pos = scan_run(text, bytes, after_space + 1, number_at);
emit(start, pos);
continue;
}
Class::Punct => {
pos = scan_run(text, bytes, after_space + 1, punct_at);
emit(start, pos);
continue;
}
Class::Space | Class::Newline => {}
Class::Lead => {
let (c, l) = char_at(text, after_space);
if is_letter_char(c) {
pos = scan_letters(text, bytes, after_space + l);
emit(start, pos);
continue;
}
if is_number_char(c) {
pos = scan_run(text, bytes, after_space + l, number_at);
emit(start, pos);
continue;
}
if !c.is_whitespace() {
pos = scan_run(text, bytes, after_space + l, punct_at);
emit(start, pos);
continue;
}
}
}
}
if space_at(text, bytes, pos).is_some() {
let (s, e) = whitespace_span(text, bytes, pos, WhitespaceOrder::Plain);
pos = e;
emit(s, e);
continue;
}
let (_, l) = char_at(text, pos);
pos += l;
}
}
pub(crate) fn for_pattern(pattern: &str) -> Option<SpanScanner> {
use crate::core::tokenizer::patterns as p;
if pattern == p::CL100K_BASE_PATTERN {
Some(cl100k_spans)
} else if pattern == p::LLAMA3_PATTERN {
Some(llama3_spans)
} else if pattern == p::QWEN2_PATTERN {
Some(qwen2_spans)
} else if pattern == p::O200K_BASE_PATTERN {
Some(o200k_spans)
} else if pattern == p::MISTRAL_V3_PATTERN {
Some(mistral_v3_spans)
} else if pattern == p::KIMI_PATTERN {
Some(kimi_spans)
} else if pattern == p::GPT2_PATTERN {
Some(gpt2_spans)
} else if pattern == p::DEEPSEEK_V3_PATTERNS[0] {
Some(deepseek_v3_pass1_spans)
} else if pattern == p::DEEPSEEK_V3_PATTERNS[1] || pattern == p::DEEPSEEK_V3_PASS2_LITERAL {
Some(deepseek_v3_pass2_spans)
} else if pattern == p::DEEPSEEK_V3_PATTERNS[2] {
Some(deepseek_v3_pass3_spans)
} else {
None
}
}
#[cfg(test)]
mod fused_tests {
use super::*;
fn isolate(text: &str, scan: fn(&str, &mut Vec<(usize, usize)>)) -> Vec<(usize, usize)> {
let mut matches = Vec::new();
scan(text, &mut matches);
let mut out = Vec::new();
let mut last = 0;
for &(s, e) in &matches {
if s > last {
out.push((last, s));
}
if e > s {
out.push((s, e));
}
last = e;
}
if last < text.len() {
out.push((last, text.len()));
}
out
}
fn staged(text: &str) -> Vec<String> {
let mut level = vec![text.to_string()];
for scan in [
deepseek_v3_pass1_spans as fn(&str, &mut Vec<(usize, usize)>),
deepseek_v3_pass2_spans,
deepseek_v3_pass3_spans,
] {
let mut next = Vec::new();
for piece in &level {
for (s, e) in isolate(piece, scan) {
next.push(piece[s..e].to_string());
}
}
level = next;
}
level
}
fn fused(text: &str) -> Vec<String> {
let mut pieces = Vec::new();
deepseek_v3_for_each(text, &mut |piece| pieces.push(piece.to_string()));
pieces
}
#[test]
fn the_fused_walk_matches_the_three_passes() {
let cases = [
"",
"hello world",
" the quick brown fox",
"abc123def",
"12345",
"1 2 3",
"a1b2c3",
"中文字",
"abc中def",
"中123文",
"ひらがなカタカナ",
" spaced out ",
"line\nbreak\r\nhere",
"!!!shout",
"a.b,c;d",
" ?!@#",
"café naïve",
"Привет мир",
"ελληνικά",
"ไทยไม่มีช่องว่าง",
"\u{0}control\u{7f}",
"mixed 中文 and 123 and abc!",
"\u{200d}zwj\u{200c}",
"emoji 🎉 here",
"tab\tsep",
"trailing ",
" leading",
"1234567890",
"a\u{301}combining",
"FULLWIDTH",
" 0",
" 中",
"\u{a0} 中",
"\u{3000}\t5b",
"a \u{a0}\u{a0}文0",
"x \r\n 1",
" \u{a0}\u{3000} ひ",
];
for text in cases {
assert_eq!(fused(text), staged(text), "fused walk diverged on {text:?}");
}
}
#[test]
fn cjk_ranges_hold_no_digits() {
for codepoint in (0x4E00..=0x9FA5).chain(0x3040..=0x30FF) {
let Some(c) = char::from_u32(codepoint) else {
continue;
};
assert!(
is_deepseek_cjk(c) && !is_number_char(c),
"U+{codepoint:04X} is claimed by both passes, so their order matters"
);
}
}
#[test]
fn the_fused_walk_matches_the_three_passes_on_generated_text() {
use proptest::prelude::*;
let alphabet: Vec<char> =
"ab_Zé中文ひカ0159 \t\n\r\u{a0}\u{3000}.,!?#$@'\u{200d}\u{301}\u{0}\u{7f}\u{ad}αДไ🎉²"
.chars()
.collect();
let mut runner = proptest::test_runner::TestRunner::deterministic();
let strategy = proptest::collection::vec(0usize..alphabet.len(), 0..48);
runner
.run(&strategy, |picks| {
let text: String = picks.iter().map(|&i| alphabet[i]).collect();
prop_assert_eq!(fused(&text), staged(&text), "diverged on {:?}", text);
Ok(())
})
.expect("fused walk must equal the three passes on every generated string");
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::tokenizer::patterns::{
CL100K_BASE_PATTERN, DEEPSEEK_V3_PATTERNS, GPT2_PATTERN, KIMI_PATTERN, LLAMA3_PATTERN,
MISTRAL_V3_PATTERN, O200K_BASE_PATTERN, QWEN2_PATTERN,
};
type Scanner = fn(&str, &mut Vec<(usize, usize)>);
fn all_scanners() -> Vec<(&'static str, &'static str, Scanner)> {
vec![
("cl100k", CL100K_BASE_PATTERN, cl100k_spans as Scanner),
("llama3", LLAMA3_PATTERN, llama3_spans as Scanner),
("qwen2", QWEN2_PATTERN, qwen2_spans as Scanner),
("o200k", O200K_BASE_PATTERN, o200k_spans as Scanner),
("kimi", KIMI_PATTERN, kimi_spans as Scanner),
(
"mistral-v3",
MISTRAL_V3_PATTERN,
mistral_v3_spans as Scanner,
),
("gpt2", GPT2_PATTERN, gpt2_spans as Scanner),
(
"deepseek-pass1",
DEEPSEEK_V3_PATTERNS[0],
deepseek_v3_pass1_spans as Scanner,
),
(
"deepseek-pass2",
DEEPSEEK_V3_PATTERNS[1],
deepseek_v3_pass2_spans as Scanner,
),
(
"deepseek-pass3",
DEEPSEEK_V3_PATTERNS[2],
deepseek_v3_pass3_spans as Scanner,
),
]
}
#[test]
fn both_spellings_of_the_deepseek_cjk_pass_agree() {
use crate::core::tokenizer::patterns::DEEPSEEK_V3_PASS2_LITERAL;
assert!(
for_pattern(DEEPSEEK_V3_PASS2_LITERAL).is_some(),
"the spelling `tokenizer.json` uses must reach the scanner"
);
let escaped = regexr::RegexBuilder::new(DEEPSEEK_V3_PATTERNS[1])
.jit(true)
.build()
.expect("escaped spelling compiles");
let literal = regexr::RegexBuilder::new(DEEPSEEK_V3_PASS2_LITERAL)
.jit(true)
.build()
.expect("literal spelling compiles");
for text in [
"abc",
"中文字",
"ひらがな",
"カタカナ",
"a中b",
"\u{4DFF}\u{4E00}\u{9FA5}\u{9FA6}",
"\u{303F}\u{3040}\u{309F}\u{30A0}\u{30FF}\u{3100}",
"1 2 3",
"",
] {
let want: Vec<(usize, usize)> = escaped
.find_iter(text)
.map(|m| (m.start(), m.end()))
.collect();
let got: Vec<(usize, usize)> = literal
.find_iter(text)
.map(|m| (m.start(), m.end()))
.collect();
assert_eq!(want, got, "spellings disagree on {text:?}");
let mut scanned = Vec::new();
deepseek_v3_pass2_spans(text, &mut scanned);
assert_eq!(want, scanned, "scanner disagrees on {text:?}");
}
}
#[test]
fn every_bundled_vocabulary_that_should_have_a_scanner_has_one() {
use crate::core::pretrained::{patterns, PretrainedVocab::*};
let no_scanner: [crate::core::pretrained::PretrainedVocab; 0] = [];
for vocab in [
Cl100kBase, O200kBase, GptOss, Llama3, DeepseekV3, Qwen3, Glm4, KimiK2, KimiK3,
MistralV1, MistralV2, MistralV3, WhisperV1, WhisperV2, WhisperV3,
] {
let Some(pats) = patterns(vocab) else {
continue;
};
let expected = !no_scanner.contains(&vocab);
for pattern in pats {
assert_eq!(
for_pattern(pattern).is_some(),
expected,
"{vocab:?} scanner coverage changed for {pattern}"
);
}
}
}
#[test]
fn the_bulk_whitespace_skip_accepts_what_the_class_table_does() {
for byte in 0..=0x7Fu8 {
let text = String::from_utf8(vec![byte; 3]).unwrap_or_default();
if text.is_empty() {
continue;
}
let bytes = text.as_bytes();
let bulk = swar_skip_ascii_space(bytes, 0);
let want = match matches!(CLASS[byte as usize], Class::Space | Class::Newline) {
true => bytes.len(),
false => 0,
};
assert_eq!(bulk, want, "byte {byte:#04X} skipped differently");
}
}
#[test]
fn han_table_matches_the_regex_over_every_scalar_value() {
let re = regexr::Regex::new(r"^\p{Han}$").expect("regexr knows \\p{Han}");
let mut buf = [0u8; 4];
let mut disagreements = 0usize;
let mut first: Option<char> = None;
for cp in 0u32..=0x10FFFF {
let Some(c) = char::from_u32(cp) else {
continue;
};
if re.is_match(c.encode_utf8(&mut buf)) != is_han_char(c) {
disagreements += 1;
first.get_or_insert(c);
}
}
assert_eq!(
disagreements,
0,
"HAN_RANGES disagrees with the engine on {disagreements} code points, \
first U+{:04X} — regenerate the table",
first.map(u32::from).unwrap_or(0)
);
}
#[test]
fn kimi_scanner_agrees_with_its_regex_on_han_boundaries() {
for input in [
"中文English混合",
"汉字abc",
"北京市 Pascal",
" 中文",
"中文 ",
"中文123",
"123中文",
"中\u{0301}文",
"あ中ア文",
"\u{3005}\u{3006}\u{3007}",
"\u{9FFF}\u{A000}",
"\u{20000}\u{20001}x",
"XMLHttp中文Request",
"中文'sX",
"中!文",
"中\n文",
] {
assert_agrees("kimi", KIMI_PATTERN, kimi_spans as Scanner, input);
}
}
fn assert_agrees(name: &str, pattern: &str, scan: Scanner, input: &str) {
let re = regexr::RegexBuilder::new(pattern)
.jit(true)
.build()
.expect("pattern compiles");
let expected: Vec<(usize, usize)> =
re.find_iter(input).map(|m| (m.start(), m.end())).collect();
let mut got = Vec::new();
scan(input, &mut got);
assert_eq!(
got,
expected,
"{name} scanner disagrees with its regex on {input:?}\n scanner: {:?}\n regex: {:?}",
got.iter().map(|&(s, e)| &input[s..e]).collect::<Vec<_>>(),
expected
.iter()
.map(|&(s, e)| &input[s..e])
.collect::<Vec<_>>()
);
}
const SHAPED: &[&str] = &[
"",
" ",
" ",
" ",
"\n",
"\n\n",
" \n",
" \n ",
"\t\n\t",
"hello world",
"hello[]!?",
"[]!?",
"The[]getUserName",
"[]get",
"hello[]",
"a{}b",
"Zürich{}",
" hello",
"!hello",
"hello!",
"don't stop",
"DON'T STOP",
"it's o'clock 'tis",
"'s 'S 'll 'LL 've 're 'd 'm 't",
"'x 'zz '",
"123",
"1234",
"12345678",
" 123",
"abc123def",
"a1b2c3",
"!!!",
" !!!",
"!!!\n\n",
" ...\r\n",
"( )",
"{\"key\": \"value\", \"n\": 42}",
"def f(x):\n return x + 1\n",
"trailing ",
"trailing\n",
"trailing \n ",
" leading",
"a b",
"a b",
"a \n b",
"中文测试",
" 中文",
"中文 abc",
"café naïve",
"emoji 🚀 here",
"🚀🚀",
" 🚀",
"Ⅷ Ⅸ",
"½ ¾",
"\u{3000}abc",
"a\u{00a0}b",
"x\u{2028}y",
"\r\n\r\n",
"a\r\nb",
" \r\n ",
"word\u{0301} mark",
"XMLHttpRequest",
"camelCase",
"PascalCase",
"ALLCAPS",
"ALLCAPSthenLower",
"aB",
"Ab",
"A",
"AB",
"iPhone",
"McDonald's",
"HTTPServer's",
"ÉCOLE école",
"path/to/file",
"a//b",
"!/",
" //\n",
"1",
"12",
"1234567",
"abc 1234567 def",
"a1234567890b",
"\u{4e00}\u{9fa5}",
"\u{4dff}\u{4e00}",
"\u{9fa5}\u{9fa6}",
"\u{303f}\u{3040}",
"\u{30ff}\u{3100}",
"\u{3040}\u{309f}\u{30a0}\u{30ff}",
"ひらがな カタカナ 漢字",
"long ascii prefix before 漢字 appears",
"漢字123ひらがな",
"abcdefg",
"abcdefgh",
"abcdefghi",
"abcdefghijklmnopqrstuvwxyz",
"abcdefghijklmnopqrstuvwxyz0",
"Supercalifragilisticexpialidocious and more words here",
"aaaaaaaa\u{4e2d}",
"aaaaaaaaaaaaaaaa\u{4e2d}bbbbbbbb",
];
#[test]
fn scanners_agree_with_their_regex_on_shaped_inputs() {
for (name, pattern, scan) in all_scanners() {
for input in SHAPED {
assert_agrees(name, pattern, scan, input);
}
}
}
#[test]
fn scanners_agree_with_their_regex_on_case_split_shapes() {
const CASES: &[&str] = &[
"XMLHttpRequest",
" XMLHttpRequest",
"ABC",
" ABC",
"ABC def",
"aB",
"Ab",
"A",
" A",
"(the",
"(The",
"(ABC",
"\r\nthe",
"\nThe",
"don't",
"DON'T",
"Don's",
"ABC'S",
"it's Sam's",
"caFé",
"ABCé",
"Ünicode",
"aÜb",
"abcé def",
"ABC\u{0301}",
"the\u{00a0}end",
"A\u{4e2d}B",
"\u{00a0}the",
"\u{2028}The",
];
for (name, pattern, scan) in all_scanners() {
for input in CASES {
assert_agrees(name, pattern, scan, input);
}
}
}
#[test]
fn scanners_agree_with_their_regex_on_long_inputs() {
let filler = "The quick brown fox jumps over the lazy dog. ";
let cases: Vec<String> = vec![
filler.repeat(400),
format!("{}\n{}", filler.repeat(200), filler.repeat(200)),
format!("{} \n \n {}", filler.repeat(200), filler.repeat(200)),
format!(
"{}\n/path/to/file {}",
filler.repeat(200),
filler.repeat(200)
),
format!("{}\n中文测试 {}", filler.repeat(200), filler.repeat(200)),
format!("{}\n\n\nx{}", filler.repeat(200), filler.repeat(200)),
format!("{}\n漢字ひらがな{}", filler.repeat(200), "漢字".repeat(600)),
format!("{}\n1234567890 {}", filler.repeat(200), "42 ".repeat(600)),
format!("{}\nx{} ", filler.repeat(200), filler.repeat(200)),
];
for (name, pattern, scan) in all_scanners() {
for input in &cases {
assert_agrees(name, pattern, scan, input);
}
}
}
#[test]
fn scanners_agree_at_every_run_boundary_in_a_swar_word() {
let cl100k = regexr::RegexBuilder::new(CL100K_BASE_PATTERN)
.jit(true)
.build()
.expect("cl100k pattern compiles");
for run in 0..20usize {
for byte in 0u8..=127 {
let c = byte as char;
if c.is_control() && c != '\n' && c != '\r' && c != '\t' {
continue;
}
let input = format!("{}{}{}", "a".repeat(run), c, "b".repeat(3));
let expected: Vec<(usize, usize)> = cl100k
.find_iter(&input)
.map(|m| (m.start(), m.end()))
.collect();
let mut got = Vec::new();
cl100k_spans(&input, &mut got);
assert_eq!(
got,
expected,
"run={run} byte={byte:#04x} input={input:?}\n scanner: {:?}",
got.iter().map(|&(s, e)| &input[s..e]).collect::<Vec<_>>()
);
}
}
}
#[test]
fn scanners_agree_with_their_regex_on_random_inputs() {
const ALPHABET: &[&str] = &[
"a",
"z",
"A",
"Z",
"1",
"9",
" ",
" ",
"\t",
"\n",
"\r",
"\r\n",
"'",
"!",
".",
",",
"-",
"_",
"/",
"中",
"é",
"É",
"🚀",
"\u{00a0}",
"\u{3000}",
"Ⅷ",
"½",
"\u{0301}",
"«",
"€",
"\u{05d0}",
"\u{2E99}",
"\u{2E9A}",
"\u{3005}",
"\u{3006}",
"\u{3007}",
"\u{9FFF}",
"\u{A000}",
"\u{F900}",
"\u{20000}",
"\u{3042}",
"\u{30A2}",
];
let mut state = 0x2545_F491_4F6C_DD1Du64;
let mut next = move || {
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
state.wrapping_mul(0x2545_F491_4F6C_DD1D)
};
let scanners = all_scanners();
for _ in 0..3000 {
let pieces = 1 + (next() % 14) as usize;
let mut input = String::new();
for _ in 0..pieces {
input.push_str(ALPHABET[(next() % ALPHABET.len() as u64) as usize]);
}
for &(name, pattern, scan) in &scanners {
assert_agrees(name, pattern, scan, &input);
}
}
}
#[test]
fn the_ideograph_lead_range_is_uniformly_other_letter() {
for lead in IDEOGRAPH_LEAD {
for (lo, hi) in [(0x80u32, 0x80u32), (0xBF, 0xBF)] {
let cp = ((lead as u32 & 0x0F) << 12) | ((lo & 0x3F) << 6) | (hi & 0x3F);
let c = char::from_u32(cp).expect("valid scalar");
assert_eq!(
get_general_category(c),
GeneralCategory::OtherLetter,
"U+{cp:04X} (lead {lead:#04X}) is not OtherLetter"
);
}
}
}
#[test]
fn every_code_point_the_lead_range_encodes_is_other_letter() {
for cp in 0x5000u32..=0x9FFF {
let c = char::from_u32(cp).expect("valid scalar");
assert_eq!(
get_general_category(c),
GeneralCategory::OtherLetter,
"U+{cp:04X} is not OtherLetter"
);
}
}
#[test]
fn ideographs_are_skipped_in_bulk() {
let text = "漢字漢字a";
assert_eq!(skip_ideographs(text.as_bytes(), 0), 12, "four ideographs");
assert_eq!(skip_ideographs(text.as_bytes(), 12), 12, "stops at ASCII");
assert_eq!(skip_ideographs(b"", 0), 0);
assert_eq!(skip_ideographs("ひらがな".as_bytes(), 0), 0);
}
#[test]
fn only_the_runs_containing_other_letter_take_the_skip() {
let han = "漢字漢字";
let upper = Run {
at: upper_run_at,
skip_ascii: skip_upper_or_ideograph,
};
assert_eq!(scan_run_of(han, han.as_bytes(), 0, upper), han.len());
let kimi_upper = Run {
at: kimi_upper_run_at,
skip_ascii: swar_skip_ascii_upper,
};
assert_eq!(
scan_run_of(han, han.as_bytes(), 0, kimi_upper),
0,
"Kimi excludes Han from its case-split classes"
);
}
}