rnglib/
rng_joiner.rs

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
15// 1  = b00000001 = Joiner::Some
16// 3  = b00000011 = Joiner::Some | Joiner::VOWEL
17// 5  = b00000101 = Joiner::Some | Joiner::ONLY_VOWEL
18// 7  = b00000111 = Joiner::Some | Joiner::VOWEL | Joiner::ONLY_VOWEL
19// 9  = b00001001 = Joiner::Some | Joiner::ONLY_CONSONANT
20// 11 = b00001011 = Joiner::Some | Joiner::VOWEL | Joiner::ONLY_CONSONANT
21
22/// Joiner is a bitflag representation of the properties that will allow for a Syllable
23/// to join with another.
24///
25/// Joiners themselves have no awareness if they represent what's joinable previously or
26/// subsequently to it. That is determined by the Syllable using it.
27///
28impl 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        // Clippy's recommended refactor doesn't work.
44        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),                                                                               // 1 to 1
156        case(Joiner::SOME, Joiner::SOME | Joiner::VOWEL),                                                               // 1 to 3
157        case(Joiner::SOME, Joiner::SOME | Joiner::ONLY_CONSONANT),                                                      // 1 to 9
158        case(Joiner::SOME, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT),                                      // 1 to 11
159        case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME),                                                               // 3 to 1
160        case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::VOWEL),                                               // 3 to 3
161        case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::ONLY_VOWEL),                                          // 3 to 5
162        case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL),                          // 3 to 7
163        case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL),                                          // 5 to 3
164        case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT),                 // 5 to 11
165        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL),                          // 7 to 3
166        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL),     // 7 to 7
167        case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME),                                                      // 9 to 1
168        case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::ONLY_CONSONANT),                             // 9 to 9
169        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME),                                      // 11 to 1
170        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::ONLY_VOWEL),                 // 11 to 5
171    )]
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),                                                              // 1 to 5
178        case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::ONLY_CONSONANT),                                          // 3 to 9
179        case(Joiner::SOME | Joiner::VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT),                          // 3 to 11
180        case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME),                                                              // 5 to 1
181        case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::ONLY_VOWEL),                                         // 5 to 5
182        case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL),                         // 5 to 7
183        case(Joiner::SOME | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::ONLY_CONSONANT),                                     // 5 to 9
184        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME),                                              // 7 to 1
185        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::ONLY_VOWEL),                         // 7 to 5
186        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::ONLY_CONSONANT),                     // 7 to 9
187        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT),     // 7 to 11
188        case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL),                                          // 9 to 3
189        case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::ONLY_VOWEL),                                     // 9 to 5
190        case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL),                     // 9 to 7
191        case(Joiner::SOME | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT),                 // 9 to 11
192        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL),                          // 11 to 3
193        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_VOWEL),     // 11 to 7
194        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::ONLY_CONSONANT),                 // 11 to 9
195        case(Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT, Joiner::SOME | Joiner::VOWEL | Joiner::ONLY_CONSONANT), // 11 to 11
196    )]
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), // Joiner::SOME is required
247    )]
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    /// delete me when all are covered
295    #[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}