- Zig 95.1%
- Python 4.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Two gaps found by ranking the oracle's "loose" clusters, both cases where a construct's structure lives in a prefix token that neither the tag nor the children capture. Multiline string literals are leaves, so nothing carried their extent: a 35-line `asm` template hashed identically to a 2-line one. Now hashes the line count -- extent is size, not value, so literal contents stay ignored and rename-invariance is preserved. A test pins both directions: different lengths must not match, equal lengths with different text still must. `inline for` and `inline while` share a tag with their runtime forms, since `inline` is a prefix token rather than a node, so a comptime-unrolled loop hashed the same as a runtime one. This is what made two unrelated 118-line AES BlockVec implementations in the stdlib report as one clone -- they agree for 81 tokens and then diverge at exactly `inline for` vs `for`. Testing the loop case needed a direct nodeHash comparison: the loops' bodies are legitimately identical, so a cluster-level test cannot distinguish a loop match from a body match. stdlib exact 85.1% -> 86.1% loose 93 -> 86 clusters 2074 -> 2093 Duplicated-line count drops 1.8%, which is the false positives leaving. The second corpus is unchanged -- it contains neither construct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
| src | ||
| tools | ||
| .gitignore | ||
| build.zig | ||
| CLAUDE.md | ||
| context.md | ||
| DESIGN.md | ||
| LICENSE | ||
| README.md | ||
simgrep
A fast structural clone detector for Zig codebases. It finds duplicated shapes — code with the same AST structure but different identifiers and literals — which plain text search and line-diff tools miss.
How it works
File Walker ──▶ Zig Parser ──▶ Fingerprint ──▶ Report
(std.zig) (Merkle)
Each subtree is fingerprinted with a Merkle hash computed over its structure,
not its spelling, so fn addUser(a: u32) and fn addItem(b: u32) with the
same body collapse to the same fingerprint.
Build
zig build -Doptimize=ReleaseFast
# -> zig-out/bin/simgrep
Usage
simgrep [options] <path>
| option | meaning |
|---|---|
--min-lines=N |
minimum line span to consider (default: 5) |
--min-clones=M |
minimum occurrences to report (default: 2) |
--format=FORMAT |
text or json (default: text) |
--ignore=PATTERN |
skip matching paths, repeatable |
--show-source, -s |
show the code of each clone's first occurrence |
--no-fail |
always exit 0, even when clones are found |
--help, -h |
show help |
Ignore patterns: * does not cross /, ** does. A pattern without / also
matches any path component — --ignore=*_test.zig skips test files anywhere.
Exit status
| code | meaning |
|---|---|
0 |
no clones found |
2 |
clones found (suppress with --no-fail) |
The non-zero exit on findings is deliberate, so simgrep can gate a build.
Examples
# survey a source tree, ignoring tests
simgrep --ignore=*_test.zig src/
# only substantial, widespread duplication
simgrep --min-lines=20 --min-clones=3 src/
# machine-readable, never fail the build
simgrep --format=json --no-fail src/
Raising --min-lines is the main noise control: short spans repeat naturally
in any codebase, so the default of 5 is a floor rather than a recommendation
for large trees.
License
MIT — see LICENSE.