pub(super) const OTHER: u8 = 0;
pub(super) const CR: u8 = 1;
pub(super) const LF: u8 = 2;
pub(super) const NEWLINE: u8 = 3;
pub(super) const EXTEND: u8 = 4;
pub(super) const ZWJ: u8 = 5;
pub(super) const REGIONAL_INDICATOR: u8 = 6;
pub(super) const FORMAT: u8 = 7;
pub(super) const KATAKANA: u8 = 8;
pub(super) const HEBREW_LETTER: u8 = 9;
pub(super) const LETTER: u8 = 10;
pub(super) const SINGLE_QUOTE: u8 = 11;
pub(super) const DOUBLE_QUOTE: u8 = 12;
pub(super) const MID_NUM_LETTER: u8 = 13;
pub(super) const MID_LETTER: u8 = 14;
pub(super) const MID_NUMBER: u8 = 15;
pub(super) const NUMERIC: u8 = 16;
pub(super) const EXTEND_NUM_LETTER: u8 = 17;
pub(super) const SEGMENT_SPACE: u8 = 18;
pub(super) const FULL_STOP: u8 = 19;
pub(super) const CHINESE_JAPANESE_LETTER: u8 = 20;
pub(super) const CLASSES: usize = 21;
const LETTER_MID: u8 = 21;
const NUMBER_MID: u8 = 22;
const HEBREW_QUOTE: u8 = 23;
const INDICATOR_PAIR: u8 = 24;
pub(super) const START: u8 = 25;
const STATES: usize = 26;
pub(super) const KEEP: u8 = 0;
pub(super) const BREAK: u8 = 1;
pub(super) const NEEDS_LETTER: u8 = 2;
pub(super) const NEEDS_HEBREW: u8 = 3;
pub(super) const NEEDS_NUMBER: u8 = 4;
const ANSWER_MASK: u8 = 0x7;
const NEXT_SHIFT: u32 = 3;
const fn is_letter(class: u8) -> bool {
matches!(class, LETTER | HEBREW_LETTER)
}
const fn is_mid_letter(class: u8) -> bool {
matches!(class, MID_LETTER | MID_NUM_LETTER | SINGLE_QUOTE)
}
const fn is_mid_number(class: u8) -> bool {
matches!(class, MID_NUMBER | MID_NUM_LETTER | SINGLE_QUOTE)
}
const fn extends(class: u8) -> bool {
is_letter(class) || class == NUMERIC || class == KATAKANA
}
pub(super) const fn is_attached(class: u8) -> bool {
matches!(class, EXTEND | FORMAT | ZWJ)
}
const fn is_newline(class: u8) -> bool {
matches!(class, CR | LF | NEWLINE)
}
const fn decide(left: u8, right: u8) -> u8 {
if left == CR && right == LF {
return KEEP;
}
if is_newline(left) || is_newline(right) {
return BREAK;
}
if left == SEGMENT_SPACE && right == SEGMENT_SPACE {
return KEEP;
}
if is_attached(right) {
return KEEP;
}
if is_letter(left) {
if is_letter(right) {
return KEEP;
}
if left == HEBREW_LETTER && right == SINGLE_QUOTE {
return KEEP;
}
if is_mid_letter(right) {
return NEEDS_LETTER;
}
if left == HEBREW_LETTER && right == DOUBLE_QUOTE {
return NEEDS_HEBREW;
}
}
if (left == LETTER_MID && is_letter(right))
|| (left == HEBREW_QUOTE && right == HEBREW_LETTER)
|| (left == NUMBER_MID && right == NUMERIC)
{
return KEEP;
}
if (left == NUMERIC || is_letter(left)) && (right == NUMERIC || is_letter(right)) {
return KEEP;
}
if left == NUMERIC && is_mid_number(right) {
return NEEDS_NUMBER;
}
if left == KATAKANA && right == KATAKANA {
return KEEP;
}
if (extends(left) || left == EXTEND_NUM_LETTER) && right == EXTEND_NUM_LETTER {
return KEEP;
}
if left == EXTEND_NUM_LETTER && extends(right) {
return KEEP;
}
if left == REGIONAL_INDICATOR && right == REGIONAL_INDICATOR {
return KEEP;
}
BREAK
}
const fn next(left: u8, right: u8) -> u8 {
if is_attached(right) {
return if is_newline(left) || left == START {
right
} else if left == SEGMENT_SPACE {
OTHER
} else {
left
};
}
if is_letter(left) && is_mid_letter(right) {
LETTER_MID
} else if left == NUMERIC && is_mid_number(right) {
NUMBER_MID
} else if left == HEBREW_LETTER && right == DOUBLE_QUOTE {
HEBREW_QUOTE
} else if left == REGIONAL_INDICATOR && right == REGIONAL_INDICATOR {
INDICATOR_PAIR
} else {
right
}
}
static PAIRS: [[u8; CLASSES]; STATES] = {
let mut table = [[0; CLASSES]; STATES];
let mut left = 0;
while left < STATES {
let mut right = 0;
while right < CLASSES {
let answer = if left as u8 == START {
BREAK
} else {
decide(left as u8, right as u8)
};
table[left][right] = answer | next(left as u8, right as u8) << NEXT_SHIFT;
right += 1;
}
left += 1;
}
table
};
#[inline(always)]
pub(super) fn pair(left: u8, right: u8) -> (u8, u8) {
let entry = PAIRS
.get(usize::from(left))
.and_then(|row| row.get(usize::from(right)))
.copied()
.unwrap_or(BREAK | OTHER << NEXT_SHIFT);
(entry & ANSWER_MASK, entry >> NEXT_SHIFT)
}
#[inline]
pub(super) const fn holds(waits: u8, next: u8) -> bool {
match waits {
NEEDS_LETTER => is_letter(next),
NEEDS_HEBREW => next == HEBREW_LETTER,
NEEDS_NUMBER => next == NUMERIC,
_ => false,
}
}