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).
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.