Code Similarity (csimx)
Code Similarity (csimx) provide a module designed to detect similarities between source code files, even when obfuscation techniques have been applied. It is particularly useful for programming instructors and students who need to verify code originality.
Origin. csimx started from csim 4.1.0 and keeps its structural comparison unchanged (same trees, same index). What it adds is a lexical stage that
groupcan use to skip pairs of files that are too different. The version history of csim before this fork is inCHANGELOG.md; the documents indocs/were written for csim and still call the project csim.
Key Features
- Source Code Similarity Analysis: Compares source code files to determine their degree of similarity.
- Pairwise Reporting: Generate detailed similarity reports for all file pairs.
- File Grouping: Cluster similar files into groups based on a configurable threshold.
- Flexible Search Strategies:
- Exhaustive Search: All-pairs comparison for maximum precision
- Two Stages: A fast lexical stage (Pygments tokens + Myers diff) can filter the pairs that
groupsends to the structural stage (parse trees + tree edit distance). - Advanced Analysis: Utilizes parse trees and the tree edit distance algorithm for in-depth analysis.
- Parse Trees: Represents the syntactic structure of source code, enabling detailed comparisons.
- Tree Edit Distance: Measures the similarity between different code structures.
- Hash-Based Pruning: Optimizes the comparison process by reducing tree size while preserving essential structure.
- Multi-Language Support: Supports Python 3.13, Python 3 (universal grammar), Java 20, Java 24, C++14, Kotlin (experimental), and C (experimental) source code analysis.
Technologies Used
- Python: The core programming language for the tool.
- ANTLR: A parser generator for creating parse trees from source code.
- apted: A library for computing the tree edit distance (default algorithm).
- zss: A library for calculating the tree edit distance, alternatively to apted.
- NumPy: Used for efficient numerical operations.
- Pygments: Tokenizer of the lexical stage (one for every supported language).
Installation
For the installation pip is required, you can either clone the repository and install it locally or install it directly from PyPI.
- Clone the repository:
git clone https://github.com/EdsonEddy/csimx.git
- Navigate to the project directory:
cd csimx
- Install the package:
pip install .
Alternatively, you can install it directly from PyPI:
pip install csimx
Version Compatibility
- Python: 3.10–3.12 (recommended 3.11)
- ANTLR4 Python Runtime: 4.13.2
- zss: 1.2.0
- apted: 1.0.3
- numpy: 1.26.4
Quick Start
New to csimx? Start here: GETTING_STARTED.md
For detailed information about search strategies, see: docs/STRATEGIES.md
csimx supports three main actions: report (for pairwise similarity analysis), group (for clustering similar files), and tree/view (for visualizing a file's normalized/pruned parse tree). The tool supports Python 3.13, Python 3, Java 20, Java 24, C++14, Kotlin (experimental), and C (experimental) source code files.
General Command Structure
csimx <action> --path <directory> [options]
Action 1: report - Generate Similarity Report
Generates a pairwise similarity report comparing all files in a directory.
csimx report --path /path/to/directory
Example Output:
file1.py is similar to file2.py with similarity index: 0.95
file1.py is similar to file3.py with similarity index: 0.45
file2.py is similar to file3.py with similarity index: 0.50
Options:
--lang, -l: Programming language (default:python_3_13). Options:python_3_13,python_3,java_20,java_24,cpp_14,kotlin,c--talg, -ta: Tree edit distance algorithm (default:apted). Options:zss,apted--index, -ix: Similarity index formula (default:legacy). Options:legacy,ratio,metric
Example with options:
csimx report --path /path/to/directory --lang java_20 --talg zss --index ratio
Action 2: group - Group Files by Similarity
Groups files by similarity using a specified threshold and strategy.
csimx group --path /path/to/directory --threshold 0.8
Example Output:
Threshold: 0.8
Total files processed: 4
Group 1 (Average Similarity: 0.98):
./file1.py
./file2.py
Group 2 (Average Similarity: 0.95):
./file3.py
./file4.py
Strategy Options
The group action supports two strategies for finding similar files:
1. exhaustive (Default)
Compares every file against every other file (O(n²)). This is the most thorough approach but slower for large datasets.
csimx group --path /path/to/directory --threshold 0.8 --strategy exhaustive
When to use each:
- exhaustive: Small datasets (< 100 files), when maximum precision is critical
Group Action Options
--threshold, -t: Similarity threshold (0.0 to 1.0). Required.--strategy, -s: Grouping strategy (default:exhaustive). Options:exhaustive--lang, -l: Programming language (default:python_3_13). Options:python_3_13,python_3,java_20,java_24,cpp_14,kotlin,c--talg, -ta: Tree edit distance algorithm (default:apted). Options:zss,apted--index, -ix: Similarity index formula (default:legacy). Options:legacy,ratio,metric--prefilter: Compare the tokens of each pair first and run the structural comparison only on the pairs whose lexical index reaches--thresholdminus 0.05 (see below). Off by default.--prefilter-margin X: Same, with marginX(0.0 to 0.30); it turns the prefilter on by itself.
Complete example:
csimx group --path /path/to/directory --threshold 0.9 --strategy exhaustive --lang python_3_13 --talg zss
Lexical prefilter (group only)
csimx has two stages: the structural one (parse tree, tree edit distance) gives the index, and a lexical one compares the token sequences of two files with the Myers diff algorithm (1 - d / (len_a + len_b), comments and layout dropped, names, numbers and strings generalized to their type). With --prefilter the lexical stage goes first and the structural one only runs on the pairs that are not too different in tokens, so the time of a big directory drops by a few times; files that take part in no such pair are not even parsed.
csimx group --path /path/to/directory --threshold 0.7 --lang python_3 --prefilter
csimx group --path /path/to/directory --threshold 0.7 --lang python_3 --prefilter-margin 0.1
The filter is lossy: a pair with the same structure and very different tokens (a moved block, reordered statements) can be skipped even though the structural stage would group it. The margin is how far under --threshold the lexical index may be; a larger margin loses fewer pairs and skips fewer. It pays off from a threshold of about 0.6 up. See the 0.1.0 entry of CHANGELOG.md for the numbers. report has no prefilter.
Similarity Index Formulas
--index chooses how the tree edit distance d is normalized into the
similarity index. With m = max(n1, n2) and s = n1 + n2:
| Value | Formula | Notes |
|---|---|---|
legacy (default) |
1 - d / m |
The index of csim <= 3.4.2. |
ratio |
m / (m + d) |
Always in (0, 1]. Ranks pairs exactly as legacy does, on a different scale. |
metric |
(s - d) / (s + d) |
Metric normalization of tree edit distance (Li & Zhang); satisfies the triangle inequality. Ranks by total size instead of by the larger tree. |
Thresholds do not carry over between formulas. ratio is a monotone
rescaling of legacy, so it groups files in exactly the same order, but on a
different scale. To reproduce a legacy threshold under ratio, use
t_ratio = 1 / (2 - t_legacy):
legacy |
ratio |
|---|---|
| 0.70 | 0.769 |
| 0.80 | 0.833 |
| 0.90 | 0.909 |
Because ratio never reaches 0 in practice (a pair with nothing in common
sits near 0.5, where legacy puts it near 0), a threshold taken straight from
the legacy scale will be far more lenient than intended. legacy is the
default so that existing thresholds keep their meaning.
Action 3: tree (alias: view) - Visualize Parse Trees
Prints the normalized/pruned tree for a single file — the exact tree that gets passed to the tree edit distance algorithm. Useful for debugging how the normalization, collapsing, and hashing rules affect a specific file before it's compared against others.
csimx tree --path /path/to/file.py --lang python_3_13
Example Output:
=== Normalized + Pruned Tree (input to Tree Edit Distance) ===
statements
function_def_raw
param [hashed:e3b0c442]
statements
STRING
if_stmt
comparison [hashed:93e10dca]
return_stmt [hashed:337adaa9]
assignment [hashed:118045cc]
primary [hashed:e1b0c7ab]
Total nodes after pruning: 24
Rule and token names are resolved for readability, LOOP marks nodes collapsed under control-flow equivalence (e.g. for/while), and [hashed:xxxxxxxx] marks subtrees that were hashed into a single node instead of compared structurally.
Options:
--path, -p: Path to a single source code file (required).--lang, -l: Programming language (default:python_3_13). Options:python_3_13,python_3,java_20,java_24,cpp_14,kotlin,c--show-raw: Also print the raw ANTLR parse tree before normalization/pruning, for side-by-side comparison.
Example with --show-raw:
csimx tree --path /path/to/file.py --lang python_3_13 --show-raw
Language Support
The tool supports the following programming languages:
Python 3.13:
csimx report --path /path/to/python/files --lang python_3_13
Python 3 (universal grammar):
csimx report --path /path/to/python/files --lang python_3
Same .py files as python_3_13, parsed with grammars-v4's "universal Python 2/3"
grammar, which publishes a C++ target -- giving a large speedup once the native
parser is built (see Native Parsers below). Grouping output is
byte-identical to python_3_13 on most real-world code (verified against
hundreds of real judge submissions); a narrow, understood exception remains for
files with very few top-level statements, where tree-size differences can
slightly overstate similarity — see csimx/python_3/utils.py for the full
writeup. Does not parse positional-only parameters (/, PEP 570), the walrus
operator (:=, PEP 572), or match/case (PEP 634); csimx falls back to
python_3_13 automatically for files using those, so results stay correct,
just slower for that subset.
Java 20:
csimx report --path /path/to/java/files --lang java_20
Java 24 (experimental):
csimx report --path /path/to/java/files --lang java_24
Java 24 uses an optimized grammar (grammars-v4/java/java) that parses much faster
than java_20 when the native parser is available (see Native Parsers
below), and supports the same modern Java syntax (records, sealed classes, pattern
matching, switch expressions, text blocks). However, its similarity/grouping
output is not yet tuned to match java_20 and can be significantly less accurate
(verified on real submissions — see CHANGELOG.md). Use java_20 for group/report
until this is resolved; java_24 is available for parse-speed experimentation only.
C++14:
csimx report --path /path/to/cpp/files --lang cpp_14
Kotlin (experimental):
csimx report --path /path/to/kotlin/files --lang kotlin
First-cut integration (grammars-v4/kotlin/kotlin) with a working native parser
(no C++ base class needed at all -- this grammar declares no superClass).
Unlike java_24/python_3, there is no real-world Kotlin corpus in this
project's benchmark set to tune or validate grouping precision against yet, so
the normalization rules (csimx/kotlin/utils.py) follow the same categories
already validated for other languages (structural punctuation, identifier
text, import/package plumbing, body-wrapping content) but haven't been
corpus-measured for false-positive/false-negative rates. Treat group/report
results as a reasonable starting point, not a tuned config, until a real
corpus drives the next pass.
C (experimental):
csimx report --path /path/to/c/files --lang c
First-cut integration (grammars-v4/c, ISO C23 + GNU/MSVC extensions), with a
working native parser backed by a real symbol-table implementation for
typedef disambiguation. Like Kotlin, there's no real-world C corpus in this
project's benchmark set to tune grouping precision against yet -- same
caveats apply, see csimx/c/utils.py.
Runs with preprocessing disabled (--nopp) always: a real preprocessor can't
be assumed present in a production container, and judge submissions have no
consistent include paths anyway. #include/#define/etc. lines are
swallowed as hidden tokens rather than expanded, which means macro-dependent
code (token-pasting tricks, macros used for control flow) can fail to parse
or parse differently than a real compiler would see it -- real submissions
essentially never rely on that, but it's a known, real limitation.
Native Parsers
For java_20, java_24, cpp_14, python_3, kotlin, and c, csimx can
use a compiled C++ ANTLR parser instead of the pure-Python one, giving a
large speedup with identical (or, for java_24, not-yet-identical -- see
above) output. python_3_13 always uses the pure-Python parser (no C++
target is available for that grammar).
Check which backend is active for each language:
csimx info
If a native library isn't present for a language, csimx falls back to the pure-Python parser automatically — results are unaffected, only speed. Build the native parsers from source with:
scripts/build_native_parsers.sh
Set CSIM_DISABLE_NATIVE=1 to force the pure-Python parsers for every
language, e.g. for debugging or benchmarking.
Threshold Guidance
The similarity threshold represents the structural similarity of the code (based on the Abstract Syntax Tree). Choose appropriate thresholds based on your use case:
- 0.95+: Nearly identical code (likely plagiarism)
- 0.85-0.95: Very similar code (probable plagiarism)
- 0.70-0.85: Moderately similar code (review recommended)
- <0.70: Low similarity (likely independent work)
Using csimx as a Python Module
You can also use csimx programmatically within your Python code. The library provides low-level functions for advanced use cases:
from csimx.utils import group_by_exhaustive_search, report_pairwise_similarity
# Example: Group files by similarity
file_names = ["file1.py", "file2.py", "file3.py"]
file_contents = [code1, code2, code3]
results = group_by_exhaustive_search(
file_names=file_names,
file_contents=file_contents,
lang="python_3_13",
threshold=0.8,
ted_algorithm="apted"
)
print(results)
Or use the legacy Compare class for simple pairwise comparisons:
from csimx import Compare
code_a = "a = 5"
code_b = "c = 50"
similarity = Compare(name_a='example A', content_a=code_a, name_b='example B', content_b=code_b)
print(f"Similarity: {similarity}") # Output: Similarity: X.XX
To see how much a program shrinks when it is normalized, pruned and hashed, count its nodes before and after:
from csimx import count_nodes
nodes_before, nodes_after = count_nodes("example.py", code, lang="python_3_13")
nodes_before is every node of the raw ANTLR parse tree; nodes_after is the size of the tree handed to the tree edit distance (the same number csimx tree prints as "Total nodes after pruning").
Documentation
- Getting Started Guide - Quick tutorial for new users
- Search Strategies Guide - Detailed explanation of available search strategies
- ANTLR Parser Generation - For grammar customization
ANTLR4 Installation and Parser/Lexer Generation
This installation is not required—the generated files are already included in the project. If you'd like to review the steps to generate them yourself, see grammars/parser_gen_guide.md.
Note: The included generated files were produced by ANTLR 4.13.2 and are compatible with the pinned runtime listed above.
Contributing
Contributions are welcome! To contribute, please follow these steps:
- Fork the repository.
- Create a new branch (
git checkout -b feature/new-feature). - Make your changes and commit them (
git commit -am 'Add new feature'). - Push to the branch (
git push origin feature/new-feature). - Open a Pull Request.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Support
- Questions? Open a GitHub Discussion
- Found a bug? File a GitHub Issue
- Want to contribute? See Contributing section
References
For more information on the techniques and tools used in this project, refer to the following resources:
- ANTLR
- Parse Tree (Wikipedia)
- Tree Edit Distance (Wikipedia)
- Locality Sensitive Hashing (Wikipedia)
- MinHash (Wikipedia)
- zss (PyPI)
- Hashing (Python Docs)
- apted (GitHub)
Third-Party Licenses
This project utilizes the following third-party libraries:
ANTLR (ANother Tool for Language Recognition)
- Purpose: A parser generator used to create parse trees from source code.
- License: BSD 3-Clause
- Website: https://www.antlr.org/
- Repository: https://github.com/antlr/antlr4
ANTLR4-parser-for-Python-3.14 by RobEin
- Purpose: Python 3.14 grammar for ANTLR4
- License: MIT License
- Repository: https://github.com/RobEin/ANTLR4-parser-for-Python-3.14
zss (Zhang-Shasha)
- Purpose: Tree edit distance algorithm implementation for comparing tree structures
- License: MIT License
- Repository: https://github.com/timtadh/zhang-shasha
apted (All Path Tree Edit Distance)
- Purpose: Python APTED algorithm for the Tree Edit Distance, an alternative to zss
- License: MIT License
- Repository: https://github.com/JoaoFelipe/apted
Metadata
Release files for csimx 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| csimx-0.2.1.tar.gz | 705.4 kB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| csimx-0.2.1-py3-none-manylinux_2_28_x86_64.whl | Python 3 | none | Linux glibc 2.28+ x86-64 | Details |
| csimx-0.2.1-py3-none-macosx_14_0_arm64.whl | Python 3 | none | macOS 14.0+ ARM64 | Details |
Total release size: 12.2 MB
Release files / csimx-0.2.1.tar.gz
| Download URL | csimx-0.2.1.tar.gz |
|---|---|
| Size | 705.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c831da2ce4acfb4e32946e063a7b6219bbd2ba66090ac1634a1355baac7c8ac1
|
|
BLAKE2b-256 checksum How to use checksums |
4adc05daa726c8252be7592901f2a62d5862312e80a4c41d713e1321f892dff1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / csimx-0.2.1-py3-none-manylinux_2_28_x86_64.whl
| Download URL | csimx-0.2.1-py3-none-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 8.3 MB |
| Tags | Linux glibc 2.28+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
d7737dbf14147671bd24a543231eb5a48e7af5b9eb1990ff4a3ca32bd79df34a
|
|
BLAKE2b-256 checksum How to use checksums |
e65ca9d2f4f983b5066aa159b53a03574b013dc72b2eb10d92d156c5620a00e6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / csimx-0.2.1-py3-none-macosx_14_0_arm64.whl
| Download URL | csimx-0.2.1-py3-none-macosx_14_0_arm64.whl |
|---|---|
| Size | 3.2 MB |
| Tags | Python 3 macOS 14.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
b47eb009a21703fab77c43ceb45cb9ce5b8cb6f427be54fdd7860b1c1fe45b08
|
|
BLAKE2b-256 checksum How to use checksums |
730b9fd1bfec820b9aad8c66442d9a7029302acd88d82d3eb9dcbd0b2870bf5e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency log