char_collection/
conversions.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Conversion (`From`) implementations for [CharCollection], via [MultiCharRange].
6
7use std::iter;
8use std::ops::RangeInclusive;
9use unic_char_range::CharRange;
10use unic_ucd_block::Block;
11use unicode_blocks::UnicodeBlockId;
12
13use crate::{CharCollection, MultiCharRange};
14
15macro_rules! impl_for_range_inclusive_int_type {
16    ($($t:ty),*) => {$(
17        impl MultiCharRange for RangeInclusive<$t> {
18            fn iter_ranges(&self) -> Box<dyn Iterator<Item=CharRange>> {
19                Box::new(iter::once(to_char_range!(self)))
20            }
21
22            fn range_count(&self) -> usize {
23                1
24            }
25    })*}
26}
27
28// This macro is needed because there is no way to express "can be cast as u32" using traits.
29macro_rules! to_char_range {
30    ($range:expr) => {
31        CharRange::closed(
32            char::try_from(*$range.start() as u32).unwrap(),
33            char::try_from(*$range.end() as u32).unwrap(),
34        )
35    };
36}
37
38impl MultiCharRange for char {
39    fn iter_ranges(&self) -> Box<dyn Iterator<Item = CharRange>> {
40        Box::new(std::iter::once(CharRange::closed(*self, *self)))
41    }
42
43    fn range_count(&self) -> usize {
44        1
45    }
46}
47
48impl MultiCharRange for CharRange {
49    fn iter_ranges(&self) -> Box<dyn Iterator<Item = CharRange>> {
50        Box::new(iter::once(self.clone()))
51    }
52
53    fn range_count(&self) -> usize {
54        1
55    }
56}
57
58impl MultiCharRange for RangeInclusive<char> {
59    fn iter_ranges(&self) -> Box<dyn Iterator<Item = CharRange>> {
60        Box::new(iter::once(CharRange::closed(*self.start(), *self.end())))
61    }
62
63    fn range_count(&self) -> usize {
64        1
65    }
66}
67
68impl_for_range_inclusive_int_type!(u8, i8, u32, i32);
69
70impl MultiCharRange for UnicodeBlockId {
71    fn iter_ranges(&self) -> Box<dyn Iterator<Item = CharRange>> {
72        self.block().iter_ranges()
73    }
74
75    fn range_count(&self) -> usize {
76        1
77    }
78}
79
80impl MultiCharRange for Block {
81    fn iter_ranges<'a>(&'a self) -> Box<dyn Iterator<Item = CharRange> + 'a> {
82        Box::new(self.range.iter_ranges())
83    }
84
85    fn range_count(&self) -> usize {
86        1
87    }
88}
89
90impl<T: MultiCharRange> From<&T> for CharCollection {
91    fn from(source: &T) -> Self {
92        let mut collection = CharCollection::new();
93        collection.insert(source);
94        collection
95    }
96}
97
98#[cfg(test)]
99mod multi_char_range_tests {
100    use crate::MultiCharRange;
101    use unic_char_range::{chars, CharRange};
102
103    #[test]
104    fn test_char() {
105        let source = 'a';
106        assert_eq!(source.iter_ranges().collect::<Vec<CharRange>>(), vec![chars!('a'..='a')]);
107        assert_eq!(source.range_count(), 1);
108    }
109
110    #[test]
111    fn test_char_range() {
112        let source = chars!('d'..='g');
113        assert_eq!(source.iter_ranges().collect::<Vec<CharRange>>(), vec![chars!('d'..='g')]);
114        assert_eq!(source.range_count(), 1);
115    }
116
117    #[test]
118    fn test_range_inclusive_char() {
119        let source = 'd'..='g';
120        assert_eq!(source.iter_ranges().collect::<Vec<CharRange>>(), vec![chars!('d'..='g')]);
121        assert_eq!(source.range_count(), 1);
122    }
123
124    macro_rules! test_range_inclusive_int {
125        ($t:ty) => {
126            paste::paste! {
127                #[test]
128                fn [<test_char_range_inclusive_ $t>]() {
129                    let source: std::ops::RangeInclusive<$t> = 0x0..=0x9;
130                        assert_eq!(
131                            source.iter_ranges().collect::<Vec<CharRange>>(),
132                            vec![chars!('\u{0}'..='\u{9}')]
133                    );
134                    assert_eq!(source.range_count(), 1);
135                }
136            }
137        };
138    }
139
140    test_range_inclusive_int!(u8);
141    test_range_inclusive_int!(i8);
142    test_range_inclusive_int!(u32);
143    test_range_inclusive_int!(i32);
144
145    #[test]
146    fn test_unicode_block_id() {
147        let source = unicode_blocks::UnicodeBlockId::BasicLatin;
148        assert_eq!(
149            source.iter_ranges().collect::<Vec<CharRange>>(),
150            vec![chars!('\u{0000}'..='\u{007f}')]
151        );
152        assert_eq!(source.range_count(), 1);
153    }
154
155    #[test]
156    fn test_unicode_block() {
157        let source = unicode_blocks::UnicodeBlockId::BasicLatin.block();
158        assert_eq!(
159            source.iter_ranges().collect::<Vec<CharRange>>(),
160            vec![chars!('\u{0000}'..='\u{007f}')]
161        );
162        assert_eq!(source.range_count(), 1);
163    }
164}
165
166#[cfg(test)]
167mod from_tests {
168    use crate::CharCollection;
169    use unicode_blocks::UnicodeBlockId;
170
171    #[test]
172    fn test_char() {
173        let actual: CharCollection = (&'a').into();
174        assert_eq!(actual, char_collect!('a'..='a'));
175    }
176
177    #[test]
178    fn test_unicode_block_id() {
179        let actual: CharCollection = (&UnicodeBlockId::BasicLatin).into();
180        assert_eq!(actual, char_collect!('\u{0000}'..='\u{007f}'));
181    }
182}