1use std::io::{self, IoSlice, Write};
2
3const MAX_IOV: usize = 1024;
6
7const CHUNK: usize = 4 * 1024 * 1024;
12
13#[inline]
17fn flush_iov(out: &mut impl Write, slices: &[IoSlice]) -> io::Result<()> {
18 if slices.is_empty() {
19 return Ok(());
20 }
21 let total: usize = slices.iter().map(|s| s.len()).sum();
23
24 let written = match out.write_vectored(slices) {
26 Ok(n) if n >= total => return Ok(()),
27 Ok(n) => n,
28 Err(e) => return Err(e),
29 };
30
31 let mut skip = written;
33 for slice in slices {
34 let slen = slice.len();
35 if skip >= slen {
36 skip -= slen;
37 continue;
38 }
39 if skip > 0 {
40 out.write_all(&slice[skip..])?;
41 skip = 0;
42 } else {
43 out.write_all(slice)?;
44 }
45 }
46 Ok(())
47}
48
49pub fn tac_bytes(data: &[u8], separator: u8, before: bool, out: &mut impl Write) -> io::Result<()> {
62 if data.is_empty() {
63 return Ok(());
64 }
65 if data.len() <= CONTIG_LIMIT {
68 if !before {
69 tac_bytes_contig_after(data, separator, out)
70 } else {
71 tac_bytes_contig_before(data, separator, out)
72 }
73 } else if !before {
74 tac_bytes_backward_after(data, separator, out)
75 } else {
76 tac_bytes_backward_before(data, separator, out)
77 }
78}
79
80const CONTIG_LIMIT: usize = 128 * 1024 * 1024;
87
88fn tac_bytes_contig_after(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
98 let positions: Vec<usize> = memchr::memchr_iter(sep, data).collect();
101
102 let num_records = positions.len() + 1; let mut iov: Vec<IoSlice> = Vec::with_capacity(num_records);
106
107 let mut end = data.len();
109 for &pos in positions.iter().rev() {
110 let rec_start = pos + 1;
111 if rec_start < end {
112 iov.push(IoSlice::new(&data[rec_start..end]));
113 }
114 end = rec_start;
115 }
116 if end > 0 {
118 iov.push(IoSlice::new(&data[0..end]));
119 }
120
121 flush_iov(out, &iov)
123}
124
125fn tac_bytes_contig_before(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
128 let positions: Vec<usize> = memchr::memchr_iter(sep, data).collect();
129
130 let num_records = positions.len() + 1;
131 let mut iov: Vec<IoSlice> = Vec::with_capacity(num_records);
132
133 let mut end = data.len();
135 for &pos in positions.iter().rev() {
136 if pos < end {
137 iov.push(IoSlice::new(&data[pos..end]));
138 }
139 end = pos;
140 }
141 if end > 0 {
143 iov.push(IoSlice::new(&data[0..end]));
144 }
145
146 flush_iov(out, &iov)
147}
148
149fn tac_bytes_backward_after(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
162 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
163
164 let mut global_end = data.len();
166
167 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK);
171
172 let mut chunk_start = data.len().saturating_sub(CHUNK);
173
174 loop {
175 let chunk_end = global_end.min(data.len());
176 if chunk_start >= chunk_end {
177 if chunk_start == 0 {
178 break;
179 }
180 chunk_start = chunk_start.saturating_sub(CHUNK);
181 continue;
182 }
183 let chunk = &data[chunk_start..chunk_end];
184
185 positions_buf.clear();
187 positions_buf.extend(memchr::memchr_iter(sep, chunk).map(|p| p + chunk_start));
188
189 for &pos in positions_buf.iter().rev() {
191 if pos + 1 < global_end {
192 iov.push(IoSlice::new(&data[pos + 1..global_end]));
193 }
194 global_end = pos + 1;
195
196 if iov.len() >= MAX_IOV {
197 flush_iov(out, &iov)?;
198 iov.clear();
199 }
200 }
201
202 if chunk_start == 0 {
203 break;
204 }
205 chunk_start = chunk_start.saturating_sub(CHUNK);
206 }
207
208 if global_end > 0 {
210 iov.push(IoSlice::new(&data[0..global_end]));
211 }
212 flush_iov(out, &iov)?;
213 Ok(())
214}
215
216fn tac_bytes_backward_before(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
221 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
222
223 let mut global_end = data.len();
224 let mut chunk_start = data.len().saturating_sub(CHUNK);
225 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK);
226
227 loop {
228 let chunk_end = global_end.min(data.len());
229 if chunk_start >= chunk_end {
230 if chunk_start == 0 {
231 break;
232 }
233 chunk_start = chunk_start.saturating_sub(CHUNK);
234 continue;
235 }
236 let chunk = &data[chunk_start..chunk_end];
237
238 positions_buf.clear();
239 positions_buf.extend(memchr::memchr_iter(sep, chunk).map(|p| p + chunk_start));
240
241 for &pos in positions_buf.iter().rev() {
242 iov.push(IoSlice::new(&data[pos..global_end]));
243 global_end = pos;
244
245 if iov.len() >= MAX_IOV {
246 flush_iov(out, &iov)?;
247 iov.clear();
248 }
249 }
250
251 if chunk_start == 0 {
252 break;
253 }
254 chunk_start = chunk_start.saturating_sub(CHUNK);
255 }
256
257 if global_end > 0 {
258 iov.push(IoSlice::new(&data[0..global_end]));
259 }
260 flush_iov(out, &iov)?;
261 Ok(())
262}
263
264pub fn tac_string_separator(
269 data: &[u8],
270 separator: &[u8],
271 before: bool,
272 out: &mut impl Write,
273) -> io::Result<()> {
274 if data.is_empty() {
275 return Ok(());
276 }
277
278 if separator.len() == 1 {
279 return tac_bytes(data, separator[0], before, out);
280 }
281
282 let sep_len = separator.len();
283
284 if !before {
293 tac_string_after(data, separator, sep_len, out)
294 } else {
295 tac_string_before(data, separator, sep_len, out)
296 }
297}
298
299fn tac_string_after(
301 data: &[u8],
302 separator: &[u8],
303 sep_len: usize,
304 out: &mut impl Write,
305) -> io::Result<()> {
306 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
307 let mut global_end = data.len();
308 let mut chunk_start = data.len().saturating_sub(CHUNK);
309 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK / 4);
310
311 loop {
312 let chunk_end = global_end.min(data.len());
313 let scan_start = chunk_start.saturating_sub(sep_len - 1);
314 if scan_start >= chunk_end {
315 if chunk_start == 0 {
316 break;
317 }
318 chunk_start = chunk_start.saturating_sub(CHUNK);
319 continue;
320 }
321 let scan_region = &data[scan_start..chunk_end];
322
323 positions_buf.clear();
324 positions_buf.extend(
325 memchr::memmem::find_iter(scan_region, separator)
326 .map(|p| p + scan_start)
327 .filter(|&p| p >= chunk_start || chunk_start == 0)
328 .filter(|&p| p + sep_len <= global_end),
329 );
330
331 for &pos in positions_buf.iter().rev() {
332 let rec_end_exclusive = pos + sep_len;
333 if rec_end_exclusive < global_end {
334 iov.push(IoSlice::new(&data[rec_end_exclusive..global_end]));
335 }
336 global_end = rec_end_exclusive;
337 if iov.len() >= MAX_IOV {
338 flush_iov(out, &iov)?;
339 iov.clear();
340 }
341 }
342
343 if chunk_start == 0 {
344 break;
345 }
346 chunk_start = chunk_start.saturating_sub(CHUNK);
347 }
348
349 if global_end > 0 {
350 iov.push(IoSlice::new(&data[0..global_end]));
351 }
352 flush_iov(out, &iov)?;
353 Ok(())
354}
355
356fn tac_string_before(
358 data: &[u8],
359 separator: &[u8],
360 sep_len: usize,
361 out: &mut impl Write,
362) -> io::Result<()> {
363 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
364 let mut global_end = data.len();
365 let mut chunk_start = data.len().saturating_sub(CHUNK);
366 let mut positions_buf: Vec<usize> = Vec::with_capacity(CHUNK / 4);
367
368 loop {
369 let chunk_end = global_end.min(data.len());
370 let scan_start = chunk_start.saturating_sub(sep_len - 1);
371 if scan_start >= chunk_end {
372 if chunk_start == 0 {
373 break;
374 }
375 chunk_start = chunk_start.saturating_sub(CHUNK);
376 continue;
377 }
378 let scan_region = &data[scan_start..chunk_end];
379
380 positions_buf.clear();
381 positions_buf.extend(
382 memchr::memmem::find_iter(scan_region, separator)
383 .map(|p| p + scan_start)
384 .filter(|&p| p >= chunk_start || chunk_start == 0)
385 .filter(|&p| p < global_end),
386 );
387
388 for &pos in positions_buf.iter().rev() {
389 iov.push(IoSlice::new(&data[pos..global_end]));
390 global_end = pos;
391 if iov.len() >= MAX_IOV {
392 flush_iov(out, &iov)?;
393 iov.clear();
394 }
395 }
396
397 if chunk_start == 0 {
398 break;
399 }
400 chunk_start = chunk_start.saturating_sub(CHUNK);
401 }
402
403 if global_end > 0 {
404 iov.push(IoSlice::new(&data[0..global_end]));
405 }
406 flush_iov(out, &iov)?;
407 Ok(())
408}
409
410fn find_regex_matches_backward(data: &[u8], re: ®ex::bytes::Regex) -> Vec<(usize, usize)> {
412 let mut matches = Vec::new();
413 let mut past_end = data.len();
414
415 while past_end > 0 {
416 let buf = &data[..past_end];
417 let mut found = false;
418
419 let mut pos = past_end;
420 while pos > 0 {
421 pos -= 1;
422 if let Some(m) = re.find_at(buf, pos) {
423 if m.start() == pos {
424 matches.push((m.start(), m.end()));
425 past_end = m.start();
426 found = true;
427 break;
428 }
429 }
430 }
431
432 if !found {
433 break;
434 }
435 }
436
437 matches.reverse();
438 matches
439}
440
441pub fn tac_regex_separator(
443 data: &[u8],
444 pattern: &str,
445 before: bool,
446 out: &mut impl Write,
447) -> io::Result<()> {
448 if data.is_empty() {
449 return Ok(());
450 }
451
452 let re = match regex::bytes::Regex::new(pattern) {
453 Ok(r) => r,
454 Err(e) => {
455 return Err(io::Error::new(
456 io::ErrorKind::InvalidInput,
457 format!("invalid regex '{}': {}", pattern, e),
458 ));
459 }
460 };
461
462 let matches = find_regex_matches_backward(data, &re);
463
464 if matches.is_empty() {
465 out.write_all(data)?;
466 return Ok(());
467 }
468
469 let mut iov: Vec<IoSlice> = Vec::with_capacity(matches.len().min(MAX_IOV) + 2);
470
471 if !before {
472 let last_end = matches.last().unwrap().1;
473
474 if last_end < data.len() {
475 iov.push(IoSlice::new(&data[last_end..]));
476 }
477
478 let mut i = matches.len();
479 while i > 0 {
480 i -= 1;
481 let rec_start = if i == 0 { 0 } else { matches[i - 1].1 };
482 iov.push(IoSlice::new(&data[rec_start..matches[i].1]));
483 if iov.len() >= MAX_IOV {
484 flush_iov(out, &iov)?;
485 iov.clear();
486 }
487 }
488 } else {
489 let mut i = matches.len();
490 while i > 0 {
491 i -= 1;
492 let start = matches[i].0;
493 let end = if i + 1 < matches.len() {
494 matches[i + 1].0
495 } else {
496 data.len()
497 };
498 iov.push(IoSlice::new(&data[start..end]));
499 if iov.len() >= MAX_IOV {
500 flush_iov(out, &iov)?;
501 iov.clear();
502 }
503 }
504
505 if matches[0].0 > 0 {
506 iov.push(IoSlice::new(&data[..matches[0].0]));
507 }
508 }
509
510 flush_iov(out, &iov)?;
511 Ok(())
512}