1
2pub trait Rand {
3 fn next(&mut self) -> f64;
4}
5
6fn util_range(rng: &mut impl Rand, range: Option<(u32, Option<u32>)>, default_min: u32, default_max: u32) -> u32 {
7 match range.unwrap_or((default_min, Some(default_max))) {
8 (x, None) => x,
9 (x, Some(y)) => {
10 let y = y.max(x);
11
12 let x = x as f64;
13 let y = y as f64;
14
15 (rng.next() * (y - x) + x) as u32
16 }
17 }
18}
19
20pub struct Config {
21 pub paragraph_len: Option<(u32, Option<u32>)>,
22 pub sentence_len: Option<(u32, Option<u32>)>,
23 pub word_len: Option<(u32, Option<u32>)>,
24}
25impl Default for Config {
26 fn default() -> Self {
27 Self {
28 paragraph_len: None,
29 sentence_len: None,
30 word_len: None,
31 }
32 }
33}
34
35
36const FULL: &'static [&str] = &[
37 "a",
38 "b",
39 "c",
40 "d",
41 "e",
42 "f",
43 "g",
44 "h",
45 "i",
46 "j",
47 "k",
48 "l",
49 "m",
50 "n",
51 "o",
52 "p",
53 "q",
54 "r",
55 "s",
56 "t",
57 "u",
58 "v",
59 "w",
60 "x",
61 "y",
62 "z",
63];
64
65const VOWEL: &'static [&str] = &[
66 "a",
67 "e",
68 "i",
69 "o",
70 "u",
71];
72
73const FILL: &'static [&str] = &[
74 "b",
75 "c",
76 "d",
77 "f",
78 "g",
79 "h",
80 "j",
81 "k",
82 "l",
83 "m",
84 "n",
85 "p",
86 "q",
87 "r",
88 "s",
89 "t",
90 "v",
91 "w",
92 "x",
93 "y",
94 "z",
95];
96
97const LAC: &'static [&str] = &[
98 "s",
99 "l",
100 "w",
101];
102
103enum Kind {
104 None,
105 Consonant,
106 Vowel,
107 Lac,
108}
109
110fn word_raw(rng: &mut impl Rand, config: &Config) -> String {
111 let mut out = String::new();
112
113 let len = util_range(rng, config.word_len, 2, 12);
114
115 let mut state = Kind::None;
116
117 let go = |a: &[&'static str], r: f64| {
118 a[(r * a.len() as f64) as usize]
119 };
120
121 for _ in 0..len {
122 out += match state {
123 Kind::None => {
124 state = Kind::Consonant;
125 go(FULL, rng.next())
126 },
127 Kind::Consonant => if rng.next() < 0.33 {
128 state = Kind::Lac;
129 go(LAC, rng.next())
130 } else {
131 state = Kind::Vowel;
132 go(VOWEL, rng.next())
133 },
134 Kind::Lac => {
135 state = Kind::Vowel;
136 go(VOWEL, rng.next())
137 },
138 Kind::Vowel => if rng.next() < 0.25 {
139 state = Kind::Vowel;
140 go(VOWEL, rng.next())
141 } else {
142 state = Kind::Consonant;
143 go(FILL, rng.next())
144 }
145 }
146 }
147
148 out
149}
150
151pub fn word(rng: &mut impl Rand, config: &Config) -> String {
153 word_raw(rng, config)
154}
155
156
157fn sentence_raw(rng: &mut impl Rand, config: &Config) -> String {
158 let mut out = String::new();
159
160 let len = util_range(rng, config.sentence_len, 2, 14);
161
162 for i in 0..len {
163 out += word_raw(rng, config).as_str();
164 if i == len - 1 {
165 match rng.next() {
166 0.00..0.05 => out += "!",
167 0.05..0.07 => out += "!!",
168 0.07..0.14 => out += "?",
169 0.14..0.25 => out += " :3",
170 0.25..0.27 => out += "...",
171 _ => out += ".",
172 }
173 } else if rng.next() <= 0.08 {
174 out += ", ";
175 } else {
176 out += " ";
177 }
178 }
179
180 out
181}
182
183pub fn sentence(rng: &mut impl Rand, config: &Config) -> String {
187 sentence_raw(rng, config)
188}
189
190
191fn paragraph_raw(rng: &mut impl Rand, config: &Config) -> String {
192 let mut out = String::new();
193
194 let len = util_range(rng, config.paragraph_len, 6, 12);
195
196 for _ in 0..len {
197 out += sentence_raw(rng, config).as_str();
198 out += " ";
199 }
200
201 out
202}
203
204pub fn paragraph(rng: &mut impl Rand, config: &Config) -> String {
206 paragraph_raw(rng, config)
207}
208
209
210#[cfg(test)]
211mod test {
212 struct Fake;
213 impl crate::Rand for Fake {
214 fn next(&mut self) -> f64 {
215 0.0
216 }
217 }
218
219 #[test]
220 fn test() {
221 let word = crate::word(&mut Fake, &crate::Config {
222 word_len: Some((4, None)),
223 ..Default::default()
224 });
225 assert_eq!(word.len(), 4);
226 }
227}
228