1use std::collections::VecDeque;
4use std::ops::Range;
5
6use crate::{CharSet, Walk, WordSet};
7
8pub trait Lowercase {
13 fn lower(&mut self, piece: &[char], out: &mut Vec<char>);
15}
16
17#[derive(Clone, Copy, Debug, Default)]
19pub struct StdLowercase;
20
21impl Lowercase for StdLowercase {
22 fn lower(&mut self, piece: &[char], out: &mut Vec<char>) {
23 let s: String = piece.iter().collect();
24 out.extend(s.to_lowercase().chars());
25 }
26}
27
28#[derive(Clone, Copy, Debug)]
30pub struct Config<'a> {
31 pub words: &'a WordSet,
33 pub allowed: &'a CharSet,
35 pub helper_allowed: &'a CharSet,
37 pub max_number_combinations: usize,
39}
40
41#[derive(Clone, Debug)]
45struct Pair {
46 sep_start: usize,
47 word: Range<usize>,
48 end_index: usize,
49 empty: bool,
50}
51
52pub fn hide_swear_words<L: Lowercase + ?Sized>(
57 text: &str,
58 censor_char: &str,
59 config: &Config<'_>,
60 lower: &mut L,
61) -> Option<String> {
62 let all: Vec<char> = text.chars().collect();
63 let start = start_of_next_word(&all, 0, config.helper_allowed);
64 if start + 1 >= all.len() {
65 return None;
66 }
67 let mut out = String::with_capacity(text.len() + 8);
68 out.extend(&all[..start]);
69 let mut scanner = Scanner {
70 text: &all[start..],
71 config,
72 lower,
73 buf: Vec::new(),
74 replacement: censor_char.repeat(4),
75 };
76 scanner.run(&mut out);
77 Some(out)
78}
79
80fn start_of_next_word(text: &[char], from: usize, allowed: &CharSet) -> usize {
81 (from..text.len())
82 .find(|&i| allowed.contains(text[i]))
83 .unwrap_or(text.len())
84}
85
86struct Scanner<'t, 'c, L: ?Sized> {
87 text: &'t [char],
88 config: &'c Config<'c>,
89 lower: &'c mut L,
90 buf: Vec<char>,
91 replacement: String,
92}
93
94impl<L: Lowercase + ?Sized> Scanner<'_, '_, L> {
95 fn run(&mut self, out: &mut String) {
96 let text = self.text;
97 let n = text.len();
98 let mut cur = 0..0;
99 let mut skip_index: isize = -1;
100 let mut window: VecDeque<Pair> = VecDeque::new();
101 let (mut whole, mut full, mut separated) =
102 (Walk::default(), Walk::default(), Walk::default());
103
104 for (index, &c) in text.iter().enumerate() {
105 if (index as isize) < skip_index {
106 continue;
107 }
108 if self.config.allowed.contains(c) {
109 if cur.is_empty() {
110 cur = index..index;
111 }
112 cur.end = index + 1;
113 continue;
114 }
115 if self.config.allowed.strips_to_empty(&text[cur.clone()]) {
116 out.push(c);
117 cur = 0..0;
118 continue;
119 }
120
121 self.update_next_words(&mut window, index);
122 self.walk(&mut whole, cur.clone());
123 match self.any_next_words_form_swear_word(&whole, &window, &mut full, &mut separated) {
124 Some(end_index) => {
125 out.push_str(&self.replacement);
126 skip_index = end_index as isize;
127 window.clear();
128 }
129 None => {
130 if self.config.words.accepts(&whole) {
131 out.push_str(&self.replacement);
132 } else {
133 out.extend(&text[cur.clone()]);
134 }
135 out.push(c);
136 }
137 }
138 cur = 0..0;
139 }
140
141 if !cur.is_empty() && skip_index < n as isize - 1 {
142 self.walk(&mut whole, cur.clone());
143 if self.config.words.accepts(&whole) {
144 out.push_str(&self.replacement);
145 } else {
146 out.extend(&text[cur]);
147 }
148 }
149 }
150
151 fn update_next_words(&self, window: &mut VecDeque<Pair>, start_idx: usize) {
153 if window.is_empty() {
154 self.get_next_words(window, start_idx, self.config.max_number_combinations);
155 } else {
156 window.pop_front();
157 if let Some(last) = window.back() {
158 if !last.empty {
159 self.get_next_words(window, last.end_index, 1);
160 }
161 }
162 }
163 }
164
165 fn get_next_words(&self, window: &mut VecDeque<Pair>, start_idx: usize, count: usize) {
167 let text = self.text;
168 let allowed = self.config.helper_allowed;
169 let mut from = start_idx;
170 for remaining in (1..=count.max(1)).rev() {
171 let start = start_of_next_word(text, from, allowed);
172 if start + 1 >= text.len() {
173 window.push_back(Pair {
174 sep_start: start,
175 word: start..start,
176 end_index: start,
177 empty: true,
178 });
179 return;
180 }
181 let (word_end, end_index) =
184 match (start..text.len()).find(|&i| !allowed.contains(text[i])) {
185 Some(i) => (i, i),
186 None => (text.len(), text.len() - 1),
187 };
188 window.push_back(Pair {
189 sep_start: from,
190 word: start..word_end,
191 end_index,
192 empty: false,
193 });
194 if remaining == 1 {
195 return;
196 }
197 from = end_index;
198 }
199 }
200
201 fn any_next_words_form_swear_word(
203 &mut self,
204 whole: &Walk,
205 window: &VecDeque<Pair>,
206 full: &mut Walk,
207 separated: &mut Walk,
208 ) -> Option<usize> {
209 if whole.is_dead() {
210 return None;
211 }
212 full.copy_from(whole);
213 separated.copy_from(whole);
214 for pair in window {
215 if pair.empty {
216 continue;
217 }
218 self.feed(full, pair.word.clone());
219 self.feed(separated, pair.sep_start..pair.word.end);
220 let words = self.config.words;
221 if words.accepts(full) || words.accepts(separated) {
222 return Some(pair.end_index);
223 }
224 if full.is_dead() && separated.is_dead() {
225 return None;
226 }
227 }
228 None
229 }
230
231 fn walk(&mut self, walk: &mut Walk, range: Range<usize>) {
233 self.config.words.reset(walk);
234 self.feed(walk, range);
235 }
236
237 fn feed(&mut self, walk: &mut Walk, range: Range<usize>) {
239 if walk.is_dead() {
240 return;
241 }
242 let words = self.config.words;
243 let piece = &self.text[range];
244 if piece.iter().all(char::is_ascii) {
245 for &c in piece {
246 words.step(walk, c.to_ascii_lowercase());
247 if walk.is_dead() {
248 return;
249 }
250 }
251 return;
252 }
253 self.buf.clear();
254 self.lower.lower(piece, &mut self.buf);
255 for &c in &self.buf {
256 words.step(walk, c);
257 if walk.is_dead() {
258 return;
259 }
260 }
261 }
262}