#![allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BidiClass {
L,
R,
AL,
EN,
ES,
ET,
AN,
CS,
NSM,
BN,
B,
S,
WS,
ON,
LRE,
LRO,
RLE,
RLO,
PDF,
LRI,
RLI,
FSI,
PDI,
}
impl BidiClass {
#[must_use]
pub const fn is_strong(self) -> bool {
matches!(self, Self::L | Self::R | Self::AL)
}
#[must_use]
pub const fn is_isolate_initiator(self) -> bool {
matches!(self, Self::LRI | Self::RLI | Self::FSI)
}
#[must_use]
pub const fn is_neutral_or_isolate(self) -> bool {
matches!(
self,
Self::B | Self::S | Self::WS | Self::ON | Self::FSI | Self::LRI | Self::RLI | Self::PDI
)
}
}
#[must_use]
pub fn bidi_class(c: char) -> BidiClass {
let u = c as u32;
match u {
0x061C => return BidiClass::AL, 0x200E => return BidiClass::L, 0x200F => return BidiClass::R, 0x202A => return BidiClass::LRE,
0x202B => return BidiClass::RLE,
0x202C => return BidiClass::PDF,
0x202D => return BidiClass::LRO,
0x202E => return BidiClass::RLO,
0x2066 => return BidiClass::LRI,
0x2067 => return BidiClass::RLI,
0x2068 => return BidiClass::FSI,
0x2069 => return BidiClass::PDI,
_ => {}
}
match u {
0x000A | 0x000D | 0x0085 | 0x001C..=0x001E | 0x2029 => return BidiClass::B,
0x0009 | 0x000B | 0x001F => return BidiClass::S,
0x000C | 0x0020 | 0x1680 | 0x2028 | 0x202F | 0x205F | 0x3000 => return BidiClass::WS,
_ => {}
}
if (0x2000..=0x200A).contains(&u) {
return BidiClass::WS;
}
match u {
0x0030..=0x0039 => return BidiClass::EN,
0x002B | 0x002D => return BidiClass::ES,
0x002C | 0x002E | 0x002F | 0x003A => return BidiClass::CS,
0x0023..=0x0025 => return BidiClass::ET,
_ => {}
}
if matches!(u, 0x0041..=0x005A | 0x0061..=0x007A) {
return BidiClass::L;
}
if matches!(u, 0x0000..=0x0008 | 0x000E..=0x001B | 0x007F..=0x0084 | 0x0086..=0x009F) {
return BidiClass::BN;
}
match u {
0x00A0 => return BidiClass::CS, 0x00A2..=0x00A5 => return BidiClass::ET, 0x00B0 | 0x00B1 => return BidiClass::ET, 0x00AD => return BidiClass::BN, _ => {}
}
if matches!(u, 0x00C0..=0x00D6 | 0x00D8..=0x00F6 | 0x00F8..=0x00FF) {
return BidiClass::L;
}
if (0x0300..=0x036F).contains(&u) {
return BidiClass::NSM;
}
if matches!(
u,
0x0610..=0x061A
| 0x064B..=0x065F
| 0x0670
| 0x06D6..=0x06DC
| 0x06DF..=0x06E4
| 0x06E7..=0x06E8
| 0x06EA..=0x06ED
) {
return BidiClass::NSM;
}
if (0x0590..=0x05FF).contains(&u) {
return BidiClass::R;
}
if (0x0FB1D..=0x0FB4F).contains(&u) {
return BidiClass::R;
}
if (0x0600..=0x06FF).contains(&u) {
if (0x0660..=0x0669).contains(&u) {
return BidiClass::AN;
}
if (0x06F0..=0x06F9).contains(&u) {
return BidiClass::EN;
}
return BidiClass::AL;
}
if (0x0700..=0x074F).contains(&u) {
return BidiClass::AL;
}
if (0x0750..=0x077F).contains(&u) {
return BidiClass::AL;
}
if (0x0780..=0x07BF).contains(&u) {
return BidiClass::AL;
}
if (0x07C0..=0x07FF).contains(&u) {
return BidiClass::R;
}
if (0xFB50..=0xFDFF).contains(&u) {
return BidiClass::AL;
}
if (0xFE70..=0xFEFF).contains(&u) {
return BidiClass::AL;
}
if matches!(u, 0x200B..=0x200D | 0x2060..=0x2064) {
return BidiClass::BN;
}
if u == 0xFFFC {
return BidiClass::ON;
}
BidiClass::L
}
#[must_use]
pub fn paragraph_level(text: &str) -> u8 {
let mut isolate_depth: u32 = 0;
for c in text.chars() {
let class = bidi_class(c);
if isolate_depth > 0 {
match class {
BidiClass::LRI | BidiClass::RLI | BidiClass::FSI => {
isolate_depth = isolate_depth.saturating_add(1);
}
BidiClass::PDI => {
isolate_depth -= 1;
}
_ => {}
}
continue;
}
match class {
BidiClass::L => return 0,
BidiClass::R | BidiClass::AL => return 1,
BidiClass::LRI | BidiClass::RLI | BidiClass::FSI => {
isolate_depth = 1;
}
_ => {}
}
}
0
}
pub fn resolve_weak_types(classes: &mut [BidiClass], sos: BidiClass, eos: BidiClass) {
let _ = eos; if classes.is_empty() {
return;
}
for i in 0..classes.len() {
if classes[i] != BidiClass::NSM {
continue;
}
let prev = if i == 0 { sos } else { classes[i - 1] };
classes[i] = match prev {
BidiClass::LRI | BidiClass::RLI | BidiClass::FSI | BidiClass::PDI => BidiClass::ON,
other => other,
};
}
{
let mut last_strong = if sos.is_strong() { sos } else { BidiClass::L };
for cls in classes.iter_mut() {
match *cls {
BidiClass::L | BidiClass::R | BidiClass::AL => last_strong = *cls,
BidiClass::EN if last_strong == BidiClass::AL => {
*cls = BidiClass::AN;
}
_ => {}
}
}
}
for cls in classes.iter_mut() {
if *cls == BidiClass::AL {
*cls = BidiClass::R;
}
}
if classes.len() >= 3 {
for i in 1..classes.len() - 1 {
let cur = classes[i];
let prev = classes[i - 1];
let next = classes[i + 1];
match cur {
BidiClass::ES if prev == BidiClass::EN && next == BidiClass::EN => {
classes[i] = BidiClass::EN;
}
BidiClass::CS if prev == BidiClass::EN && next == BidiClass::EN => {
classes[i] = BidiClass::EN;
}
BidiClass::CS if prev == BidiClass::AN && next == BidiClass::AN => {
classes[i] = BidiClass::AN;
}
_ => {}
}
}
}
{
let n = classes.len();
let mut i = 0;
while i < n {
if classes[i] != BidiClass::ET {
i += 1;
continue;
}
let start = i;
while i < n && classes[i] == BidiClass::ET {
i += 1;
}
let end = i; let left_en = start > 0 && classes[start - 1] == BidiClass::EN;
let right_en = end < n && classes[end] == BidiClass::EN;
if left_en || right_en {
for cls in &mut classes[start..end] {
*cls = BidiClass::EN;
}
}
}
}
for cls in classes.iter_mut() {
if matches!(*cls, BidiClass::ES | BidiClass::ET | BidiClass::CS) {
*cls = BidiClass::ON;
}
}
{
let mut last_strong = if matches!(sos, BidiClass::L | BidiClass::R) {
sos
} else {
BidiClass::L
};
for cls in classes.iter_mut() {
match *cls {
BidiClass::L | BidiClass::R => last_strong = *cls,
BidiClass::EN if last_strong == BidiClass::L => {
*cls = BidiClass::L;
}
_ => {}
}
}
}
}
pub fn resolve_neutral_types(
classes: &mut [BidiClass],
embedding_level: u8,
sos: BidiClass,
eos: BidiClass,
) {
if classes.is_empty() {
return;
}
fn strong_dir(c: BidiClass) -> Option<BidiClass> {
match c {
BidiClass::L => Some(BidiClass::L),
BidiClass::R | BidiClass::EN | BidiClass::AN => Some(BidiClass::R),
_ => None,
}
}
let sos_dir = strong_dir(sos).unwrap_or(BidiClass::L);
let eos_dir = strong_dir(eos).unwrap_or(BidiClass::L);
let embedding_dir = if embedding_level % 2 == 0 {
BidiClass::L
} else {
BidiClass::R
};
let n = classes.len();
let mut i = 0;
while i < n {
if !classes[i].is_neutral_or_isolate() {
i += 1;
continue;
}
let start = i;
while i < n && classes[i].is_neutral_or_isolate() {
i += 1;
}
let end = i;
let mut left = sos_dir;
if start > 0 {
let mut k = start;
while k > 0 {
k -= 1;
if let Some(d) = strong_dir(classes[k]) {
left = d;
break;
}
if k == 0 {
break;
}
}
}
let mut right = eos_dir;
{
let mut k = end;
while k < n {
if let Some(d) = strong_dir(classes[k]) {
right = d;
break;
}
k += 1;
}
}
let target = if left == right { left } else { embedding_dir };
for cls in &mut classes[start..end] {
*cls = target;
}
}
}
#[must_use]
pub fn split_paragraphs(text: &str) -> Vec<&str> {
let mut out = Vec::new();
let mut start = 0usize;
for (i, c) in text.char_indices() {
if bidi_class(c) == BidiClass::B {
let end = i + c.len_utf8();
out.push(&text[start..end]);
start = end;
}
}
if start < text.len() {
out.push(&text[start..]);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn explicit_format_codepoints_have_canonical_classes() {
assert_eq!(bidi_class('\u{202A}'), BidiClass::LRE);
assert_eq!(bidi_class('\u{202B}'), BidiClass::RLE);
assert_eq!(bidi_class('\u{202C}'), BidiClass::PDF);
assert_eq!(bidi_class('\u{202D}'), BidiClass::LRO);
assert_eq!(bidi_class('\u{202E}'), BidiClass::RLO);
assert_eq!(bidi_class('\u{2066}'), BidiClass::LRI);
assert_eq!(bidi_class('\u{2067}'), BidiClass::RLI);
assert_eq!(bidi_class('\u{2068}'), BidiClass::FSI);
assert_eq!(bidi_class('\u{2069}'), BidiClass::PDI);
assert_eq!(bidi_class('\u{200E}'), BidiClass::L);
assert_eq!(bidi_class('\u{200F}'), BidiClass::R);
assert_eq!(bidi_class('\u{061C}'), BidiClass::AL);
}
#[test]
fn isolate_initiator_predicate_only_fires_for_three() {
assert!(BidiClass::LRI.is_isolate_initiator());
assert!(BidiClass::RLI.is_isolate_initiator());
assert!(BidiClass::FSI.is_isolate_initiator());
assert!(!BidiClass::PDI.is_isolate_initiator());
assert!(!BidiClass::LRE.is_isolate_initiator());
assert!(!BidiClass::RLE.is_isolate_initiator());
assert!(!BidiClass::LRO.is_isolate_initiator());
assert!(!BidiClass::RLO.is_isolate_initiator());
assert!(!BidiClass::L.is_isolate_initiator());
assert!(!BidiClass::R.is_isolate_initiator());
assert!(!BidiClass::AL.is_isolate_initiator());
assert!(!BidiClass::EN.is_isolate_initiator());
assert!(!BidiClass::ON.is_isolate_initiator());
}
#[test]
fn strong_predicate_fires_only_for_l_r_al() {
assert!(BidiClass::L.is_strong());
assert!(BidiClass::R.is_strong());
assert!(BidiClass::AL.is_strong());
for c in [
BidiClass::EN,
BidiClass::ES,
BidiClass::ET,
BidiClass::AN,
BidiClass::CS,
BidiClass::NSM,
BidiClass::BN,
] {
assert!(!c.is_strong(), "{c:?} should not be strong");
}
for c in [BidiClass::B, BidiClass::S, BidiClass::WS, BidiClass::ON] {
assert!(!c.is_strong());
}
for c in [
BidiClass::LRE,
BidiClass::LRO,
BidiClass::RLE,
BidiClass::RLO,
BidiClass::PDF,
BidiClass::LRI,
BidiClass::RLI,
BidiClass::FSI,
BidiClass::PDI,
] {
assert!(!c.is_strong());
}
}
#[test]
fn ascii_classes_match_uax9_table_4() {
assert_eq!(bidi_class('A'), BidiClass::L);
assert_eq!(bidi_class('a'), BidiClass::L);
assert_eq!(bidi_class('Z'), BidiClass::L);
assert_eq!(bidi_class('z'), BidiClass::L);
assert_eq!(bidi_class('0'), BidiClass::EN);
assert_eq!(bidi_class('5'), BidiClass::EN);
assert_eq!(bidi_class('9'), BidiClass::EN);
assert_eq!(bidi_class('+'), BidiClass::ES);
assert_eq!(bidi_class('-'), BidiClass::ES);
assert_eq!(bidi_class(','), BidiClass::CS);
assert_eq!(bidi_class('.'), BidiClass::CS);
assert_eq!(bidi_class('/'), BidiClass::CS);
assert_eq!(bidi_class(':'), BidiClass::CS);
assert_eq!(bidi_class('#'), BidiClass::ET);
assert_eq!(bidi_class('$'), BidiClass::ET);
assert_eq!(bidi_class('%'), BidiClass::ET);
assert_eq!(bidi_class(' '), BidiClass::WS);
assert_eq!(bidi_class('\t'), BidiClass::S);
assert_eq!(bidi_class('\n'), BidiClass::B);
assert_eq!(bidi_class('\r'), BidiClass::B);
assert_eq!(bidi_class('\0'), BidiClass::BN);
assert_eq!(bidi_class('\u{00A0}'), BidiClass::CS);
assert_eq!(bidi_class('\u{00A3}'), BidiClass::ET); assert_eq!(bidi_class('\u{00A5}'), BidiClass::ET); assert_eq!(bidi_class('\u{00B0}'), BidiClass::ET);
assert_eq!(bidi_class('\u{00AD}'), BidiClass::BN);
}
#[test]
fn hebrew_letters_are_r() {
assert_eq!(bidi_class('\u{05D0}'), BidiClass::R);
assert_eq!(bidi_class('\u{05E0}'), BidiClass::R);
assert_eq!(bidi_class('\u{05EA}'), BidiClass::R);
}
#[test]
fn arabic_letters_are_al_and_digits_split_en_an() {
assert_eq!(bidi_class('\u{0627}'), BidiClass::AL);
assert_eq!(bidi_class('\u{0628}'), BidiClass::AL);
assert_eq!(bidi_class('\u{064A}'), BidiClass::AL);
assert_eq!(bidi_class('\u{0660}'), BidiClass::AN);
assert_eq!(bidi_class('\u{0669}'), BidiClass::AN);
assert_eq!(bidi_class('\u{06F0}'), BidiClass::EN);
assert_eq!(bidi_class('\u{06F9}'), BidiClass::EN);
assert_eq!(bidi_class('\u{064B}'), BidiClass::NSM);
assert_eq!(bidi_class('\u{0651}'), BidiClass::NSM);
assert_eq!(bidi_class('\u{0640}'), BidiClass::AL);
assert_eq!(bidi_class('\u{FE8E}'), BidiClass::AL); assert_eq!(bidi_class('\u{FEFC}'), BidiClass::AL); }
#[test]
fn combining_diacriticals_are_nsm() {
assert_eq!(bidi_class('\u{0301}'), BidiClass::NSM);
assert_eq!(bidi_class('\u{0308}'), BidiClass::NSM);
assert_eq!(bidi_class('\u{036F}'), BidiClass::NSM);
}
#[test]
fn split_paragraphs_keeps_b_with_previous() {
let v = split_paragraphs("Hello\nWorld");
assert_eq!(v, vec!["Hello\n", "World"]);
let v = split_paragraphs("Hi\n");
assert_eq!(v, vec!["Hi\n"]);
let v = split_paragraphs("A\n\nB");
assert_eq!(v, vec!["A\n", "\n", "B"]);
let v = split_paragraphs("no separators here");
assert_eq!(v, vec!["no separators here"]);
let v = split_paragraphs("");
assert!(v.is_empty());
let v = split_paragraphs("a\u{2029}b");
assert_eq!(v, vec!["a\u{2029}", "b"]);
}
#[test]
fn paragraph_level_p3_pure_latin_is_zero() {
assert_eq!(paragraph_level("Hello, world!"), 0);
assert_eq!(paragraph_level(""), 0); assert_eq!(paragraph_level(" "), 0); assert_eq!(paragraph_level("123"), 0); }
#[test]
fn paragraph_level_p3_pure_hebrew_is_one() {
assert_eq!(paragraph_level("\u{05E9}\u{05DC}\u{05D5}\u{05DD}"), 1);
}
#[test]
fn paragraph_level_p3_pure_arabic_is_one() {
assert_eq!(
paragraph_level("\u{0645}\u{0631}\u{062D}\u{0628}\u{0627}"),
1
);
}
#[test]
fn paragraph_level_first_strong_after_neutrals_decides() {
assert_eq!(paragraph_level(" \"Hello\""), 0);
assert_eq!(paragraph_level(" \u{0627}"), 1);
}
#[test]
fn paragraph_level_p2_skips_isolate_regions() {
let s = "\u{2066}Hello\u{2069}\u{05D0}";
assert_eq!(paragraph_level(s), 1);
let s = "\u{2067}\u{05D0}\u{2069}Hello";
assert_eq!(paragraph_level(s), 0);
let s = "\u{2066}\u{2067}\u{0627}\u{2069}\u{2069}World";
assert_eq!(paragraph_level(s), 0);
let s = "\u{2066}\u{05D0}";
assert_eq!(paragraph_level(s), 0);
let s = "\u{2068}\u{05D0}\u{2069}World";
assert_eq!(paragraph_level(s), 0);
}
#[test]
fn paragraph_level_embedding_initiators_do_not_skip() {
let s = "\u{202B}\u{05D0}\u{202C}Hello";
assert_eq!(paragraph_level(s), 1); let s = "\u{202B}Hello\u{202C}\u{05D0}";
assert_eq!(paragraph_level(s), 0);
}
#[test]
fn paragraph_level_unmatched_pdi_is_ignored() {
let s = "\u{2069}Hello";
assert_eq!(paragraph_level(s), 0);
}
#[test]
fn neutral_or_isolate_predicate_covers_uax9_ni_alias() {
for c in [
BidiClass::B,
BidiClass::S,
BidiClass::WS,
BidiClass::ON,
BidiClass::FSI,
BidiClass::LRI,
BidiClass::RLI,
BidiClass::PDI,
] {
assert!(c.is_neutral_or_isolate(), "{c:?} should be NI");
}
for c in [
BidiClass::L,
BidiClass::R,
BidiClass::AL,
BidiClass::EN,
BidiClass::ES,
BidiClass::ET,
BidiClass::AN,
BidiClass::CS,
BidiClass::NSM,
BidiClass::BN,
BidiClass::LRE,
BidiClass::LRO,
BidiClass::RLE,
BidiClass::RLO,
BidiClass::PDF,
] {
assert!(!c.is_neutral_or_isolate(), "{c:?} should not be NI");
}
}
#[test]
fn w_rules_empty_input_is_noop() {
let mut cls: Vec<BidiClass> = vec![];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert!(cls.is_empty());
}
#[test]
fn w1_consecutive_nsm_inherit_first_strongs_type() {
let mut cls = vec![BidiClass::AL, BidiClass::NSM, BidiClass::NSM];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::R, BidiClass::R, BidiClass::R]);
}
#[test]
fn w1_nsm_at_sequence_start_takes_sos_type() {
let mut cls = vec![BidiClass::NSM, BidiClass::L];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::R, BidiClass::L]);
}
#[test]
fn w1_nsm_after_isolate_initiator_or_pdi_becomes_on() {
let mut cls = vec![BidiClass::LRI, BidiClass::NSM];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::LRI, BidiClass::ON]);
let mut cls = vec![BidiClass::PDI, BidiClass::NSM];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::PDI, BidiClass::ON]);
let mut cls = vec![BidiClass::RLI, BidiClass::NSM];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::RLI, BidiClass::ON]);
let mut cls = vec![BidiClass::FSI, BidiClass::NSM];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::FSI, BidiClass::ON]);
}
#[test]
fn w2_en_after_al_strong_becomes_an() {
let mut cls = vec![BidiClass::AL, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::R, BidiClass::AN]);
let mut cls = vec![BidiClass::AL, BidiClass::ON, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::R, BidiClass::ON, BidiClass::AN]);
}
#[test]
fn w2_en_with_no_al_predecessor_stays_en() {
let mut cls = vec![BidiClass::ON, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::ON, BidiClass::L]);
let mut cls = vec![BidiClass::L, BidiClass::ON, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::L, BidiClass::ON, BidiClass::L]);
let mut cls = vec![BidiClass::R, BidiClass::ON, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::R, BidiClass::ON, BidiClass::EN]);
}
#[test]
fn w2_sos_alone_does_not_flip_en() {
let mut cls = vec![BidiClass::ON, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::ON, BidiClass::L]);
let mut cls = vec![BidiClass::ON, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::ON, BidiClass::EN]);
}
#[test]
fn w3_all_remaining_al_become_r() {
let mut cls = vec![BidiClass::AL, BidiClass::AL, BidiClass::AL];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::R, BidiClass::R, BidiClass::R]);
}
#[test]
fn w4_single_es_or_cs_between_two_ens_collapses_to_en() {
let mut cls = vec![BidiClass::EN, BidiClass::ES, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN, BidiClass::EN, BidiClass::EN]);
let mut cls = vec![BidiClass::EN, BidiClass::CS, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN, BidiClass::EN, BidiClass::EN]);
let mut cls = vec![BidiClass::AN, BidiClass::CS, BidiClass::AN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::AN, BidiClass::AN, BidiClass::AN]);
}
#[test]
fn w4_does_not_collapse_mixed_or_multiple_separators() {
let mut cls = vec![BidiClass::EN, BidiClass::CS, BidiClass::AN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN, BidiClass::ON, BidiClass::AN]);
let mut cls = vec![BidiClass::EN, BidiClass::ES, BidiClass::ES, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(
cls,
vec![BidiClass::EN, BidiClass::ON, BidiClass::ON, BidiClass::EN]
);
let mut cls = vec![BidiClass::AN, BidiClass::ES, BidiClass::AN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::AN, BidiClass::ON, BidiClass::AN]);
}
#[test]
fn w5_ets_adjacent_to_en_collapse() {
let mut cls = vec![BidiClass::ET, BidiClass::ET, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN, BidiClass::EN, BidiClass::EN]);
let mut cls = vec![BidiClass::EN, BidiClass::ET, BidiClass::ET];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN, BidiClass::EN, BidiClass::EN]);
let mut cls = vec![BidiClass::AN, BidiClass::ET, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::AN, BidiClass::EN, BidiClass::EN]);
}
#[test]
fn w5_isolated_ets_do_not_collapse() {
let mut cls = vec![BidiClass::R, BidiClass::ET, BidiClass::R];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::R, BidiClass::ON, BidiClass::R]);
let mut cls = vec![BidiClass::ET, BidiClass::ET];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::ON, BidiClass::ON]);
}
#[test]
fn w6_remaining_separators_terminators_become_on() {
let mut cls = vec![BidiClass::AN, BidiClass::ET];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::AN, BidiClass::ON]);
let mut cls = vec![BidiClass::L, BidiClass::ES, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::L, BidiClass::ON, BidiClass::L]);
let mut cls = vec![BidiClass::EN, BidiClass::CS, BidiClass::AN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN, BidiClass::ON, BidiClass::AN]);
let mut cls = vec![BidiClass::ET, BidiClass::AN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::ON, BidiClass::AN]);
}
#[test]
fn w7_en_after_l_becomes_l() {
let mut cls = vec![BidiClass::L, BidiClass::ON, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::L, BidiClass::ON, BidiClass::L]);
let mut cls = vec![BidiClass::R, BidiClass::ON, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::R, BidiClass::ON, BidiClass::EN]);
}
#[test]
fn w7_with_sos_l_flips_lone_en() {
let mut cls = vec![BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::L]);
let mut cls = vec![BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN]);
}
#[test]
fn w_rules_compose_w2_before_w3_before_w7() {
let mut cls = vec![BidiClass::AL, BidiClass::EN, BidiClass::EN];
resolve_weak_types(&mut cls, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::R, BidiClass::AN, BidiClass::AN]);
}
#[test]
fn n_rules_empty_input_is_noop() {
let mut cls: Vec<BidiClass> = vec![];
resolve_neutral_types(&mut cls, 0, BidiClass::L, BidiClass::L);
assert!(cls.is_empty());
}
#[test]
fn n1_l_ni_l_collapses_to_l() {
let mut cls = vec![BidiClass::L, BidiClass::ON, BidiClass::L];
resolve_neutral_types(&mut cls, 0, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::L, BidiClass::L, BidiClass::L]);
}
#[test]
fn n1_r_ni_r_collapses_to_r() {
let mut cls = vec![BidiClass::R, BidiClass::ON, BidiClass::R];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::R, BidiClass::R, BidiClass::R]);
}
#[test]
fn n1_numbers_count_as_r_for_surrounding_check() {
let mut cls = vec![BidiClass::R, BidiClass::ON, BidiClass::AN];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::R, BidiClass::R, BidiClass::AN]);
let mut cls = vec![BidiClass::R, BidiClass::ON, BidiClass::EN];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::R, BidiClass::R, BidiClass::EN]);
let mut cls = vec![BidiClass::AN, BidiClass::ON, BidiClass::R];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::AN, BidiClass::R, BidiClass::R]);
let mut cls = vec![BidiClass::AN, BidiClass::ON, BidiClass::AN];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::AN, BidiClass::R, BidiClass::AN]);
let mut cls = vec![BidiClass::AN, BidiClass::ON, BidiClass::EN];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::AN, BidiClass::R, BidiClass::EN]);
let mut cls = vec![BidiClass::EN, BidiClass::ON, BidiClass::R];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN, BidiClass::R, BidiClass::R]);
let mut cls = vec![BidiClass::EN, BidiClass::ON, BidiClass::AN];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN, BidiClass::R, BidiClass::AN]);
let mut cls = vec![BidiClass::EN, BidiClass::ON, BidiClass::EN];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::EN, BidiClass::R, BidiClass::EN]);
}
#[test]
fn n2_differing_strong_context_takes_embedding_direction() {
let mut cls = vec![BidiClass::L, BidiClass::ON, BidiClass::R];
resolve_neutral_types(&mut cls, 0, BidiClass::L, BidiClass::R);
assert_eq!(cls, vec![BidiClass::L, BidiClass::L, BidiClass::R]);
let mut cls = vec![BidiClass::L, BidiClass::ON, BidiClass::R];
resolve_neutral_types(&mut cls, 1, BidiClass::L, BidiClass::R);
assert_eq!(cls, vec![BidiClass::L, BidiClass::R, BidiClass::R]);
let mut cls = vec![BidiClass::R, BidiClass::ON, BidiClass::L];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::L);
assert_eq!(cls, vec![BidiClass::R, BidiClass::R, BidiClass::L]);
}
#[test]
fn n_rules_sos_eos_drive_boundary_runs() {
let mut cls = vec![BidiClass::ON, BidiClass::L];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::L);
assert_eq!(cls, vec![BidiClass::R, BidiClass::L]);
let mut cls = vec![BidiClass::ON, BidiClass::L];
resolve_neutral_types(&mut cls, 0, BidiClass::R, BidiClass::L);
assert_eq!(cls, vec![BidiClass::L, BidiClass::L]);
let mut cls = vec![BidiClass::ON, BidiClass::WS];
resolve_neutral_types(&mut cls, 0, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::L, BidiClass::L]);
let mut cls = vec![BidiClass::ON, BidiClass::WS];
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(cls, vec![BidiClass::R, BidiClass::R]);
}
#[test]
fn n_rules_long_ni_run_collapses_uniformly() {
let mut cls = vec![
BidiClass::L,
BidiClass::WS,
BidiClass::ON,
BidiClass::LRI,
BidiClass::PDI,
BidiClass::S,
BidiClass::B,
BidiClass::L,
];
resolve_neutral_types(&mut cls, 0, BidiClass::L, BidiClass::L);
assert_eq!(
cls,
vec![
BidiClass::L,
BidiClass::L,
BidiClass::L,
BidiClass::L,
BidiClass::L,
BidiClass::L,
BidiClass::L,
BidiClass::L,
]
);
}
#[test]
fn n_rules_leave_nsm_and_bn_alone() {
let mut cls = vec![BidiClass::L, BidiClass::NSM, BidiClass::BN, BidiClass::L];
resolve_neutral_types(&mut cls, 0, BidiClass::L, BidiClass::L);
assert_eq!(
cls,
vec![BidiClass::L, BidiClass::NSM, BidiClass::BN, BidiClass::L,]
);
}
#[test]
fn n_rules_nsm_does_not_terminate_ni_run() {
let mut cls = vec![
BidiClass::L,
BidiClass::NSM,
BidiClass::ON,
BidiClass::ON,
BidiClass::L,
];
resolve_neutral_types(&mut cls, 0, BidiClass::L, BidiClass::L);
assert_eq!(
cls,
vec![
BidiClass::L,
BidiClass::NSM,
BidiClass::L,
BidiClass::L,
BidiClass::L,
]
);
}
#[test]
fn n_rules_multiple_independent_ni_runs() {
let mut cls = vec![
BidiClass::R,
BidiClass::ON,
BidiClass::R,
BidiClass::ON,
BidiClass::ON,
BidiClass::L,
];
resolve_neutral_types(&mut cls, 0, BidiClass::R, BidiClass::L);
assert_eq!(
cls,
vec![
BidiClass::R,
BidiClass::R,
BidiClass::R,
BidiClass::L,
BidiClass::L,
BidiClass::L,
]
);
}
#[test]
fn n_rules_ni_only_sequence_uses_sos_eos() {
let mut cls = vec![BidiClass::ON, BidiClass::WS, BidiClass::ON];
resolve_neutral_types(&mut cls, 0, BidiClass::L, BidiClass::L);
assert_eq!(cls, vec![BidiClass::L, BidiClass::L, BidiClass::L]);
let mut cls = vec![BidiClass::ON, BidiClass::WS, BidiClass::ON];
resolve_neutral_types(&mut cls, 1, BidiClass::L, BidiClass::R);
assert_eq!(cls, vec![BidiClass::R, BidiClass::R, BidiClass::R]);
}
#[test]
fn n_rules_compose_with_w_rules_realistic_run() {
let mut cls = vec![
BidiClass::AL,
BidiClass::NSM,
BidiClass::EN,
BidiClass::ET,
BidiClass::EN,
BidiClass::CS,
BidiClass::AN,
];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
resolve_neutral_types(&mut cls, 1, BidiClass::R, BidiClass::R);
assert_eq!(
cls,
vec![
BidiClass::R,
BidiClass::R,
BidiClass::AN,
BidiClass::R,
BidiClass::AN,
BidiClass::AN,
BidiClass::AN,
]
);
}
#[test]
fn w_rules_full_pipeline_realistic_run() {
let mut cls = vec![
BidiClass::AL,
BidiClass::NSM,
BidiClass::EN,
BidiClass::ET,
BidiClass::EN,
BidiClass::CS,
BidiClass::AN,
];
resolve_weak_types(&mut cls, BidiClass::R, BidiClass::R);
assert_eq!(
cls,
vec![
BidiClass::R,
BidiClass::R,
BidiClass::AN,
BidiClass::ON,
BidiClass::AN,
BidiClass::AN,
BidiClass::AN,
]
);
}
}