1use std::io::{self, IoSlice, Write};
2
3const MAX_IOV: usize = 1024;
6
7const CHUNK: usize = 4 * 1024 * 1024;
12
13#[inline]
16fn flush_iov(out: &mut impl Write, slices: &[IoSlice]) -> io::Result<()> {
17 for batch in slices.chunks(MAX_IOV) {
18 let total: usize = batch.iter().map(|s| s.len()).sum();
19 let written = match out.write_vectored(batch) {
20 Ok(n) if n >= total => continue,
21 Ok(n) => n,
22 Err(e) => return Err(e),
23 };
24 let mut skip = written;
26 for slice in batch {
27 let slen = slice.len();
28 if skip >= slen {
29 skip -= slen;
30 continue;
31 }
32 if skip > 0 {
33 out.write_all(&slice[skip..])?;
34 skip = 0;
35 } else {
36 out.write_all(slice)?;
37 }
38 }
39 }
40 Ok(())
41}
42
43pub fn tac_bytes_owned(
51 data: &mut [u8],
52 separator: u8,
53 before: bool,
54 out: &mut impl Write,
55) -> io::Result<()> {
56 if data.is_empty() {
57 return Ok(());
58 }
59 if before {
61 return tac_bytes(data, separator, before, out);
62 }
63
64 let len = data.len();
65
66 data.reverse();
68
69 let positions: Vec<usize> = memchr::memchr_iter(separator, data).collect();
78 let mut start = 0;
79 for &pos in &positions {
80 if pos > start {
81 data[start..pos].reverse();
82 }
83 start = pos + 1;
84 }
85 if start < len {
87 data[start..len].reverse();
88 }
89
90 out.write_all(data)
91}
92
93pub fn tac_bytes(data: &[u8], separator: u8, before: bool, out: &mut impl Write) -> io::Result<()> {
101 if data.is_empty() {
102 return Ok(());
103 }
104 if data.len() <= ZEROCOPY_LIMIT {
105 if !before {
106 tac_bytes_zerocopy_after(data, separator, out)
107 } else {
108 tac_bytes_zerocopy_before(data, separator, out)
109 }
110 } else if !before {
111 tac_bytes_backward_after(data, separator, out)
112 } else {
113 tac_bytes_backward_before(data, separator, out)
114 }
115}
116
117const ZEROCOPY_LIMIT: usize = 256 * 1024 * 1024;
121
122fn tac_bytes_zerocopy_after(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
131 let positions: Vec<usize> = memchr::memchr_iter(sep, data).collect();
134
135 let num_records = positions.len() + 1; let mut iov: Vec<IoSlice> = Vec::with_capacity(num_records);
139
140 let mut end = data.len();
141 for &pos in positions.iter().rev() {
142 let rec_start = pos + 1;
143 if rec_start < end {
144 iov.push(IoSlice::new(&data[rec_start..end]));
145 }
146 end = rec_start;
147 }
148 if end > 0 {
150 iov.push(IoSlice::new(&data[..end]));
151 }
152
153 flush_iov(out, &iov)
154}
155
156fn tac_bytes_zerocopy_before(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
159 let positions: Vec<usize> = memchr::memchr_iter(sep, data).collect();
160
161 let num_records = positions.len() + 1;
162 let mut iov: Vec<IoSlice> = Vec::with_capacity(num_records);
163
164 let mut end = data.len();
165 for &pos in positions.iter().rev() {
166 if pos < end {
167 iov.push(IoSlice::new(&data[pos..end]));
168 }
169 end = pos;
170 }
171 if end > 0 {
173 iov.push(IoSlice::new(&data[..end]));
174 }
175
176 flush_iov(out, &iov)
177}
178
179fn tac_bytes_backward_after(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
192 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
193
194 let mut global_end = data.len();
196
197 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK);
201
202 let mut chunk_start = data.len().saturating_sub(CHUNK);
203
204 loop {
205 let chunk_end = global_end.min(data.len());
206 if chunk_start >= chunk_end {
207 if chunk_start == 0 {
208 break;
209 }
210 chunk_start = chunk_start.saturating_sub(CHUNK);
211 continue;
212 }
213 let chunk = &data[chunk_start..chunk_end];
214
215 positions_buf.clear();
217 positions_buf.extend(memchr::memchr_iter(sep, chunk).map(|p| p + chunk_start));
218
219 for &pos in positions_buf.iter().rev() {
221 if pos + 1 < global_end {
222 iov.push(IoSlice::new(&data[pos + 1..global_end]));
223 }
224 global_end = pos + 1;
225
226 if iov.len() >= MAX_IOV {
227 flush_iov(out, &iov)?;
228 iov.clear();
229 }
230 }
231
232 if chunk_start == 0 {
233 break;
234 }
235 chunk_start = chunk_start.saturating_sub(CHUNK);
236 }
237
238 if global_end > 0 {
240 iov.push(IoSlice::new(&data[0..global_end]));
241 }
242 flush_iov(out, &iov)?;
243 Ok(())
244}
245
246fn tac_bytes_backward_before(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
251 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
252
253 let mut global_end = data.len();
254 let mut chunk_start = data.len().saturating_sub(CHUNK);
255 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK);
256
257 loop {
258 let chunk_end = global_end.min(data.len());
259 if chunk_start >= chunk_end {
260 if chunk_start == 0 {
261 break;
262 }
263 chunk_start = chunk_start.saturating_sub(CHUNK);
264 continue;
265 }
266 let chunk = &data[chunk_start..chunk_end];
267
268 positions_buf.clear();
269 positions_buf.extend(memchr::memchr_iter(sep, chunk).map(|p| p + chunk_start));
270
271 for &pos in positions_buf.iter().rev() {
272 iov.push(IoSlice::new(&data[pos..global_end]));
273 global_end = pos;
274
275 if iov.len() >= MAX_IOV {
276 flush_iov(out, &iov)?;
277 iov.clear();
278 }
279 }
280
281 if chunk_start == 0 {
282 break;
283 }
284 chunk_start = chunk_start.saturating_sub(CHUNK);
285 }
286
287 if global_end > 0 {
288 iov.push(IoSlice::new(&data[0..global_end]));
289 }
290 flush_iov(out, &iov)?;
291 Ok(())
292}
293
294pub fn tac_string_separator(
299 data: &[u8],
300 separator: &[u8],
301 before: bool,
302 out: &mut impl Write,
303) -> io::Result<()> {
304 if data.is_empty() {
305 return Ok(());
306 }
307
308 if separator.len() == 1 {
309 return tac_bytes(data, separator[0], before, out);
310 }
311
312 let sep_len = separator.len();
313
314 if !before {
323 tac_string_after(data, separator, sep_len, out)
324 } else {
325 tac_string_before(data, separator, sep_len, out)
326 }
327}
328
329fn tac_string_after(
331 data: &[u8],
332 separator: &[u8],
333 sep_len: usize,
334 out: &mut impl Write,
335) -> io::Result<()> {
336 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
337 let mut global_end = data.len();
338 let mut chunk_start = data.len().saturating_sub(CHUNK);
339 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK / 4);
340
341 loop {
342 let chunk_end = global_end.min(data.len());
343 let scan_start = chunk_start.saturating_sub(sep_len - 1);
344 if scan_start >= chunk_end {
345 if chunk_start == 0 {
346 break;
347 }
348 chunk_start = chunk_start.saturating_sub(CHUNK);
349 continue;
350 }
351 let scan_region = &data[scan_start..chunk_end];
352
353 positions_buf.clear();
354 positions_buf.extend(
355 memchr::memmem::find_iter(scan_region, separator)
356 .map(|p| p + scan_start)
357 .filter(|&p| p >= chunk_start || chunk_start == 0)
358 .filter(|&p| p + sep_len <= global_end),
359 );
360
361 for &pos in positions_buf.iter().rev() {
362 let rec_end_exclusive = pos + sep_len;
363 if rec_end_exclusive < global_end {
364 iov.push(IoSlice::new(&data[rec_end_exclusive..global_end]));
365 }
366 global_end = rec_end_exclusive;
367 if iov.len() >= MAX_IOV {
368 flush_iov(out, &iov)?;
369 iov.clear();
370 }
371 }
372
373 if chunk_start == 0 {
374 break;
375 }
376 chunk_start = chunk_start.saturating_sub(CHUNK);
377 }
378
379 if global_end > 0 {
380 iov.push(IoSlice::new(&data[0..global_end]));
381 }
382 flush_iov(out, &iov)?;
383 Ok(())
384}
385
386fn tac_string_before(
388 data: &[u8],
389 separator: &[u8],
390 sep_len: usize,
391 out: &mut impl Write,
392) -> io::Result<()> {
393 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
394 let mut global_end = data.len();
395 let mut chunk_start = data.len().saturating_sub(CHUNK);
396 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK / 4);
397
398 loop {
399 let chunk_end = global_end.min(data.len());
400 let scan_start = chunk_start.saturating_sub(sep_len - 1);
401 if scan_start >= chunk_end {
402 if chunk_start == 0 {
403 break;
404 }
405 chunk_start = chunk_start.saturating_sub(CHUNK);
406 continue;
407 }
408 let scan_region = &data[scan_start..chunk_end];
409
410 positions_buf.clear();
411 positions_buf.extend(
412 memchr::memmem::find_iter(scan_region, separator)
413 .map(|p| p + scan_start)
414 .filter(|&p| p >= chunk_start || chunk_start == 0)
415 .filter(|&p| p < global_end),
416 );
417
418 for &pos in positions_buf.iter().rev() {
419 iov.push(IoSlice::new(&data[pos..global_end]));
420 global_end = pos;
421 if iov.len() >= MAX_IOV {
422 flush_iov(out, &iov)?;
423 iov.clear();
424 }
425 }
426
427 if chunk_start == 0 {
428 break;
429 }
430 chunk_start = chunk_start.saturating_sub(CHUNK);
431 }
432
433 if global_end > 0 {
434 iov.push(IoSlice::new(&data[0..global_end]));
435 }
436 flush_iov(out, &iov)?;
437 Ok(())
438}
439
440fn find_regex_matches_backward(data: &[u8], re: ®ex::bytes::Regex) -> Vec<(usize, usize)> {
442 let mut matches = Vec::new();
443 let mut past_end = data.len();
444
445 while past_end > 0 {
446 let buf = &data[..past_end];
447 let mut found = false;
448
449 let mut pos = past_end;
450 while pos > 0 {
451 pos -= 1;
452 if let Some(m) = re.find_at(buf, pos) {
453 if m.start() == pos {
454 matches.push((m.start(), m.end()));
455 past_end = m.start();
456 found = true;
457 break;
458 }
459 }
460 }
461
462 if !found {
463 break;
464 }
465 }
466
467 matches.reverse();
468 matches
469}
470
471pub fn tac_regex_separator(
473 data: &[u8],
474 pattern: &str,
475 before: bool,
476 out: &mut impl Write,
477) -> io::Result<()> {
478 if data.is_empty() {
479 return Ok(());
480 }
481
482 let re = match regex::bytes::Regex::new(pattern) {
483 Ok(r) => r,
484 Err(e) => {
485 return Err(io::Error::new(
486 io::ErrorKind::InvalidInput,
487 format!("invalid regex '{}': {}", pattern, e),
488 ));
489 }
490 };
491
492 let matches = find_regex_matches_backward(data, &re);
493
494 if matches.is_empty() {
495 out.write_all(data)?;
496 return Ok(());
497 }
498
499 let mut iov: Vec<IoSlice> = Vec::with_capacity(matches.len().min(MAX_IOV) + 2);
500
501 if !before {
502 let last_end = matches.last().unwrap().1;
503
504 if last_end < data.len() {
505 iov.push(IoSlice::new(&data[last_end..]));
506 }
507
508 let mut i = matches.len();
509 while i > 0 {
510 i -= 1;
511 let rec_start = if i == 0 { 0 } else { matches[i - 1].1 };
512 iov.push(IoSlice::new(&data[rec_start..matches[i].1]));
513 if iov.len() >= MAX_IOV {
514 flush_iov(out, &iov)?;
515 iov.clear();
516 }
517 }
518 } else {
519 let mut i = matches.len();
520 while i > 0 {
521 i -= 1;
522 let start = matches[i].0;
523 let end = if i + 1 < matches.len() {
524 matches[i + 1].0
525 } else {
526 data.len()
527 };
528 iov.push(IoSlice::new(&data[start..end]));
529 if iov.len() >= MAX_IOV {
530 flush_iov(out, &iov)?;
531 iov.clear();
532 }
533 }
534
535 if matches[0].0 > 0 {
536 iov.push(IoSlice::new(&data[..matches[0].0]));
537 }
538 }
539
540 flush_iov(out, &iov)?;
541 Ok(())
542}