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<()> {
72 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
74
75 let mut global_end = data.len();
78
79 let mut chunk_start = data.len().saturating_sub(CHUNK);
83
84 loop {
85 let chunk_end = global_end.min(data.len());
86 if chunk_start >= chunk_end {
87 if chunk_start == 0 {
89 break;
90 }
91 chunk_start = chunk_start.saturating_sub(CHUNK);
92 continue;
93 }
94 let chunk = &data[chunk_start..chunk_end];
95
96 let positions: Vec<usize> = memchr::memchr_iter(sep, chunk)
100 .map(|p| p + chunk_start) .collect();
102
103 for &pos in positions.iter().rev() {
105 if pos + 1 < global_end {
112 iov.push(IoSlice::new(&data[pos + 1..global_end]));
113 }
114 global_end = pos + 1; if iov.len() >= MAX_IOV {
117 flush_iov(out, &iov)?;
118 iov.clear();
119 }
120 }
121
122 if chunk_start == 0 {
123 break;
124 }
125 chunk_start = chunk_start.saturating_sub(CHUNK);
126 }
127
128 if global_end > 0 {
131 iov.push(IoSlice::new(&data[0..global_end]));
132 }
133 flush_iov(out, &iov)?;
134 Ok(())
135}
136
137fn tac_bytes_backward_before(data: &[u8], sep: u8, out: &mut impl Write) -> io::Result<()> {
142 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
143
144 let mut global_end = data.len();
146 let mut chunk_start = data.len().saturating_sub(CHUNK);
147
148 loop {
149 let chunk_end = global_end.min(data.len());
150 if chunk_start >= chunk_end {
151 if chunk_start == 0 {
152 break;
153 }
154 chunk_start = chunk_start.saturating_sub(CHUNK);
155 continue;
156 }
157 let chunk = &data[chunk_start..chunk_end];
158
159 let positions: Vec<usize> = memchr::memchr_iter(sep, chunk)
160 .map(|p| p + chunk_start)
161 .collect();
162
163 for &pos in positions.iter().rev() {
165 iov.push(IoSlice::new(&data[pos..global_end]));
168 global_end = pos;
169
170 if iov.len() >= MAX_IOV {
171 flush_iov(out, &iov)?;
172 iov.clear();
173 }
174 }
175
176 if chunk_start == 0 {
177 break;
178 }
179 chunk_start = chunk_start.saturating_sub(CHUNK);
180 }
181
182 if global_end > 0 {
184 iov.push(IoSlice::new(&data[0..global_end]));
185 }
186 flush_iov(out, &iov)?;
187 Ok(())
188}
189
190pub fn tac_string_separator(
195 data: &[u8],
196 separator: &[u8],
197 before: bool,
198 out: &mut impl Write,
199) -> io::Result<()> {
200 if data.is_empty() {
201 return Ok(());
202 }
203
204 if separator.len() == 1 {
205 return tac_bytes(data, separator[0], before, out);
206 }
207
208 let sep_len = separator.len();
209
210 if !before {
219 tac_string_after(data, separator, sep_len, out)
220 } else {
221 tac_string_before(data, separator, sep_len, out)
222 }
223}
224
225fn tac_string_after(
227 data: &[u8],
228 separator: &[u8],
229 sep_len: usize,
230 out: &mut impl Write,
231) -> io::Result<()> {
232 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
233 let mut global_end = data.len();
234 let mut chunk_start = data.len().saturating_sub(CHUNK);
235
236 loop {
237 let chunk_end = global_end.min(data.len());
238 let scan_start = chunk_start.saturating_sub(sep_len - 1);
240 if scan_start >= chunk_end {
241 if chunk_start == 0 {
242 break;
243 }
244 chunk_start = chunk_start.saturating_sub(CHUNK);
245 continue;
246 }
247 let scan_region = &data[scan_start..chunk_end];
248
249 let positions: Vec<usize> = memchr::memmem::find_iter(scan_region, separator)
251 .map(|p| p + scan_start) .filter(|&p| p >= chunk_start || chunk_start == 0) .filter(|&p| p + sep_len <= global_end) .collect();
255
256 for &pos in positions.iter().rev() {
257 let rec_end_exclusive = pos + sep_len;
258 if rec_end_exclusive < global_end {
259 iov.push(IoSlice::new(&data[rec_end_exclusive..global_end]));
260 }
261 global_end = rec_end_exclusive;
262 if iov.len() >= MAX_IOV {
263 flush_iov(out, &iov)?;
264 iov.clear();
265 }
266 }
267
268 if chunk_start == 0 {
269 break;
270 }
271 chunk_start = chunk_start.saturating_sub(CHUNK);
272 }
273
274 if global_end > 0 {
276 iov.push(IoSlice::new(&data[0..global_end]));
277 }
278 flush_iov(out, &iov)?;
279 Ok(())
280}
281
282fn tac_string_before(
284 data: &[u8],
285 separator: &[u8],
286 sep_len: usize,
287 out: &mut impl Write,
288) -> io::Result<()> {
289 let mut iov: Vec<IoSlice> = Vec::with_capacity(MAX_IOV);
290 let mut global_end = data.len();
291 let mut chunk_start = data.len().saturating_sub(CHUNK);
292
293 loop {
294 let chunk_end = global_end.min(data.len());
295 let scan_start = chunk_start.saturating_sub(sep_len - 1);
296 if scan_start >= chunk_end {
297 if chunk_start == 0 {
298 break;
299 }
300 chunk_start = chunk_start.saturating_sub(CHUNK);
301 continue;
302 }
303 let scan_region = &data[scan_start..chunk_end];
304
305 let positions: Vec<usize> = memchr::memmem::find_iter(scan_region, separator)
306 .map(|p| p + scan_start)
307 .filter(|&p| p >= chunk_start || chunk_start == 0)
308 .filter(|&p| p < global_end)
309 .collect();
310
311 for &pos in positions.iter().rev() {
312 iov.push(IoSlice::new(&data[pos..global_end]));
313 global_end = pos;
314 if iov.len() >= MAX_IOV {
315 flush_iov(out, &iov)?;
316 iov.clear();
317 }
318 }
319
320 if chunk_start == 0 {
321 break;
322 }
323 chunk_start = chunk_start.saturating_sub(CHUNK);
324 }
325
326 if global_end > 0 {
328 iov.push(IoSlice::new(&data[0..global_end]));
329 }
330 flush_iov(out, &iov)?;
331 Ok(())
332}
333
334fn find_regex_matches_backward(data: &[u8], re: ®ex::bytes::Regex) -> Vec<(usize, usize)> {
336 let mut matches = Vec::new();
337 let mut past_end = data.len();
338
339 while past_end > 0 {
340 let buf = &data[..past_end];
341 let mut found = false;
342
343 let mut pos = past_end;
344 while pos > 0 {
345 pos -= 1;
346 if let Some(m) = re.find_at(buf, pos) {
347 if m.start() == pos {
348 matches.push((m.start(), m.end()));
349 past_end = m.start();
350 found = true;
351 break;
352 }
353 }
354 }
355
356 if !found {
357 break;
358 }
359 }
360
361 matches.reverse();
362 matches
363}
364
365pub fn tac_regex_separator(
367 data: &[u8],
368 pattern: &str,
369 before: bool,
370 out: &mut impl Write,
371) -> io::Result<()> {
372 if data.is_empty() {
373 return Ok(());
374 }
375
376 let re = match regex::bytes::Regex::new(pattern) {
377 Ok(r) => r,
378 Err(e) => {
379 return Err(io::Error::new(
380 io::ErrorKind::InvalidInput,
381 format!("invalid regex '{}': {}", pattern, e),
382 ));
383 }
384 };
385
386 let matches = find_regex_matches_backward(data, &re);
387
388 if matches.is_empty() {
389 out.write_all(data)?;
390 return Ok(());
391 }
392
393 let mut iov: Vec<IoSlice> = Vec::with_capacity(matches.len().min(MAX_IOV) + 2);
394
395 if !before {
396 let last_end = matches.last().unwrap().1;
397
398 if last_end < data.len() {
399 iov.push(IoSlice::new(&data[last_end..]));
400 }
401
402 let mut i = matches.len();
403 while i > 0 {
404 i -= 1;
405 let rec_start = if i == 0 { 0 } else { matches[i - 1].1 };
406 iov.push(IoSlice::new(&data[rec_start..matches[i].1]));
407 if iov.len() >= MAX_IOV {
408 flush_iov(out, &iov)?;
409 iov.clear();
410 }
411 }
412 } else {
413 let mut i = matches.len();
414 while i > 0 {
415 i -= 1;
416 let start = matches[i].0;
417 let end = if i + 1 < matches.len() {
418 matches[i + 1].0
419 } else {
420 data.len()
421 };
422 iov.push(IoSlice::new(&data[start..end]));
423 if iov.len() >= MAX_IOV {
424 flush_iov(out, &iov)?;
425 iov.clear();
426 }
427 }
428
429 if matches[0].0 > 0 {
430 iov.push(IoSlice::new(&data[..matches[0].0]));
431 }
432 }
433
434 flush_iov(out, &iov)?;
435 Ok(())
436}