1use alloc::{
2    string::{String, ToString},
3    vec::Vec,
4};
5
6use crate::hir;
7
8type Range = &'static [(char, char)];
11
12#[derive(Debug)]
17pub enum Error {
18    PropertyNotFound,
19    PropertyValueNotFound,
20    #[allow(dead_code)]
22    PerlClassNotFound,
23}
24
25#[derive(Debug)]
31pub struct CaseFoldError(());
32
33#[cfg(feature = "std")]
34impl std::error::Error for CaseFoldError {}
35
36impl core::fmt::Display for CaseFoldError {
37    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
38        write!(
39            f,
40            "Unicode-aware case folding is not available \
41             (probably because the unicode-case feature is not enabled)"
42        )
43    }
44}
45
46#[derive(Debug)]
52pub struct UnicodeWordError(());
53
54#[cfg(feature = "std")]
55impl std::error::Error for UnicodeWordError {}
56
57impl core::fmt::Display for UnicodeWordError {
58    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59        write!(
60            f,
61            "Unicode-aware \\w class is not available \
62             (probably because the unicode-perl feature is not enabled)"
63        )
64    }
65}
66
67#[derive(Debug)]
81pub struct SimpleCaseFolder {
82    table: &'static [(char, &'static [char])],
87    last: Option<char>,
89    next: usize,
93}
94
95impl SimpleCaseFolder {
96    pub fn new() -> Result<SimpleCaseFolder, CaseFoldError> {
99        #[cfg(not(feature = "unicode-case"))]
100        {
101            Err(CaseFoldError(()))
102        }
103        #[cfg(feature = "unicode-case")]
104        {
105            Ok(SimpleCaseFolder {
106                table: crate::unicode_tables::case_folding_simple::CASE_FOLDING_SIMPLE,
107                last: None,
108                next: 0,
109            })
110        }
111    }
112
113    pub fn mapping(&mut self, c: char) -> &'static [char] {
125        if let Some(last) = self.last {
126            assert!(
127                last < c,
128                "got codepoint U+{:X} which occurs before \
129                 last codepoint U+{:X}",
130                u32::from(c),
131                u32::from(last),
132            );
133        }
134        self.last = Some(c);
135        if self.next >= self.table.len() {
136            return &[];
137        }
138        let (k, v) = self.table[self.next];
139        if k == c {
140            self.next += 1;
141            return v;
142        }
143        match self.get(c) {
144            Err(i) => {
145                self.next = i;
146                &[]
147            }
148            Ok(i) => {
149                assert!(i > self.next);
156                self.next = i + 1;
157                self.table[i].1
158            }
159        }
160    }
161
162    pub fn overlaps(&self, start: char, end: char) -> bool {
179        use core::cmp::Ordering;
180
181        assert!(start <= end);
182        self.table
183            .binary_search_by(|&(c, _)| {
184                if start <= c && c <= end {
185                    Ordering::Equal
186                } else if c > end {
187                    Ordering::Greater
188                } else {
189                    Ordering::Less
190                }
191            })
192            .is_ok()
193    }
194
195    fn get(&self, c: char) -> Result<usize, usize> {
199        self.table.binary_search_by_key(&c, |&(c1, _)| c1)
200    }
201}
202
203#[derive(Debug)]
216pub enum ClassQuery<'a> {
217    OneLetter(char),
220    Binary(&'a str),
226    ByValue {
230        property_name: &'a str,
232        property_value: &'a str,
234    },
235}
236
237impl<'a> ClassQuery<'a> {
238    fn canonicalize(&self) -> Result<CanonicalClassQuery, Error> {
239        match *self {
240            ClassQuery::OneLetter(c) => self.canonical_binary(&c.to_string()),
241            ClassQuery::Binary(name) => self.canonical_binary(name),
242            ClassQuery::ByValue { property_name, property_value } => {
243                let property_name = symbolic_name_normalize(property_name);
244                let property_value = symbolic_name_normalize(property_value);
245
246                let canon_name = match canonical_prop(&property_name)? {
247                    None => return Err(Error::PropertyNotFound),
248                    Some(canon_name) => canon_name,
249                };
250                Ok(match canon_name {
251                    "General_Category" => {
252                        let canon = match canonical_gencat(&property_value)? {
253                            None => return Err(Error::PropertyValueNotFound),
254                            Some(canon) => canon,
255                        };
256                        CanonicalClassQuery::GeneralCategory(canon)
257                    }
258                    "Script" => {
259                        let canon = match canonical_script(&property_value)? {
260                            None => return Err(Error::PropertyValueNotFound),
261                            Some(canon) => canon,
262                        };
263                        CanonicalClassQuery::Script(canon)
264                    }
265                    _ => {
266                        let vals = match property_values(canon_name)? {
267                            None => return Err(Error::PropertyValueNotFound),
268                            Some(vals) => vals,
269                        };
270                        let canon_val =
271                            match canonical_value(vals, &property_value) {
272                                None => {
273                                    return Err(Error::PropertyValueNotFound)
274                                }
275                                Some(canon_val) => canon_val,
276                            };
277                        CanonicalClassQuery::ByValue {
278                            property_name: canon_name,
279                            property_value: canon_val,
280                        }
281                    }
282                })
283            }
284        }
285    }
286
287    fn canonical_binary(
288        &self,
289        name: &str,
290    ) -> Result<CanonicalClassQuery, Error> {
291        let norm = symbolic_name_normalize(name);
292
293        if norm != "cf" && norm != "sc" && norm != "lc" {
310            if let Some(canon) = canonical_prop(&norm)? {
311                return Ok(CanonicalClassQuery::Binary(canon));
312            }
313        }
314        if let Some(canon) = canonical_gencat(&norm)? {
315            return Ok(CanonicalClassQuery::GeneralCategory(canon));
316        }
317        if let Some(canon) = canonical_script(&norm)? {
318            return Ok(CanonicalClassQuery::Script(canon));
319        }
320        Err(Error::PropertyNotFound)
321    }
322}
323
324#[derive(Debug, Eq, PartialEq)]
328enum CanonicalClassQuery {
329    Binary(&'static str),
331    GeneralCategory(&'static str),
333    Script(&'static str),
335    ByValue {
342        property_name: &'static str,
344        property_value: &'static str,
346    },
347}
348
349pub fn class(query: ClassQuery<'_>) -> Result<hir::ClassUnicode, Error> {
352    use self::CanonicalClassQuery::*;
353
354    match query.canonicalize()? {
355        Binary(name) => bool_property(name),
356        GeneralCategory(name) => gencat(name),
357        Script(name) => script(name),
358        ByValue { property_name: "Age", property_value } => {
359            let mut class = hir::ClassUnicode::empty();
360            for set in ages(property_value)? {
361                class.union(&hir_class(set));
362            }
363            Ok(class)
364        }
365        ByValue { property_name: "Script_Extensions", property_value } => {
366            script_extension(property_value)
367        }
368        ByValue {
369            property_name: "Grapheme_Cluster_Break",
370            property_value,
371        } => gcb(property_value),
372        ByValue { property_name: "Sentence_Break", property_value } => {
373            sb(property_value)
374        }
375        ByValue { property_name: "Word_Break", property_value } => {
376            wb(property_value)
377        }
378        _ => {
379            Err(Error::PropertyNotFound)
381        }
382    }
383}
384
385pub fn perl_word() -> Result<hir::ClassUnicode, Error> {
389    #[cfg(not(feature = "unicode-perl"))]
390    fn imp() -> Result<hir::ClassUnicode, Error> {
391        Err(Error::PerlClassNotFound)
392    }
393
394    #[cfg(feature = "unicode-perl")]
395    fn imp() -> Result<hir::ClassUnicode, Error> {
396        use crate::unicode_tables::perl_word::PERL_WORD;
397        Ok(hir_class(PERL_WORD))
398    }
399
400    imp()
401}
402
403pub fn perl_space() -> Result<hir::ClassUnicode, Error> {
407    #[cfg(not(any(feature = "unicode-perl", feature = "unicode-bool")))]
408    fn imp() -> Result<hir::ClassUnicode, Error> {
409        Err(Error::PerlClassNotFound)
410    }
411
412    #[cfg(all(feature = "unicode-perl", not(feature = "unicode-bool")))]
413    fn imp() -> Result<hir::ClassUnicode, Error> {
414        use crate::unicode_tables::perl_space::WHITE_SPACE;
415        Ok(hir_class(WHITE_SPACE))
416    }
417
418    #[cfg(feature = "unicode-bool")]
419    fn imp() -> Result<hir::ClassUnicode, Error> {
420        use crate::unicode_tables::property_bool::WHITE_SPACE;
421        Ok(hir_class(WHITE_SPACE))
422    }
423
424    imp()
425}
426
427pub fn perl_digit() -> Result<hir::ClassUnicode, Error> {
431    #[cfg(not(any(feature = "unicode-perl", feature = "unicode-gencat")))]
432    fn imp() -> Result<hir::ClassUnicode, Error> {
433        Err(Error::PerlClassNotFound)
434    }
435
436    #[cfg(all(feature = "unicode-perl", not(feature = "unicode-gencat")))]
437    fn imp() -> Result<hir::ClassUnicode, Error> {
438        use crate::unicode_tables::perl_decimal::DECIMAL_NUMBER;
439        Ok(hir_class(DECIMAL_NUMBER))
440    }
441
442    #[cfg(feature = "unicode-gencat")]
443    fn imp() -> Result<hir::ClassUnicode, Error> {
444        use crate::unicode_tables::general_category::DECIMAL_NUMBER;
445        Ok(hir_class(DECIMAL_NUMBER))
446    }
447
448    imp()
449}
450
451pub fn hir_class(ranges: &[(char, char)]) -> hir::ClassUnicode {
453    let hir_ranges: Vec<hir::ClassUnicodeRange> = ranges
454        .iter()
455        .map(|&(s, e)| hir::ClassUnicodeRange::new(s, e))
456        .collect();
457    hir::ClassUnicode::new(hir_ranges)
458}
459
460pub fn is_word_character(c: char) -> Result<bool, UnicodeWordError> {
464    #[cfg(not(feature = "unicode-perl"))]
465    fn imp(_: char) -> Result<bool, UnicodeWordError> {
466        Err(UnicodeWordError(()))
467    }
468
469    #[cfg(feature = "unicode-perl")]
470    fn imp(c: char) -> Result<bool, UnicodeWordError> {
471        use crate::{is_word_byte, unicode_tables::perl_word::PERL_WORD};
472
473        if u8::try_from(c).map_or(false, is_word_byte) {
474            return Ok(true);
475        }
476        Ok(PERL_WORD
477            .binary_search_by(|&(start, end)| {
478                use core::cmp::Ordering;
479
480                if start <= c && c <= end {
481                    Ordering::Equal
482                } else if start > c {
483                    Ordering::Greater
484                } else {
485                    Ordering::Less
486                }
487            })
488            .is_ok())
489    }
490
491    imp(c)
492}
493
494type PropertyValues = &'static [(&'static str, &'static str)];
500
501fn canonical_gencat(
502    normalized_value: &str,
503) -> Result<Option<&'static str>, Error> {
504    Ok(match normalized_value {
505        "any" => Some("Any"),
506        "assigned" => Some("Assigned"),
507        "ascii" => Some("ASCII"),
508        _ => {
509            let gencats = property_values("General_Category")?.unwrap();
510            canonical_value(gencats, normalized_value)
511        }
512    })
513}
514
515fn canonical_script(
516    normalized_value: &str,
517) -> Result<Option<&'static str>, Error> {
518    let scripts = property_values("Script")?.unwrap();
519    Ok(canonical_value(scripts, normalized_value))
520}
521
522fn canonical_prop(
531    normalized_name: &str,
532) -> Result<Option<&'static str>, Error> {
533    #[cfg(not(any(
534        feature = "unicode-age",
535        feature = "unicode-bool",
536        feature = "unicode-gencat",
537        feature = "unicode-perl",
538        feature = "unicode-script",
539        feature = "unicode-segment",
540    )))]
541    fn imp(_: &str) -> Result<Option<&'static str>, Error> {
542        Err(Error::PropertyNotFound)
543    }
544
545    #[cfg(any(
546        feature = "unicode-age",
547        feature = "unicode-bool",
548        feature = "unicode-gencat",
549        feature = "unicode-perl",
550        feature = "unicode-script",
551        feature = "unicode-segment",
552    ))]
553    fn imp(name: &str) -> Result<Option<&'static str>, Error> {
554        use crate::unicode_tables::property_names::PROPERTY_NAMES;
555
556        Ok(PROPERTY_NAMES
557            .binary_search_by_key(&name, |&(n, _)| n)
558            .ok()
559            .map(|i| PROPERTY_NAMES[i].1))
560    }
561
562    imp(normalized_name)
563}
564
565fn canonical_value(
576    vals: PropertyValues,
577    normalized_value: &str,
578) -> Option<&'static str> {
579    vals.binary_search_by_key(&normalized_value, |&(n, _)| n)
580        .ok()
581        .map(|i| vals[i].1)
582}
583
584fn property_values(
588    canonical_property_name: &'static str,
589) -> Result<Option<PropertyValues>, Error> {
590    #[cfg(not(any(
591        feature = "unicode-age",
592        feature = "unicode-bool",
593        feature = "unicode-gencat",
594        feature = "unicode-perl",
595        feature = "unicode-script",
596        feature = "unicode-segment",
597    )))]
598    fn imp(_: &'static str) -> Result<Option<PropertyValues>, Error> {
599        Err(Error::PropertyValueNotFound)
600    }
601
602    #[cfg(any(
603        feature = "unicode-age",
604        feature = "unicode-bool",
605        feature = "unicode-gencat",
606        feature = "unicode-perl",
607        feature = "unicode-script",
608        feature = "unicode-segment",
609    ))]
610    fn imp(name: &'static str) -> Result<Option<PropertyValues>, Error> {
611        use crate::unicode_tables::property_values::PROPERTY_VALUES;
612
613        Ok(PROPERTY_VALUES
614            .binary_search_by_key(&name, |&(n, _)| n)
615            .ok()
616            .map(|i| PROPERTY_VALUES[i].1))
617    }
618
619    imp(canonical_property_name)
620}
621
622#[allow(dead_code)]
625fn property_set(
626    name_map: &'static [(&'static str, Range)],
627    canonical: &'static str,
628) -> Option<Range> {
629    name_map
630        .binary_search_by_key(&canonical, |x| x.0)
631        .ok()
632        .map(|i| name_map[i].1)
633}
634
635fn ages(canonical_age: &str) -> Result<impl Iterator<Item = Range>, Error> {
642    #[cfg(not(feature = "unicode-age"))]
643    fn imp(_: &str) -> Result<impl Iterator<Item = Range>, Error> {
644        use core::option::IntoIter;
645        Err::<IntoIter<Range>, _>(Error::PropertyNotFound)
646    }
647
648    #[cfg(feature = "unicode-age")]
649    fn imp(canonical_age: &str) -> Result<impl Iterator<Item = Range>, Error> {
650        use crate::unicode_tables::age;
651
652        const AGES: &[(&str, Range)] = &[
653            ("V1_1", age::V1_1),
654            ("V2_0", age::V2_0),
655            ("V2_1", age::V2_1),
656            ("V3_0", age::V3_0),
657            ("V3_1", age::V3_1),
658            ("V3_2", age::V3_2),
659            ("V4_0", age::V4_0),
660            ("V4_1", age::V4_1),
661            ("V5_0", age::V5_0),
662            ("V5_1", age::V5_1),
663            ("V5_2", age::V5_2),
664            ("V6_0", age::V6_0),
665            ("V6_1", age::V6_1),
666            ("V6_2", age::V6_2),
667            ("V6_3", age::V6_3),
668            ("V7_0", age::V7_0),
669            ("V8_0", age::V8_0),
670            ("V9_0", age::V9_0),
671            ("V10_0", age::V10_0),
672            ("V11_0", age::V11_0),
673            ("V12_0", age::V12_0),
674            ("V12_1", age::V12_1),
675            ("V13_0", age::V13_0),
676            ("V14_0", age::V14_0),
677            ("V15_0", age::V15_0),
678            ("V15_1", age::V15_1),
679            ("V16_0", age::V16_0),
680        ];
681        assert_eq!(AGES.len(), age::BY_NAME.len(), "ages are out of sync");
682
683        let pos = AGES.iter().position(|&(age, _)| canonical_age == age);
684        match pos {
685            None => Err(Error::PropertyValueNotFound),
686            Some(i) => Ok(AGES[..=i].iter().map(|&(_, classes)| classes)),
687        }
688    }
689
690    imp(canonical_age)
691}
692
693fn gencat(canonical_name: &'static str) -> Result<hir::ClassUnicode, Error> {
700    #[cfg(not(feature = "unicode-gencat"))]
701    fn imp(_: &'static str) -> Result<hir::ClassUnicode, Error> {
702        Err(Error::PropertyNotFound)
703    }
704
705    #[cfg(feature = "unicode-gencat")]
706    fn imp(name: &'static str) -> Result<hir::ClassUnicode, Error> {
707        use crate::unicode_tables::general_category::BY_NAME;
708        match name {
709            "ASCII" => Ok(hir_class(&[('\0', '\x7F')])),
710            "Any" => Ok(hir_class(&[('\0', '\u{10FFFF}')])),
711            "Assigned" => {
712                let mut cls = gencat("Unassigned")?;
713                cls.negate();
714                Ok(cls)
715            }
716            name => property_set(BY_NAME, name)
717                .map(hir_class)
718                .ok_or(Error::PropertyValueNotFound),
719        }
720    }
721
722    match canonical_name {
723        "Decimal_Number" => perl_digit(),
724        name => imp(name),
725    }
726}
727
728fn script(canonical_name: &'static str) -> Result<hir::ClassUnicode, Error> {
735    #[cfg(not(feature = "unicode-script"))]
736    fn imp(_: &'static str) -> Result<hir::ClassUnicode, Error> {
737        Err(Error::PropertyNotFound)
738    }
739
740    #[cfg(feature = "unicode-script")]
741    fn imp(name: &'static str) -> Result<hir::ClassUnicode, Error> {
742        use crate::unicode_tables::script::BY_NAME;
743        property_set(BY_NAME, name)
744            .map(hir_class)
745            .ok_or(Error::PropertyValueNotFound)
746    }
747
748    imp(canonical_name)
749}
750
751fn script_extension(
758    canonical_name: &'static str,
759) -> Result<hir::ClassUnicode, Error> {
760    #[cfg(not(feature = "unicode-script"))]
761    fn imp(_: &'static str) -> Result<hir::ClassUnicode, Error> {
762        Err(Error::PropertyNotFound)
763    }
764
765    #[cfg(feature = "unicode-script")]
766    fn imp(name: &'static str) -> Result<hir::ClassUnicode, Error> {
767        use crate::unicode_tables::script_extension::BY_NAME;
768        property_set(BY_NAME, name)
769            .map(hir_class)
770            .ok_or(Error::PropertyValueNotFound)
771    }
772
773    imp(canonical_name)
774}
775
776fn bool_property(
784    canonical_name: &'static str,
785) -> Result<hir::ClassUnicode, Error> {
786    #[cfg(not(feature = "unicode-bool"))]
787    fn imp(_: &'static str) -> Result<hir::ClassUnicode, Error> {
788        Err(Error::PropertyNotFound)
789    }
790
791    #[cfg(feature = "unicode-bool")]
792    fn imp(name: &'static str) -> Result<hir::ClassUnicode, Error> {
793        use crate::unicode_tables::property_bool::BY_NAME;
794        property_set(BY_NAME, name)
795            .map(hir_class)
796            .ok_or(Error::PropertyNotFound)
797    }
798
799    match canonical_name {
800        "Decimal_Number" => perl_digit(),
801        "White_Space" => perl_space(),
802        name => imp(name),
803    }
804}
805
806fn gcb(canonical_name: &'static str) -> Result<hir::ClassUnicode, Error> {
814    #[cfg(not(feature = "unicode-segment"))]
815    fn imp(_: &'static str) -> Result<hir::ClassUnicode, Error> {
816        Err(Error::PropertyNotFound)
817    }
818
819    #[cfg(feature = "unicode-segment")]
820    fn imp(name: &'static str) -> Result<hir::ClassUnicode, Error> {
821        use crate::unicode_tables::grapheme_cluster_break::BY_NAME;
822        property_set(BY_NAME, name)
823            .map(hir_class)
824            .ok_or(Error::PropertyValueNotFound)
825    }
826
827    imp(canonical_name)
828}
829
830fn wb(canonical_name: &'static str) -> Result<hir::ClassUnicode, Error> {
838    #[cfg(not(feature = "unicode-segment"))]
839    fn imp(_: &'static str) -> Result<hir::ClassUnicode, Error> {
840        Err(Error::PropertyNotFound)
841    }
842
843    #[cfg(feature = "unicode-segment")]
844    fn imp(name: &'static str) -> Result<hir::ClassUnicode, Error> {
845        use crate::unicode_tables::word_break::BY_NAME;
846        property_set(BY_NAME, name)
847            .map(hir_class)
848            .ok_or(Error::PropertyValueNotFound)
849    }
850
851    imp(canonical_name)
852}
853
854fn sb(canonical_name: &'static str) -> Result<hir::ClassUnicode, Error> {
862    #[cfg(not(feature = "unicode-segment"))]
863    fn imp(_: &'static str) -> Result<hir::ClassUnicode, Error> {
864        Err(Error::PropertyNotFound)
865    }
866
867    #[cfg(feature = "unicode-segment")]
868    fn imp(name: &'static str) -> Result<hir::ClassUnicode, Error> {
869        use crate::unicode_tables::sentence_break::BY_NAME;
870        property_set(BY_NAME, name)
871            .map(hir_class)
872            .ok_or(Error::PropertyValueNotFound)
873    }
874
875    imp(canonical_name)
876}
877
878fn symbolic_name_normalize(x: &str) -> String {
880    let mut tmp = x.as_bytes().to_vec();
881    let len = symbolic_name_normalize_bytes(&mut tmp).len();
882    tmp.truncate(len);
883    String::from_utf8(tmp).unwrap()
890}
891
892fn symbolic_name_normalize_bytes(slice: &mut [u8]) -> &mut [u8] {
903    let mut start = 0;
907    let mut starts_with_is = false;
908    if slice.len() >= 2 {
909        starts_with_is = slice[0..2] == b"is"[..]
911            || slice[0..2] == b"IS"[..]
912            || slice[0..2] == b"iS"[..]
913            || slice[0..2] == b"Is"[..];
914        if starts_with_is {
915            start = 2;
916        }
917    }
918    let mut next_write = 0;
919    for i in start..slice.len() {
920        let b = slice[i];
924        if b == b' ' || b == b'_' || b == b'-' {
925            continue;
926        } else if b'A' <= b && b <= b'Z' {
927            slice[next_write] = b + (b'a' - b'A');
928            next_write += 1;
929        } else if b <= 0x7F {
930            slice[next_write] = b;
931            next_write += 1;
932        }
933    }
934    if starts_with_is && next_write == 1 && slice[0] == b'c' {
939        slice[0] = b'i';
940        slice[1] = b's';
941        slice[2] = b'c';
942        next_write = 3;
943    }
944    &mut slice[..next_write]
945}
946
947#[cfg(test)]
948mod tests {
949    use super::*;
950
951    #[cfg(feature = "unicode-case")]
952    fn simple_fold_ok(c: char) -> impl Iterator<Item = char> {
953        SimpleCaseFolder::new().unwrap().mapping(c).iter().copied()
954    }
955
956    #[cfg(feature = "unicode-case")]
957    fn contains_case_map(start: char, end: char) -> bool {
958        SimpleCaseFolder::new().unwrap().overlaps(start, end)
959    }
960
961    #[test]
962    #[cfg(feature = "unicode-case")]
963    fn simple_fold_k() {
964        let xs: Vec<char> = simple_fold_ok('k').collect();
965        assert_eq!(xs, alloc::vec!['K', 'K']);
966
967        let xs: Vec<char> = simple_fold_ok('K').collect();
968        assert_eq!(xs, alloc::vec!['k', 'K']);
969
970        let xs: Vec<char> = simple_fold_ok('K').collect();
971        assert_eq!(xs, alloc::vec!['K', 'k']);
972    }
973
974    #[test]
975    #[cfg(feature = "unicode-case")]
976    fn simple_fold_a() {
977        let xs: Vec<char> = simple_fold_ok('a').collect();
978        assert_eq!(xs, alloc::vec!['A']);
979
980        let xs: Vec<char> = simple_fold_ok('A').collect();
981        assert_eq!(xs, alloc::vec!['a']);
982    }
983
984    #[test]
985    #[cfg(not(feature = "unicode-case"))]
986    fn simple_fold_disabled() {
987        assert!(SimpleCaseFolder::new().is_err());
988    }
989
990    #[test]
991    #[cfg(feature = "unicode-case")]
992    fn range_contains() {
993        assert!(contains_case_map('A', 'A'));
994        assert!(contains_case_map('Z', 'Z'));
995        assert!(contains_case_map('A', 'Z'));
996        assert!(contains_case_map('@', 'A'));
997        assert!(contains_case_map('Z', '['));
998        assert!(contains_case_map('☃', 'Ⰰ'));
999
1000        assert!(!contains_case_map('[', '['));
1001        assert!(!contains_case_map('[', '`'));
1002
1003        assert!(!contains_case_map('☃', '☃'));
1004    }
1005
1006    #[test]
1007    #[cfg(feature = "unicode-gencat")]
1008    fn regression_466() {
1009        use super::{CanonicalClassQuery, ClassQuery};
1010
1011        let q = ClassQuery::OneLetter('C');
1012        assert_eq!(
1013            q.canonicalize().unwrap(),
1014            CanonicalClassQuery::GeneralCategory("Other")
1015        );
1016    }
1017
1018    #[test]
1019    fn sym_normalize() {
1020        let sym_norm = symbolic_name_normalize;
1021
1022        assert_eq!(sym_norm("Line_Break"), "linebreak");
1023        assert_eq!(sym_norm("Line-break"), "linebreak");
1024        assert_eq!(sym_norm("linebreak"), "linebreak");
1025        assert_eq!(sym_norm("BA"), "ba");
1026        assert_eq!(sym_norm("ba"), "ba");
1027        assert_eq!(sym_norm("Greek"), "greek");
1028        assert_eq!(sym_norm("isGreek"), "greek");
1029        assert_eq!(sym_norm("IS_Greek"), "greek");
1030        assert_eq!(sym_norm("isc"), "isc");
1031        assert_eq!(sym_norm("is c"), "isc");
1032        assert_eq!(sym_norm("is_c"), "isc");
1033    }
1034
1035    #[test]
1036    fn valid_utf8_symbolic() {
1037        let mut x = b"abc\xFFxyz".to_vec();
1038        let y = symbolic_name_normalize_bytes(&mut x);
1039        assert_eq!(y, b"abcxyz");
1040    }
1041}