GNU grep's PCRE backend supports only a single pattern. Supplying
multiple patterns via repeated -e flags, or a pattern string that
contains a literal newline, must exit 2 with the message:
the -P option only supports a single pattern
Add the validation immediately after patterns are collected, before
regex-mode selection. Add a test covering:
- two separate -e flags with -P
- a newline-embedded pattern string with -P
- single -e with -P still works normally
Closes#34
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).
Add a cargo-fuzz harness (fuzz_grep) that runs uu_grep and GNU grep on
the same generated args/input and panics on any divergence, using the
vendored uufuzz crate (adapted from uutils/coreutils to depend on
crates.io uucore rather than a path).
A CI workflow (.github/workflows/fuzzing.yml) builds the uufuzz
examples, builds the fuzzer, and runs it for 60s. fuzz_grep is marked
should_pass: false / continue-on-error since it currently surfaces real
GNU-compatibility gaps (e.g. uu_grep rejects a repeated -m, GNU accepts).