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}