1use log::{debug, trace};
2use std::fmt;
3
4bitflags! {
5 #[derive(Clone, Copy, Debug, PartialEq)]
6 pub struct Joiner: u8 {
7 const NONE = 0b0000_0000;
8 const SOME = 0b0000_0001;
9 const VOWEL = 0b0000_0010;
10 const ONLY_VOWEL = 0b0000_0100;
11 const ONLY_CONSONANT = 0b0000_1000;
12 }
13}
14
15impl Joiner {
29 #[allow(dead_code)]
30 pub fn joins(self, to: Joiner) -> bool {
31 debug!("{}.joins({})", self, to);
32
33 let can_to = self.joins_to(to);
34 debug!("can to: {}", can_to);
35 let can_from = to.joins_to(self);
36 debug!("can from: {}", can_from);
37 can_to && can_from
38 }
39
40 #[allow(clippy::needless_bool)]
41 #[allow(clippy::if_same_then_else)]
42 fn joins_to(self, to: Joiner) -> bool {
43 if to.is_empty() {
45 trace!("to is empty empty");
46 false
47 } else if !to.contains(Joiner::SOME) {
48 trace!("no some in to");
49 false
50 } else if self.contains(Joiner::VOWEL) && to.contains(Joiner::ONLY_CONSONANT) {
51 false
52 } else if !self.contains(Joiner::VOWEL) && to.contains(Joiner::ONLY_VOWEL) {
53 false
54 } else {
55 true
56 }
57 }
58
59 pub fn value_next(self) -> String {
60 debug!("value_next {:b})", self);
61 if self.contains(Joiner::ONLY_CONSONANT) {
62 " +c".to_string()
63 } else if self.contains(Joiner::ONLY_VOWEL) {
64 " +v".to_string()
65 } else {
66 String::new()
67 }
68 }
69
70 pub fn value_previous(self) -> String {
71 debug!("value_previous {:b}", self);
72 if self.contains(Joiner::ONLY_CONSONANT) {
73 " -c".to_string()
74 } else if self.contains(Joiner::ONLY_VOWEL) {
75 " -v".to_string()
76 } else {
77 String::new()
78 }
79 }
80}
81
82impl fmt::Display for Joiner {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "{:b}", self.bits())
85 }
86}
87
88#[cfg(test)]
89#[allow(non_snake_case)]
90mod joiner_tests {
91 use super::*;
92 use rstest::rstest;
93
94 #[test]
95 fn value_previous__blank() {
96 let j = Joiner::SOME | Joiner::VOWEL;
97
98 assert_eq!(j.value_previous(), "".to_string());
99 assert_eq!(Joiner::SOME.value_previous(), "".to_string());
100 }
101
102 #[test]
103 fn value_previous__only_consonant() {
104 let j1 = Joiner::SOME | Joiner::ONLY_CONSONANT;
105 let j2 = Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT;
106
107 assert_eq!(j1.value_previous(), " -c".to_string());
108 assert_eq!(j2.value_previous(), " -c".to_string());
109 }
110
111 #[test]
112 fn value_previous__only_vowel() {
113 let j1 = Joiner::SOME | Joiner::ONLY_VOWEL;
114 let j2 = Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL;
115
116 assert_eq!(j1.value_previous(), " -v".to_string());
117 assert_eq!(j2.value_previous(), " -v".to_string());
118 }
119
120 #[test]
121 fn value_next__blank() {
122 let j = Joiner::SOME | Joiner::VOWEL;
123
124 assert_eq!(j.value_next(), "".to_string());
125 assert_eq!(Joiner::SOME.value_next(), "".to_string());
126 }
127
128 #[test]
129 fn value_next__only_consonant() {
130 let j1 = Joiner::SOME | Joiner::ONLY_CONSONANT;
131 let j2 = Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT;
132
133 assert_eq!(j1.value_next(), " +c".to_string());
134 assert_eq!(j2.value_next(), " +c".to_string());
135 }
136
137 #[test]
138 fn value_next__only_vowel() {
139 let j1 = Joiner::SOME | Joiner::ONLY_VOWEL;
140 let j2 = Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL;
141
142 assert_eq!(j1.value_next(), " +v".to_string());
143 assert_eq!(j2.value_next(), " +v".to_string());
144 }
145
146 #[test]
147 fn joins_1_to_9__neg() {
148 let j = Joiner::SOME;
149 let t = Joiner::SOME | Joiner::ONLY_VOWEL;
150
151 assert!(!j.joins(t));
152 }
153
154 #[rstest(j, input,
155 case(Joiner::SOME, Joiner::SOME), case(Joiner::SOME, Joiner::SOME | Joiner::VOWEL), case(Joiner::SOME, Joiner::SOME | Joiner::ONLY_CONSONANT), case(Joiner::SOME, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME), case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::VOWEL), case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL), case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME), case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::ONLY_VOWEL), )]
172 fn joins_matrix(j: Joiner, input: Joiner) {
173 assert!(j.joins(input));
174 }
175
176 #[rstest(j, input,
177 case(Joiner::SOME, Joiner::SOME | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME), case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL), case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::ONLY_CONSONANT), case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT), )]
197 fn joins_matrix_neg(j: Joiner, input: Joiner) {
198 assert!(!j.joins(input));
199 }
200
201 #[rstest(input, case(Joiner::NONE), case(Joiner::SOME))]
202 fn joins__only_vowel_ne(input: Joiner) {
203 let j = Joiner::SOME | Joiner::ONLY_VOWEL;
204
205 assert!(!j.joins(input));
206 }
207
208 #[rstest(input,
209 case(Joiner::SOME),
210 case(Joiner::SOME | Joiner::ONLY_CONSONANT),
211 )]
212 fn joins__only_consonant(input: Joiner) {
213 let j = Joiner::SOME | Joiner::ONLY_CONSONANT;
214
215 assert!(j.joins(input));
216 }
217
218 #[rstest(input,
219 case(Joiner::SOME | Joiner::VOWEL),
220 case(Joiner::SOME | Joiner::ONLY_VOWEL),
221 )]
222 fn joins__only_consonant_ne(input: Joiner) {
223 let j = Joiner::SOME | Joiner::ONLY_CONSONANT;
224
225 assert!(!j.joins(input));
226 }
227
228 #[test]
229 fn joins__s() {
230 let j = Joiner::SOME | Joiner::ONLY_VOWEL;
231 let input = Joiner::SOME | Joiner::VOWEL;
232
233 assert!(j.joins(input));
234 }
235
236 #[test]
237 fn joins_to__s() {
238 let j = Joiner::SOME | Joiner::VOWEL;
239 let input = Joiner::SOME | Joiner::ONLY_VOWEL;
240
241 assert!(j.joins_to(input));
242 }
243
244 #[rstest(input,
245 case(Joiner::NONE),
246 case(Joiner::VOWEL), )]
248 fn joins__some_ne(input: Joiner) {
249 let j = Joiner::SOME;
250
251 assert!(!j.joins(input));
252 }
253
254 #[rstest(input,
255 case(Joiner::NONE),
256 case(Joiner::SOME),
257 case(Joiner::VOWEL),
258 case(Joiner::SOME | Joiner::ONLY_VOWEL),
259 case(Joiner::SOME | Joiner::ONLY_CONSONANT),
260 case(Joiner::SOME | Joiner::VOWEL),
261 case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL),
262 case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT),
263 )]
264 fn joins__none_ne(input: Joiner) {
265 let j = Joiner::NONE;
266
267 assert!(!j.joins(input));
268 }
269
270 #[test]
271 fn contains() {
272 let j = Joiner::SOME;
273 let input = Joiner::VOWEL;
274
275 assert!(!j.contains(input));
276 }
277
278 #[test]
279 fn no_some() {
280 let j = Joiner::SOME;
281 let input = Joiner::VOWEL;
282
283 assert!(!j.contains(input));
284 }
285
286 #[test]
287 fn joins__no_ome() {
288 let j = Joiner::VOWEL;
289 let input = Joiner::SOME | Joiner::VOWEL;
290
291 assert!(!j.joins(input));
292 }
293
294 #[test]
296 fn joins__holding() {
297 let j = Joiner::SOME;
298 let input = Joiner::VOWEL;
299
300 assert!(!j.joins(input));
301 }
302
303 #[test]
304 fn joint__none() {
305 let j = Joiner::NONE;
306
307 assert!(!j.joins(Joiner::SOME));
308 }
309}