include!(concat!(env!("OUT_DIR"), "/bidi_class.rs"));
use precis_core::Codepoints;
fn bidi_class_cp(cp: u32) -> BidiClass {
match BIDI_CLASS_TABLE.binary_search_by(|(cps, _)| cps.partial_cmp(&cp).unwrap()) {
Ok(idx) => BIDI_CLASS_TABLE[idx].1,
Err(_) => BidiClass::L,
}
}
fn bidi_class(c: char) -> BidiClass {
bidi_class_cp(c as u32)
}
pub fn has_rtl(label: &str) -> bool {
label
.find(|c| matches!(bidi_class(c), BidiClass::R | BidiClass::AL | BidiClass::AN))
.is_some()
}
pub fn satisfy_bidi_rule(label: &str) -> bool {
let mut it = label.chars();
if let Some(c) = it.next() {
let first = bidi_class(c);
if matches!(first, BidiClass::R | BidiClass::AL) {
is_valid_rtl_label(it, first)
} else if first == BidiClass::L {
is_valid_ltr_label(it, first)
} else {
false
}
} else {
true
}
}
fn is_valid_rtl_label<I>(it: I, prev: BidiClass) -> bool
where
I: IntoIterator<Item = char>,
{
let mut prev = prev;
let mut nsm = false;
let mut en = false;
let mut an = false;
for c in it {
let class = bidi_class(c);
match class {
BidiClass::R
| BidiClass::AL
| BidiClass::ES
| BidiClass::CS
| BidiClass::ET
| BidiClass::ON
| BidiClass::BN => {}
BidiClass::AN => {
if en {
return false;
}
an = true;
}
BidiClass::EN => {
if an {
return false;
}
en = true;
}
BidiClass::NSM => {
if !matches!(
prev,
BidiClass::R | BidiClass::AL | BidiClass::EN | BidiClass::AN
) {
return false;
}
nsm = true;
continue;
}
_ => return false,
}
if nsm {
return false;
} else {
prev = class;
}
}
nsm || matches!(
prev,
BidiClass::R | BidiClass::AL | BidiClass::EN | BidiClass::AN
)
}
fn is_valid_ltr_label<I>(it: I, prev: BidiClass) -> bool
where
I: IntoIterator<Item = char>,
{
let mut prev = prev;
let mut nsm = false;
for c in it {
let class = bidi_class(c);
match class {
BidiClass::L
| BidiClass::EN
| BidiClass::ES
| BidiClass::CS
| BidiClass::ET
| BidiClass::ON
| BidiClass::BN => {
if nsm {
return false;
}
prev = class;
}
BidiClass::NSM => {
if !matches!(prev, BidiClass::L | BidiClass::EN) {
return false;
}
nsm = true;
}
_ => return false,
};
}
nsm || matches!(prev, BidiClass::L | BidiClass::EN)
}
#[cfg(test)]
mod bidi_tests {
use crate::bidi::*;
const L: char = '\u{00aa}';
const R: char = '\u{05be}';
const AL: char = '\u{0608}';
const AN: char = '\u{06dd}';
const EN: char = '\u{00b9}';
const ES: char = '\u{002b}';
const CS: char = '\u{002c}';
const ET: char = '\u{058f}';
const ON: char = '\u{037e}';
const BN: char = '\u{00ad}';
const NSM: char = '\u{1e2ae}';
const WS: char = '\u{0020}';
macro_rules! str_chars {
($($args:expr),*) => {{
let mut result = String::from("");
$(
result.push($args);
)*
result
}}
}
#[test]
fn test_bidi_class() {
assert_eq!(bidi_class(L), BidiClass::L);
assert_eq!(bidi_class(R), BidiClass::R);
assert_eq!(bidi_class(AL), BidiClass::AL);
assert_eq!(bidi_class(AN), BidiClass::AN);
assert_eq!(bidi_class(EN), BidiClass::EN);
assert_eq!(bidi_class(ES), BidiClass::ES);
assert_eq!(bidi_class(CS), BidiClass::CS);
assert_eq!(bidi_class(ET), BidiClass::ET);
assert_eq!(bidi_class(ON), BidiClass::ON);
assert_eq!(bidi_class(BN), BidiClass::BN);
assert_eq!(bidi_class(NSM), BidiClass::NSM);
assert_eq!(bidi_class(WS), BidiClass::WS);
assert_eq!(bidi_class('\u{e0080}'), BidiClass::L);
}
#[test]
fn test_has_rtl() {
assert!(!has_rtl(""));
assert!(!has_rtl("Hi"));
assert!(has_rtl(&str_chars!(R)));
assert!(has_rtl(&str_chars!(R, 'A')));
assert!(has_rtl(&str_chars!('A', R)));
assert!(has_rtl(&str_chars!(AL)));
assert!(has_rtl(&str_chars!(AL, 'A')));
assert!(has_rtl(&str_chars!('A', AL)));
assert!(has_rtl(&str_chars!(AN)));
assert!(has_rtl(&str_chars!(AN, 'A')));
assert!(has_rtl(&str_chars!('A', AN)));
}
#[test]
fn test_bidi_rule() {
assert!(satisfy_bidi_rule(""));
assert!(satisfy_bidi_rule(&str_chars!(L)));
assert!(satisfy_bidi_rule(&str_chars!(R)));
assert!(satisfy_bidi_rule(&str_chars!(AL)));
assert!(!satisfy_bidi_rule(&str_chars!(ES)));
assert!(!satisfy_bidi_rule(&str_chars!(WS)));
}
#[test]
fn test_rtl_label() {
assert!(satisfy_bidi_rule(&str_chars!(
R, AL, ES, CS, ET, ON, BN, AN
)));
assert!(satisfy_bidi_rule(&str_chars!(
R, AL, ES, CS, ET, ON, BN, EN
)));
assert!(satisfy_bidi_rule(&str_chars!(
R, AL, ES, CS, ET, ON, BN, EN, NSM
)));
assert!(!satisfy_bidi_rule(&str_chars!(
R, AL, ES, CS, WS, ON, BN, EN, NSM
)));
assert!(satisfy_bidi_rule(&str_chars!(R, AL, EN, NSM, NSM)));
assert!(satisfy_bidi_rule(&str_chars!(R, NSM, NSM, NSM, NSM)));
assert!(!satisfy_bidi_rule(&str_chars!(R, CS)));
assert!(!satisfy_bidi_rule(&str_chars!(R, ET, NSM)));
assert!(!satisfy_bidi_rule(&str_chars!(R, BN, NSM, NSM)));
assert!(!satisfy_bidi_rule(&str_chars!(R, NSM, AN)));
assert!(!satisfy_bidi_rule(&str_chars!(R, BN, NSM, NSM, AN)));
assert!(!satisfy_bidi_rule(&str_chars!(R, EN, CS, AN, AL, NSM)));
assert!(!satisfy_bidi_rule(&str_chars!(R, AN, CS, EN, AL, NSM)));
assert!(satisfy_bidi_rule(&str_chars!(R, AN, AN, AL)));
assert!(satisfy_bidi_rule(&str_chars!(R, EN, EN, AL)));
}
#[test]
fn test_ltr_label() {
assert!(satisfy_bidi_rule(&str_chars!(L, EN, ES, CS, ET, ON, BN, L)));
assert!(!satisfy_bidi_rule(&str_chars!(L, EN, ES, CS, R, ON, BN, L)));
assert!(satisfy_bidi_rule(&str_chars!(L)));
assert!(satisfy_bidi_rule(&str_chars!(L, EN)));
assert!(satisfy_bidi_rule(&str_chars!(L, EN, NSM)));
assert!(satisfy_bidi_rule(&str_chars!(L, EN, NSM, NSM)));
assert!(satisfy_bidi_rule(&str_chars!(L, NSM)));
assert!(!satisfy_bidi_rule(&str_chars!(L, ES)));
assert!(!satisfy_bidi_rule(&str_chars!(L, CS, NSM)));
assert!(!satisfy_bidi_rule(&str_chars!(L, NSM, EN)));
assert!(!satisfy_bidi_rule(&str_chars!(L, NSM, NSM, L, EN, NSM)));
}
}