1use std::io::{self, IoSlice, Write};
2
3const MAX_IOV: usize = 1024;
6
7const CHUNK: usize = 512 * 1024;
10
11#[inline]
15fn flush_iov(out: &mut impl Write, slices: &[IoSlice]) -> io::Result<()> {
16 if slices.is_empty() {
17 return Ok(());
18 }
19 let total: usize = slices.iter().map(|s| s.len()).sum();
21
22 let written = match out.write_vectored(slices) {
24 Ok(n) if n >= total => return Ok(()),
25 Ok(n) => n,
26 Err(e) => return Err(e),
27 };
28
29 let mut skip = written;
31 for slice in slices {
32 let slen = slice.len();
33 if skip >= slen {
34 skip -= slen;
35 continue;
36 }
37 if skip > 0 {
38 out.write_all(&slice[skip..])?;
39 skip = 0;
40 } else {
41 out.write_all(slice)?;
42 }
43 }
44 Ok(())
45}
46
47pub fn tac_bytes(data: &[u8], separator: u8, before: bool, out: &mut impl Write) -> io::Result<()> {
52 if data.is_empty() {
53 return Ok(());
54 }
55 if !before {
56 tac_bytes_backward_after(data, separator, out)
57 } else {
58 tac_bytes_backward_before(data, separator, out)
59 }
60}
61
62fn tac_bytes_backward_after(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
75 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
76
77 let mut global_end = data.len();
79
80 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK);
84
85 let mut chunk_start = data.len().saturating_sub(CHUNK);
86
87 loop {
88 let chunk_end = global_end.min(data.len());
89 if chunk_start >= chunk_end {
90 if chunk_start == 0 {
91 break;
92 }
93 chunk_start = chunk_start.saturating_sub(CHUNK);
94 continue;
95 }
96 let chunk = &data[chunk_start..chunk_end];
97
98 positions_buf.clear();
100 positions_buf.extend(memchr::memchr_iter(sep, chunk).map(|p| p + chunk_start));
101
102 for &pos in positions_buf.iter().rev() {
104 if pos + 1 < global_end {
105 iov.push(IoSlice::new(&data[pos + 1..global_end]));
106 }
107 global_end = pos + 1;
108
109 if iov.len() >= MAX_IOV {
110 flush_iov(out, &iov)?;
111 iov.clear();
112 }
113 }
114
115 if chunk_start == 0 {
116 break;
117 }
118 chunk_start = chunk_start.saturating_sub(CHUNK);
119 }
120
121 if global_end > 0 {
123 iov.push(IoSlice::new(&data[0..global_end]));
124 }
125 flush_iov(out, &iov)?;
126 Ok(())
127}
128
129fn tac_bytes_backward_before(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
134 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
135
136 let mut global_end = data.len();
137 let mut chunk_start = data.len().saturating_sub(CHUNK);
138 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK);
139
140 loop {
141 let chunk_end = global_end.min(data.len());
142 if chunk_start >= chunk_end {
143 if chunk_start == 0 {
144 break;
145 }
146 chunk_start = chunk_start.saturating_sub(CHUNK);
147 continue;
148 }
149 let chunk = &data[chunk_start..chunk_end];
150
151 positions_buf.clear();
152 positions_buf.extend(memchr::memchr_iter(sep, chunk).map(|p| p + chunk_start));
153
154 for &pos in positions_buf.iter().rev() {
155 iov.push(IoSlice::new(&data[pos..global_end]));
156 global_end = pos;
157
158 if iov.len() >= MAX_IOV {
159 flush_iov(out, &iov)?;
160 iov.clear();
161 }
162 }
163
164 if chunk_start == 0 {
165 break;
166 }
167 chunk_start = chunk_start.saturating_sub(CHUNK);
168 }
169
170 if global_end > 0 {
171 iov.push(IoSlice::new(&data[0..global_end]));
172 }
173 flush_iov(out, &iov)?;
174 Ok(())
175}
176
177pub fn tac_string_separator(
182 data: &[u8],
183 separator: &[u8],
184 before: bool,
185 out: &mut impl Write,
186) -> io::Result<()> {
187 if data.is_empty() {
188 return Ok(());
189 }
190
191 if separator.len() == 1 {
192 return tac_bytes(data, separator[0], before, out);
193 }
194
195 let sep_len = separator.len();
196
197 if !before {
206 tac_string_after(data, separator, sep_len, out)
207 } else {
208 tac_string_before(data, separator, sep_len, out)
209 }
210}
211
212fn tac_string_after(
214 data: &[u8],
215 separator: &[u8],
216 sep_len: usize,
217 out: &mut impl Write,
218) -> io::Result<()> {
219 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
220 let mut global_end = data.len();
221 let mut chunk_start = data.len().saturating_sub(CHUNK);
222 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK / 4);
223
224 loop {
225 let chunk_end = global_end.min(data.len());
226 let scan_start = chunk_start.saturating_sub(sep_len - 1);
227 if scan_start >= chunk_end {
228 if chunk_start == 0 {
229 break;
230 }
231 chunk_start = chunk_start.saturating_sub(CHUNK);
232 continue;
233 }
234 let scan_region = &data[scan_start..chunk_end];
235
236 positions_buf.clear();
237 positions_buf.extend(
238 memchr::memmem::find_iter(scan_region, separator)
239 .map(|p| p + scan_start)
240 .filter(|&p| p >= chunk_start || chunk_start == 0)
241 .filter(|&p| p + sep_len <= global_end),
242 );
243
244 for &pos in positions_buf.iter().rev() {
245 let rec_end_exclusive = pos + sep_len;
246 if rec_end_exclusive < global_end {
247 iov.push(IoSlice::new(&data[rec_end_exclusive..global_end]));
248 }
249 global_end = rec_end_exclusive;
250 if iov.len() >= MAX_IOV {
251 flush_iov(out, &iov)?;
252 iov.clear();
253 }
254 }
255
256 if chunk_start == 0 {
257 break;
258 }
259 chunk_start = chunk_start.saturating_sub(CHUNK);
260 }
261
262 if global_end > 0 {
263 iov.push(IoSlice::new(&data[0..global_end]));
264 }
265 flush_iov(out, &iov)?;
266 Ok(())
267}
268
269fn tac_string_before(
271 data: &[u8],
272 separator: &[u8],
273 sep_len: usize,
274 out: &mut impl Write,
275) -> io::Result<()> {
276 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
277 let mut global_end = data.len();
278 let mut chunk_start = data.len().saturating_sub(CHUNK);
279 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK / 4);
280
281 loop {
282 let chunk_end = global_end.min(data.len());
283 let scan_start = chunk_start.saturating_sub(sep_len - 1);
284 if scan_start >= chunk_end {
285 if chunk_start == 0 {
286 break;
287 }
288 chunk_start = chunk_start.saturating_sub(CHUNK);
289 continue;
290 }
291 let scan_region = &data[scan_start..chunk_end];
292
293 positions_buf.clear();
294 positions_buf.extend(
295 memchr::memmem::find_iter(scan_region, separator)
296 .map(|p| p + scan_start)
297 .filter(|&p| p >= chunk_start || chunk_start == 0)
298 .filter(|&p| p < global_end),
299 );
300
301 for &pos in positions_buf.iter().rev() {
302 iov.push(IoSlice::new(&data[pos..global_end]));
303 global_end = pos;
304 if iov.len() >= MAX_IOV {
305 flush_iov(out, &iov)?;
306 iov.clear();
307 }
308 }
309
310 if chunk_start == 0 {
311 break;
312 }
313 chunk_start = chunk_start.saturating_sub(CHUNK);
314 }
315
316 if global_end > 0 {
317 iov.push(IoSlice::new(&data[0..global_end]));
318 }
319 flush_iov(out, &iov)?;
320 Ok(())
321}
322
323fn find_regex_matches_backward(data: &[u8], re: ®ex::bytes::Regex) -> Vec<(usize, usize)> {
325 let mut matches = Vec::new();
326 let mut past_end = data.len();
327
328 while past_end > 0 {
329 let buf = &data[..past_end];
330 let mut found = false;
331
332 let mut pos = past_end;
333 while pos > 0 {
334 pos -= 1;
335 if let Some(m) = re.find_at(buf, pos) {
336 if m.start() == pos {
337 matches.push((m.start(), m.end()));
338 past_end = m.start();
339 found = true;
340 break;
341 }
342 }
343 }
344
345 if !found {
346 break;
347 }
348 }
349
350 matches.reverse();
351 matches
352}
353
354pub fn tac_regex_separator(
356 data: &[u8],
357 pattern: &str,
358 before: bool,
359 out: &mut impl Write,
360) -> io::Result<()> {
361 if data.is_empty() {
362 return Ok(());
363 }
364
365 let re = match regex::bytes::Regex::new(pattern) {
366 Ok(r) => r,
367 Err(e) => {
368 return Err(io::Error::new(
369 io::ErrorKind::InvalidInput,
370 format!("invalid regex '{}': {}", pattern, e),
371 ));
372 }
373 };
374
375 let matches = find_regex_matches_backward(data, &re);
376
377 if matches.is_empty() {
378 out.write_all(data)?;
379 return Ok(());
380 }
381
382 let mut iov: Vec<IoSlice> = Vec::with_capacity(matches.len().min(MAX_IOV) + 2);
383
384 if !before {
385 let last_end = matches.last().unwrap().1;
386
387 if last_end < data.len() {
388 iov.push(IoSlice::new(&data[last_end..]));
389 }
390
391 let mut i = matches.len();
392 while i > 0 {
393 i -= 1;
394 let rec_start = if i == 0 { 0 } else { matches[i - 1].1 };
395 iov.push(IoSlice::new(&data[rec_start..matches[i].1]));
396 if iov.len() >= MAX_IOV {
397 flush_iov(out, &iov)?;
398 iov.clear();
399 }
400 }
401 } else {
402 let mut i = matches.len();
403 while i > 0 {
404 i -= 1;
405 let start = matches[i].0;
406 let end = if i + 1 < matches.len() {
407 matches[i + 1].0
408 } else {
409 data.len()
410 };
411 iov.push(IoSlice::new(&data[start..end]));
412 if iov.len() >= MAX_IOV {
413 flush_iov(out, &iov)?;
414 iov.clear();
415 }
416 }
417
418 if matches[0].0 > 0 {
419 iov.push(IoSlice::new(&data[..matches[0].0]));
420 }
421 }
422
423 flush_iov(out, &iov)?;
424 Ok(())
425}