A code duplication scanner for Zig code.
  • Zig 95.1%
  • Python 4.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Mikael Säker 6e088a93ef Hash structure carried by prefix tokens
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>
2026-07-25 12:53:21 +02:00
src Hash structure carried by prefix tokens 2026-07-25 12:53:21 +02:00
tools Retune sequence windows; delete redundant size bucketing 2026-07-25 11:39:36 +02:00
.gitignore Retune sequence windows; delete redundant size bucketing 2026-07-25 11:39:36 +02:00
build.zig Replace vendored corpus with a stdlib corpus step 2026-07-25 10:40:49 +02:00
CLAUDE.md Add simgrep: structural clone detector for Zig codebases 2025-12-25 11:26:28 +01:00
context.md Hash structure carried by prefix tokens 2026-07-25 12:53:21 +02:00
DESIGN.md Hash structure carried by prefix tokens 2026-07-25 12:53:21 +02:00
LICENSE Add MIT license 2026-07-25 12:00:19 +02:00
README.md Add README 2026-07-25 12:01:57 +02:00

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.