scent Public
Code
Use Git or checkout with SVN using the web URL.
No matching headings.
scent
A byte-oriented grep in wolf,
compared with GNU grep 3.12 configured --disable-perl-regexp.
| Platform | Coverage |
|---|---|
| Linux x86-64 | Ubuntu 24.04 oracle; native, release and deterministic static builds |
| macOS arm64 | Builds and units; non-gating Homebrew grep field comparison |
| PAX | Static builder and locale-free acceptance; OS integration is next |
Status
C-locale basic regular expressions (the default, -G), extended regular
expressions (-E) and fixed strings (-F) are connected to the full M1
selection/output interface. This includes backreferences, classes, GNU
assertions, intervals, case folding, word/whole-line matching, context,
prefixes, spans, counts, file lists, binary policies and bounded streaming.
-P gives the configured GNU build's refusal. Recursion and terminal colour
are M2; UTF-8 locale semantics are M3. C-locale -i folds ASCII only.
The generated CLI matrix has 5,072 cases: 40 option rows × three syntaxes × seven pattern families × six input forms, plus 32 source and diagnostic cases. Separate CLI rows cover the original 958-case corpus, 150 reviewed-family cases, 18 wrapper cases and nine context/span joins. The engine also gates 20,000 generated pattern–line pairs per syntax, exhaustive short malformed patterns, and bounded regular/backreference costs.
The exact reviewed cases below are not counted as agreement. Eight runtime gaps are separately marked upstream-blocked; only 12 recursion/colour cases remain not-yet. Local native, release, static and locale-free runs each report 6,078 agree + 109 published differences + 8 upstream-blocked + 12 M2 not-yet, with zero unapproved differences (6,207 cases total). All input and pattern-file bytes remain arbitrary bytes.
Install and build
No release is published yet. packaging/scent.rb and packaging/PKGBUILD
are drafts for a future v0.1.0 source tag: their release digest must be
filled before use. They build with the pinned compiler, not a moving wolf
package, and do not replace the system grep. Optional compatibility names
are installed in a separate directory.
tools/fetch-toolchain
tools/build --all
tools/check-test
The pin is wolf 0.2.35, paired lupin 0.1.56, and bundled wolf-std
2386522eb059cca5a84108562c2c0a0e72345d34. Archives are checked by digest;
complete runtime/compiler pairs stay together. Build on a compatible host,
not with an arbitrary compiler from PATH.
wrappers/egrep and wrappers/fgrep are shell scripts requiring /bin/sh.
Place them beside a grep link to scent. They emit GNU 3.12's measured
obsolescence warning on stderr, even with --help, and prepend -E/-F.
A later conflicting syntax flag is an error, not an override.
Acceptance
tools/in-container tools/gate
tools/pax-posture
The oracle is a signature-verified release-tarball build in pinned Ubuntu
24.04/glibc 2.39, never the host's distribution grep. tools/oracle --only NAME selects exactly one witness; explicit glob selectors are supported.
--field reports distribution differences without gating. cmp compares
stdout, stderr, exit status and remaining stdin. Help/version are scent's
own prose; those two actions compare stream shape and status, with exact
stderr (including wrapper warnings).
Every reviewed difference in tests/divergences.json
locks the complete case identity and both result tuples. A new input,
changed output, or vanished difference fails the gate. The only dynamic
field is the numeric runtime PID in the two upstream-blocked panic rows.
tools/oracle-review-test rejects nondigits there and changes to every
other byte. No other output is normalized.
tools/mkscent OUTDIR builds the static ELF twice and prints equal digests,
size and stripped size. tools/pax-posture runs the whole byte suite in a
plain Ubuntu container without locale data or LANG; Python is only the
test coordinator. The Linux PR gate includes both compiler tiers, units,
all CLI cases, engine costs, deterministic static builds, locale-free
acceptance and the 2 GiB streaming-memory gate (below 32 MiB peak RSS).
tools/measure records three-run medians on 256 MiB for -F, -G and -E;
speed is recorded, not gated, until M4. Full CI adds the macOS field leg.
Published differences
These are deliberate consistency choices, approved as rulings #115–#117.The witness bytes use \0 for NUL; ordinary output ends in newline.
The complete individual cases and exact answers are in the manifest above.
| Rule | Minimal witness | GNU 3.12 | scent | Why |
|---|---|---|---|---|
| #115: whole-word, leftmost-longest | -E -a -o -b -w '.{1,2}', four spaces then ab |
0: \n4:ab\n |
0: \n2: \n4:ab\n |
Keep the valid shorter whole-word span GNU's retry omits. BRE is equivalent. |
#116: ** means * |
-E with the double-star pattern below, on a |
No output, status 1 | a\n, status 0 |
Replacing either ** with * restores GNU's empty-capture path; POSIX leaves adjacent duplication undefined. |
| #117: never cross records | Pattern-file bytes x\0y\n, -F -z, input x\0y\0 |
x\0y\0, status 0 when in one read |
No output, status 1 | GNU -G/-E, -F -w and cross-read -F already reject it. Input record boundaries must not depend on read placement. |
The double-star command is literal (the alternation bar is not escaped):
printf 'a\n' | grep -E '(A**|.)(a)\1**'
The CLI family rows are 94 agree + 56 reviewed (22 word-span and
34 double-star differences). The 216 NUL-pattern rows are 164 agree +
52 reviewed; the original two additional NUL witnesses add one agreement and one
reviewed difference under the same rule. The
98,304-byte read-boundary witness is checked as agreement, not waived.
A short double-star bug report is drafted in
notes/ct02-evidence/gnu-double-star-report.txt;
it has not been sent.
Blocked upstream, retires at the pin that ships env_arg0 / env_args_bytes
These eight are scent/runtime gaps, not GNU quirks. Each row is executed
and checked as an exact expected difference. Let D(name) be
name: unrecognized option '--bad'\n, and U(name) be
Usage: name [OPTION]... PATTERNS [FILE]...\nTry 'name --help' for more information.\n.
The invocation rows all use --bad, empty stdin, no stdout and status 2.
| Exact case name | GNU stderr | scent stderr | Upstream |
|---|---|---|---|
not-yet-argv0-diagnostic-0 |
D(/abs/path/grep) + U(grep) |
D(grep) + U(grep) |
wolf-lang#899, #904 |
not-yet-argv0-diagnostic-1 |
D(./grep) + U(grep) |
D(grep) + U(grep) |
wolf-lang#899, #904 |
not-yet-argv0-diagnostic-2 |
D(hound) + U(hound) |
D(grep) + U(grep) |
wolf-lang#899, #904 |
not-yet-argv0-usage-0 |
D(/abs/path/grep) + U(grep) |
D(grep) + U(grep) |
wolf-lang#899, #904 |
not-yet-argv0-usage-1 |
D(./grep) + U(grep) |
D(grep) + U(grep) |
wolf-lang#899, #904 |
not-yet-argv0-usage-2 |
D(hound) + U(hound) |
D(grep) + U(grep) |
wolf-lang#899, #904 |
not-yet-argv-bytes-0: -F, byte FF pattern, empty stdin |
No output/status 1 | Runtime panic below, abort | wolf-lang#406, #904 |
not-yet-argv-bytes-1: -F x, byte FF filename |
grep: <FF>: No such file or directory\n, status 2 |
Runtime panic below, abort | wolf-lang#406, #904 |
The two byte-argument rows have no stdout. Their full stderr is fixed in
the manifest, with just {pid} typed as one or more ASCII digits; status
is abort (-6 in the subprocess harness):
thread '<unnamed>' ({pid}) panicked at /rustc/8bab26f4f68e0e26f0bb7960be334d5b520ea452/library/std/src/env.rs:876:51:
called `Result::unwrap()` on an `Err` value: "\xFF"
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fatal runtime error: failed to initiate panic, error 5, aborting
Until those builtins ship, ordinary grep file/regex diagnostics already
say grep; full-path invalid-option prefixes and alternate invocation
names differ. /bin/grep can be a symlink to scent without a private
name-override option. Raw invalid-UTF-8 argv can abort; use pattern files
for arbitrary pattern bytes. Retirement requires replaying these witnesses
at the new pin, not deleting them without a check.
Design and sources
docs/MAP.md describes the matcher seam and bounded buffers; docs/RE.md describes the immutable regex and reusable workspace. Input is never a whole-file string. Per-chunk regions release temporary reads/output; only unfinished lines and requested context survive.
Sources are POSIX.1-2024, GNU grep documentation and black-box runs, plus cited design references from the project's own ferp and yew engines. GNU grep, gnulib, glibc regex, busybox and PCRE2 implementation sources are never read. GNU is the behavioral oracle except for the explicit, individually checked consistency decisions above.
Licence
GPL-3.0-or-later (LICENSE), with the wolf Training Data Permission
(LICENSE-TRAINING-DATA).