1use std::str::{CharIndices, SplitAsciiWhitespace, SplitWhitespace};
25
26#[non_exhaustive]
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub enum Split {
33 None,
35 #[default]
38 Whitespace,
39 Character,
42 UnicodeWhitespace,
46}
47
48pub(crate) enum SplitIter<'a> {
50 Whole(Option<&'a str>),
52 Whitespace(SplitAsciiWhitespace<'a>),
54 UnicodeWhitespace(SplitWhitespace<'a>),
56 Character(&'a str, CharIndices<'a>),
58}
59
60impl<'a> Iterator for SplitIter<'a> {
61 type Item = &'a str;
62
63 fn next(&mut self) -> Option<&'a str> {
64 match self {
65 SplitIter::Whole(rest) => rest.take(),
66 SplitIter::Whitespace(it) => it.next(),
67 SplitIter::UnicodeWhitespace(it) => it.next(),
68 SplitIter::Character(src, it) => {
69 let (i, c) = it.next()?;
70 src.get(i..i + c.len_utf8())
72 }
73 }
74 }
75}
76
77pub(crate) fn split(input: &str, mode: Split) -> SplitIter<'_> {
79 match mode {
80 Split::None => SplitIter::Whole(Some(input)),
81 Split::Whitespace => SplitIter::Whitespace(input.split_ascii_whitespace()),
82 Split::UnicodeWhitespace => SplitIter::UnicodeWhitespace(input.split_whitespace()),
83 Split::Character => SplitIter::Character(input, input.char_indices()),
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 fn toks(s: &str, m: Split) -> Vec<&str> {
92 split(s, m).collect()
93 }
94
95 fn is_ws(c: char) -> bool {
96 c.is_ascii_whitespace()
97 }
98
99 fn all_strings() -> Vec<String> {
100 let alphabet = ['a', 'é', ' ', '\t', '\n', '\u{3000}', '\u{A0}', '日'];
101 let mut all = vec![String::new()];
102 let mut frontier = vec![String::new()];
103 for _ in 0..4 {
104 let mut next = Vec::new();
105 for s in &frontier {
106 for c in alphabet {
107 let mut t = s.clone();
108 t.push(c);
109 next.push(t);
110 }
111 }
112 all.extend(next.iter().cloned());
113 frontier = next;
114 }
115 all
116 }
117
118 #[test]
119 fn 空白分割() {
120 assert_eq!(toks(" a b\tc\n", Split::Whitespace), ["a", "b", "c"]);
121 assert!(toks("", Split::Whitespace).is_empty());
122 assert!(toks(" \t\n", Split::Whitespace).is_empty());
123 assert_eq!(toks("a\u{3000}b", Split::Whitespace), ["a\u{3000}b"]);
124 assert_eq!(toks("a\u{00A0}b", Split::Whitespace), ["a\u{00A0}b"]);
125 assert_eq!(toks("a\u{0B}b", Split::Whitespace), ["a\u{0B}b"]);
126 assert_eq!(toks("日本 語", Split::Whitespace), ["日本", "語"]);
127 }
128
129 #[test]
130 fn 文字分割() {
131 assert_eq!(toks("aé日", Split::Character), ["a", "é", "日"]);
132 assert!(toks("", Split::Character).is_empty());
133 assert_eq!(toks("e\u{301}", Split::Character).len(), 2);
134 assert_eq!(toks("👨\u{200D}👩", Split::Character).len(), 3);
135 assert_eq!(toks("a b", Split::Character), ["a", " ", "b"]);
136 }
137
138 #[test]
139 fn 分割なし() {
140 assert_eq!(toks("a b", Split::None), ["a b"]);
141 assert_eq!(toks("", Split::None), [""]);
142 assert_eq!(toks(" \t", Split::None), [" \t"]);
143 }
144
145 #[test]
146 fn 長さ不変条件() {
147 for s in all_strings() {
148 let ch = toks(&s, Split::Character);
149 assert_eq!(ch.len(), s.chars().count());
150 assert!(ch.iter().all(|t| t.chars().count() == 1));
151 assert_eq!(ch.concat(), s);
152 assert_eq!(ch.iter().map(|t| t.len()).sum::<usize>(), s.len());
153
154 let ws = toks(&s, Split::Whitespace);
155 assert!(ws.iter().all(|t| !t.is_empty() && !t.contains(is_ws)));
156 let stripped: String = s.chars().filter(|c| !is_ws(*c)).collect();
157 assert_eq!(ws.concat(), stripped);
158 assert!(ws.iter().map(|t| t.len()).sum::<usize>() <= s.len());
159
160 assert_eq!(toks(&s, Split::None), [s.as_str()]);
161 }
162 }
163
164 #[test]
165 fn unicode_空白分割() {
166 let u = |s| toks(s, Split::UnicodeWhitespace);
167 assert_eq!(u("a\u{3000}b"), ["a", "b"]);
168 assert_eq!(u("a\u{00A0}b"), ["a", "b"]);
169 assert_eq!(u("a\u{0B}b"), ["a", "b"]);
170 assert!(u("").is_empty());
171 assert!(u("\u{3000} \u{A0}").is_empty());
172 assert_eq!(u("a\u{200B}b"), ["a\u{200B}b"]);
173 }
174
175 #[test]
176 fn unicode_空白分割の不変条件() {
177 for s in all_strings() {
178 let ws = toks(&s, Split::UnicodeWhitespace);
179 assert!(
180 ws.iter()
181 .all(|t| !t.is_empty() && !t.contains(char::is_whitespace))
182 );
183 let stripped: String = s.chars().filter(|c| !c.is_whitespace()).collect();
184 assert_eq!(ws.concat(), stripped);
185 }
186 }
187
188 #[test]
189 fn 既定値は空白() {
190 assert_eq!(Split::default(), Split::Whitespace);
191 }
192}