The buffer-at-a-time fast path now serves the literal patterns that the
existing -l/-L/-q and binary tests used, leaving the line-at-a-time
engine's equivalents uncovered. Add bracket-class (non-literal) tests
for -l/-L/-q and binary handling (notice, -a text, without-match bail,
and the finalize-time notice), plus a fast-path test for a NUL that is
only discovered after a line was already printed.
No dead code was found: the remaining uncovered lines are writer I/O
error-propagation arms and pre-existing filesystem error handlers.
Literal searches were ~50-70x slower than GNU grep because every line
paid per-line costs (terminator scan, NUL scan, dispatch) even when a
buffer held no match. Add a buffer-at-a-time driver that scans whole
chunks with a substring searcher and only locates line boundaries
around the matches it finds; a chunk with no match costs a single
vectorized sweep and no per-line work.
The driver activates only for plain ASCII literal patterns (case
sensitive, no metacharacters) in the simpler output modes: -c, -l, -L,
-q, and plain line printing with -n/-b/filename/-m. Anything needing
match positions, context, inversion, color, or special binary handling
falls back to the unchanged line-at-a-time path. Output stays
byte-identical to that path, including binary/invalid-UTF-8 behavior.
- line_buffer: read_chunk() yields the largest span of complete lines.
- matcher: expose per-pattern memmem searchers when every pattern is a
plain literal (plain_literal()).
- searcher: eligible_for_fast_path(), fast_locate(), fast_print().
All scanning rides on the memchr crate (SIMD memchr/memrchr/memmem).
Unit tests for read_chunk and plain_literal; integration tests for
prefixes, -m, and multi-chunk line-number correctness.
Benchmarks (31 MB corpus) vs prior release:
-F (no match): 232ms -> 15ms (15.9x; now faster than GNU)
-c literal: 229ms -> 15ms (15.2x)
plain print: 248ms -> 18ms (13.5x)
Regex and -i paths are unchanged (still the line-at-a-time engine).
Cover the real-world grep usage shapes from the tldr page end-to-end
through uumain over a shared multi-MB corpus (plus a directory with a
binary file for -rI):
search pattern, -F fixed string, -rI recursive ignoring binary,
-C 3 context, -Hn --color=always, -o only-matching, -v invert,
-Ei extended + ignore-case.
Kept alongside the pure-scan throughput benches (literal vs regex, no
match). A rare marker keeps matched output small so the full-file scan
dominates the timing.
Replace the matcher micro-benchmarks with a single end-to-end 'search'
group driven through uumain over a multi-megabyte file: a literal
pattern (which a buffer-at-a-time searcher can accelerate) and an
extended-regex control (which cannot). Matching pre-split lines in
isolation cannot reveal how the searcher feeds data to the matcher;
this does.
The existing match/throughput benches call Matcher::match_line on
pre-split lines, so they only measure matching in isolation and cannot
observe how the searcher feeds data to the matcher. Add a 'search'
group that drives the whole pipeline through uumain over a multi-MB
file: a literal pattern (which a buffer-at-a-time searcher can speed
up) and an extended-regex control (which it cannot). Uses -q with a
non-matching pattern for a silent full-file scan.
GNU grep tolerates options given more than once (boolean flags are
idempotent, value options take the last occurrence), but clap rejected
them with "cannot be used multiple times" (exit 2). Set
args_override_self(true) so clap replaces rather than errors; args with
ArgAction::Append (-e/-f/--include/--exclude) still accumulate.
Found by the differential fuzzer (e.g. `grep -e .* -E -n -o -n -x`).
Add a regression test covering repeated booleans, repeated value options
(last wins), and that -e still accumulates patterns.