Skip to main content

unicode_normalize_aligned/
quick_check.rs

1//! UAX #15 section 9 quick checks and the authoritative `is_nfX` predicates.
2
3use crate::props::{Props, QuickCheck};
4use crate::UnicodeNormalization;
5
6/// Answer of a quick check.
7///
8/// `Maybe` means the quick check cannot decide from per-character properties
9/// alone. The `is_nfc`, `is_nfd`, `is_nfkc` and `is_nfkd` functions resolve it
10/// by normalizing and comparing.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum IsNormalized {
13    /// The text is in the normalization form.
14    Yes,
15    /// The text is not in the normalization form.
16    No,
17    /// Undecidable without normalizing.
18    Maybe,
19}
20
21#[derive(Clone, Copy)]
22enum Form {
23    Nfc,
24    Nfd,
25    Nfkc,
26    Nfkd,
27}
28
29fn quick_check<I: Iterator<Item = char>>(s: I, form: Form) -> IsNormalized {
30    let mut last_ccc = 0u8;
31    let mut result = IsNormalized::Yes;
32    for c in s {
33        if (c as u32) < 0x80 {
34            last_ccc = 0;
35            continue;
36        }
37        let p = Props::of(c);
38        let ccc = p.ccc();
39        if ccc != 0 && last_ccc > ccc {
40            return IsNormalized::No;
41        }
42        let qc = match form {
43            Form::Nfc => p.nfc_qc(),
44            Form::Nfkc => p.nfkc_qc(),
45            Form::Nfd => {
46                if p.nfd_no() {
47                    QuickCheck::No
48                } else {
49                    QuickCheck::Yes
50                }
51            }
52            Form::Nfkd => {
53                if p.nfkd_no() {
54                    QuickCheck::No
55                } else {
56                    QuickCheck::Yes
57                }
58            }
59        };
60        match qc {
61            QuickCheck::No => return IsNormalized::No,
62            QuickCheck::Maybe => result = IsNormalized::Maybe,
63            QuickCheck::Yes => {}
64        }
65        last_ccc = ccc;
66    }
67    result
68}
69
70/// Quick check for NFC over a character iterator.
71pub fn is_nfc_quick<I: Iterator<Item = char>>(s: I) -> IsNormalized {
72    quick_check(s, Form::Nfc)
73}
74
75/// Quick check for NFD over a character iterator.
76pub fn is_nfd_quick<I: Iterator<Item = char>>(s: I) -> IsNormalized {
77    quick_check(s, Form::Nfd)
78}
79
80/// Quick check for NFKC over a character iterator.
81pub fn is_nfkc_quick<I: Iterator<Item = char>>(s: I) -> IsNormalized {
82    quick_check(s, Form::Nfkc)
83}
84
85/// Quick check for NFKD over a character iterator.
86pub fn is_nfkd_quick<I: Iterator<Item = char>>(s: I) -> IsNormalized {
87    quick_check(s, Form::Nfkd)
88}
89
90/// Whether `s` is in NFC. Resolves a `Maybe` quick check by normalizing and
91/// comparing, without building a string.
92pub fn is_nfc(s: &str) -> bool {
93    match is_nfc_quick(s.chars()) {
94        IsNormalized::Yes => true,
95        IsNormalized::No => false,
96        IsNormalized::Maybe => s.nfc().map(|p| p.0).eq(s.chars()),
97    }
98}
99
100/// Whether `s` is in NFD. Resolves a `Maybe` quick check by normalizing and
101/// comparing, without building a string.
102pub fn is_nfd(s: &str) -> bool {
103    match is_nfd_quick(s.chars()) {
104        IsNormalized::Yes => true,
105        IsNormalized::No => false,
106        IsNormalized::Maybe => s.nfd().map(|p| p.0).eq(s.chars()),
107    }
108}
109
110/// Whether `s` is in NFKC. Resolves a `Maybe` quick check by normalizing and
111/// comparing, without building a string.
112pub fn is_nfkc(s: &str) -> bool {
113    match is_nfkc_quick(s.chars()) {
114        IsNormalized::Yes => true,
115        IsNormalized::No => false,
116        IsNormalized::Maybe => s.nfkc().map(|p| p.0).eq(s.chars()),
117    }
118}
119
120/// Whether `s` is in NFKD. Resolves a `Maybe` quick check by normalizing and
121/// comparing, without building a string.
122pub fn is_nfkd(s: &str) -> bool {
123    match is_nfkd_quick(s.chars()) {
124        IsNormalized::Yes => true,
125        IsNormalized::No => false,
126        IsNormalized::Maybe => s.nfkd().map(|p| p.0).eq(s.chars()),
127    }
128}