From 0f8550b517dac7896af254fb3c5d4590b89d86ab Mon Sep 17 00:00:00 2001 From: Matthieu Jolimaitre Date: Fri, 22 Aug 2025 12:29:55 +0200 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 14 +++ Cargo.toml | 2 + README.md | 35 ++++++ assets/screenshot.png | Bin 0 -> 79310 bytes mousquet/Cargo.toml | 6 ++ mousquet/examples/differents.rs | 10 ++ mousquet/examples/primes_1.py | 21 ++++ mousquet/examples/primes_1_ren.py | 24 +++++ mousquet/examples/primes_2.py | 24 +++++ mousquet/examples/renamed.rs | 10 ++ mousquet/examples/same.rs | 10 ++ mousquet/examples/small.py | 2 + mousquet/examples/tokenize.rs | 6 ++ mousquet/src/lang.rs | 39 +++++++ mousquet/src/lang/python.rs | 171 ++++++++++++++++++++++++++++++ mousquet/src/lcs.rs | 46 ++++++++ mousquet/src/lib.rs | 92 ++++++++++++++++ mousquetaire/Cargo.toml | 7 ++ mousquetaire/src/main.rs | 61 +++++++++++ rustfmt.toml | 3 + 21 files changed, 584 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 assets/screenshot.png create mode 100644 mousquet/Cargo.toml create mode 100644 mousquet/examples/differents.rs create mode 100644 mousquet/examples/primes_1.py create mode 100644 mousquet/examples/primes_1_ren.py create mode 100644 mousquet/examples/primes_2.py create mode 100644 mousquet/examples/renamed.rs create mode 100644 mousquet/examples/same.rs create mode 100644 mousquet/examples/small.py create mode 100644 mousquet/examples/tokenize.rs create mode 100644 mousquet/src/lang.rs create mode 100644 mousquet/src/lang/python.rs create mode 100644 mousquet/src/lcs.rs create mode 100644 mousquet/src/lib.rs create mode 100644 mousquetaire/Cargo.toml create mode 100644 mousquetaire/src/main.rs create mode 100644 rustfmt.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cdbcb7f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "mousquet" +version = "0.1.0" + +[[package]] +name = "mousquetaire" +version = "0.1.0" +dependencies = [ + "mousquet", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4dc58fa --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["mousquet", "mousquetaire"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..92547fe --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Mousquet + +Utility for making rough, euristic estimations of code similarity between +implementations. + +The similarity algorithm is based on token sequence matching, like some other +software serving the same purpose. This aproach has many limitations but may fit +some use cases. + +## Example + +```bash +$ mousquetaire 'examples/primes_1.py' 'examples/primes_2.py' +``` + +![screenshot](./assets/screenshot.png) + +## Build + +### Dependencies + +- cargo + - Install cargo through rustup + - `pacman -S rustup` + - `curl --proto '=https' --tlsv1.2 -sSf 'https://sh.rustup.rs' | sh` + - Use any toolchain + - `rustup default stable` + +## Building + +```bash +cargo build --release +``` + +Find the binary at `mousquetaire/target/release/mousquetaire`. diff --git a/assets/screenshot.png b/assets/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e286060cb4188fb68902a0a9340b7c538d0b94e5 GIT binary patch literal 79310 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 literal 0 HcmV?d00001 diff --git a/mousquet/Cargo.toml b/mousquet/Cargo.toml new file mode 100644 index 0000000..fb1cab6 --- /dev/null +++ b/mousquet/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "mousquet" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/mousquet/examples/differents.rs b/mousquet/examples/differents.rs new file mode 100644 index 0000000..0d0979f --- /dev/null +++ b/mousquet/examples/differents.rs @@ -0,0 +1,10 @@ +fn main() { + let source_a = include_str!("primes_1.py"); + let source_b = include_str!("primes_2.py"); + let sims = mousquet::similarity(mousquet::lang::PYTHON, source_a, source_b); + for sim in sims.token_matches { + let text_in_a = &source_a[sim.0.clone()].to_string().replace("\n", "\\n"); + let text_in_b = &source_b[sim.1.clone()].to_string().replace("\n", "\\n"); + println!("Found similarity {sim:?}\n\ta: '{text_in_a}'\n\tb: '{text_in_b}'"); + } +} diff --git a/mousquet/examples/primes_1.py b/mousquet/examples/primes_1.py new file mode 100644 index 0000000..ba4d934 --- /dev/null +++ b/mousquet/examples/primes_1.py @@ -0,0 +1,21 @@ + +# Generates the sequence of prime numbers up to a maximum number. +def primes(max = 999_999_999): + found = list[int]() # Known primes for subsequent dividability checks. + for value in range(2, max): + is_prime = True # Prime until proven otherwise. + for prime in found: + if value % prime == 0: + is_prime = False + break + if is_prime: + yield value + found.append(value) + + +def main(): + for p in primes(): + print(p) + + +if __name__ == "__main__": main() diff --git a/mousquet/examples/primes_1_ren.py b/mousquet/examples/primes_1_ren.py new file mode 100644 index 0000000..8bb8edf --- /dev/null +++ b/mousquet/examples/primes_1_ren.py @@ -0,0 +1,24 @@ +# original file ? + + +def get_pr(limit = 999_999_999): + result = list[int]() + + for num in range(2, limit): + valid = True + for known in result: + if num % known == 0: + valid = False + break + + if valid: + yield num + result.append(num) + + +def main(): + for num in get_pr(): + print(num) + + +if __name__ == "__main__": main() diff --git a/mousquet/examples/primes_2.py b/mousquet/examples/primes_2.py new file mode 100644 index 0000000..26c7286 --- /dev/null +++ b/mousquet/examples/primes_2.py @@ -0,0 +1,24 @@ + +from typing import Generator + + +def prime_numbers(max = 999_999_999): + def rec_between(value: int, at: int, until: int) -> bool: + if at >= until: return True + if value % at == 0: return False + return rec_between(value, at + 1, until) + def rec(value: int) -> Generator[int]: + if value >= max: return + if rec_between(value, 2, value): yield value + for r in rec(value + 1): yield r + for r in rec(2): yield r + + +def print_all(): + for p in prime_numbers(): + print(p) + + +if __name__ == "__main__": print_all() + +# author: mb diff --git a/mousquet/examples/renamed.rs b/mousquet/examples/renamed.rs new file mode 100644 index 0000000..a8f67c6 --- /dev/null +++ b/mousquet/examples/renamed.rs @@ -0,0 +1,10 @@ +fn main() { + let source_a = include_str!("primes_1.py"); + let source_b = include_str!("primes_1_ren.py"); + let sims = mousquet::similarity(mousquet::lang::PYTHON, source_a, source_b); + for sim in sims.token_matches { + let text_in_a = &source_a[sim.0.clone()].to_string().replace("\n", "\\n"); + let text_in_b = &source_b[sim.1.clone()].to_string().replace("\n", "\\n"); + println!("Found similarity {sim:?}\n\ta: '{text_in_a}'\n\tb: '{text_in_b}'"); + } +} diff --git a/mousquet/examples/same.rs b/mousquet/examples/same.rs new file mode 100644 index 0000000..4d73f11 --- /dev/null +++ b/mousquet/examples/same.rs @@ -0,0 +1,10 @@ +fn main() { + let source_a = include_str!("primes_1.py"); + let source_b = include_str!("primes_1.py"); + let sims = mousquet::similarity(mousquet::lang::PYTHON, source_a, source_b); + for sim in sims.token_matches { + let text_in_a = &source_a[sim.0.clone()].to_string().replace("\n", "\\n"); + let text_in_b = &source_b[sim.1.clone()].to_string().replace("\n", "\\n"); + println!("Found similarity {sim:?}\n\ta: '{text_in_a}'\n\tb: '{text_in_b}'"); + } +} diff --git a/mousquet/examples/small.py b/mousquet/examples/small.py new file mode 100644 index 0000000..cc2ff96 --- /dev/null +++ b/mousquet/examples/small.py @@ -0,0 +1,2 @@ +def hello(): + print("Hello World") diff --git a/mousquet/examples/tokenize.rs b/mousquet/examples/tokenize.rs new file mode 100644 index 0000000..fe5e830 --- /dev/null +++ b/mousquet/examples/tokenize.rs @@ -0,0 +1,6 @@ +fn main() { + let source = include_str!("small.py"); + let language = mousquet::lang::python::LANG; + let tokens = (language.tokenizer)(source); + dbg!(&tokens, tokens.len()); +} diff --git a/mousquet/src/lang.rs b/mousquet/src/lang.rs new file mode 100644 index 0000000..fe51458 --- /dev/null +++ b/mousquet/src/lang.rs @@ -0,0 +1,39 @@ +use std::{fmt::Debug, ops::Range}; + +pub mod python; + +pub const PYTHON: Lang = python::LANG; +pub const ALL: &[Lang] = &[PYTHON]; + +#[derive(Debug, Clone, Copy)] +pub struct Lang { + pub id: &'static str, + pub tokenizer: fn(&str) -> Vec>, + pub ignored_token: &'static [&'static str], + pub ignored_token_content: &'static [&'static str], +} + +pub type Span = Range; +pub type Located = (Span, T); + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Token { + pub kind: &'static str, + pub content: String, +} + +impl Token { + pub fn of_kind(kind: &'static str) -> impl Fn(&str) -> Token { + move |content| Token { + kind, + content: content.into(), + } + } +} + +impl Debug for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Token { kind, content } = self; + f.write_fmt(format_args!(r#"Token({kind},"{content}")"#)) + } +} diff --git a/mousquet/src/lang/python.rs b/mousquet/src/lang/python.rs new file mode 100644 index 0000000..f817b38 --- /dev/null +++ b/mousquet/src/lang/python.rs @@ -0,0 +1,171 @@ +use crate::lang::{Lang, Located, Token}; + +pub const LANG: Lang = Lang { + id: "python", + tokenizer, + ignored_token: &["sp"], + ignored_token_content: &["id"], +}; + +pub fn tokenizer(text: &str) -> Vec> { + Tokenizer(text, text.len()).collect() +} + +pub struct Tokenizer<'s>(&'s str, usize); + +impl<'s> Iterator for Tokenizer<'s> { + type Item = Located; + fn next(&mut self) -> Option { + if self.0.is_empty() { + return None; + } + self.skip_comment(); + let start = self.1 - self.0.len(); + let result = self + .parse_space() + .or_else(|| self.parse_op()) + .or_else(|| self.parse_str()) + .or_else(|| self.parse_ident()) + .or_else(|| self.parse_unknown()); + let end = self.1 - self.0.len(); + result.map(|r| ((start..end), r)) + } +} + +impl<'s> Tokenizer<'s> { + fn skip_comment(&mut self) { + while self.0.starts_with("#") { + let line_length = self.0.find("\n").unwrap_or(self.0.len()); + self.0 = &self.0[line_length..]; + } + } + + fn try_take(&mut self, word: &str) -> Option<&str> { + if self.0.strip_prefix(word).is_some() { + let (word, rest) = self.0.split_at(word.len()); + self.0 = rest; + Some(word) + } else { + None + } + } + + fn parse_space(&mut self) -> Option { + [" ", "\n", "\t", "\r"] + .iter() + .filter_map(|op| self.try_take(op).map(Token::of_kind("sp"))) + .next() + } + + fn parse_op(&mut self) -> Option { + OPERATORS + .iter() + .filter_map(|op| self.try_take(op).map(Token::of_kind("op"))) + .next() + } + + fn parse_str(&mut self) -> Option { + let (open, close) = STR_STARTS.iter().find(|(s, _)| self.0.starts_with(s))?; + let mut content_length = 0; + loop { + let prefix_length = open.len() + content_length; + let remainder = match self.0.get(prefix_length..) { + None => break, + Some("") => break, + Some(r) => r, + }; + if remainder.starts_with("\\") { + content_length += 2; + continue; + } + if remainder.starts_with(close) { + let length = open.len() + content_length + close.len(); + let content = &self.0[..length]; + return self.try_take(content).map(Token::of_kind("str")); + } + content_length += 1; + } + None + } + + fn parse_ident(&mut self) -> Option { + let forbidden = " \n\t\r!-@*/&%^+<=>|~()[]{}:;,."; + let length = self.0.chars().take_while(|c| !forbidden.contains(*c)).count(); + self.try_take(&self.0[..length]).map(|content| { + let kind = match KEYWORDS.contains(&content) { + true => "kw", + false => "id", + }; + Token::of_kind(kind)(content) + }) + } + + fn parse_unknown(&mut self) -> Option { + let next_break = self.0.find(' ').unwrap_or(self.0.len()); + let content = self.try_take(&self.0[..next_break]).unwrap(); + Some(Token::of_kind("unk")(content)) + } +} + +/// Ordered by size then alphabetically. +const OPERATORS: &[&str] = &[ + "**=", // + "//=", // + "<<=", // + ">>=", // + "-=", // + "!=", // + "[]", // + "@=", // + "**", // + "*=", // + "//", // + "/=", // + "&=", // + "%=", // + "^=", // + "+=", // + "<<", // + "<=", // + "==", // + ">=", // + ">>", // + "|=", // + "-", // + "@", // + "*", // + "/", // + "&", // + "%", // + "^", // + "+", // + "<", // + "=", // + ">", // + "|", // + "~", // + "(", // + ")", // + "[", // + "]", // + "{", // + "}", // + ":", // + ";", // + ",", // + ".", // +]; + +const KEYWORDS: &[&str] = &[ + "def", "and", "or", "not", "for", "while", "in", "try", "raise", "except", "yield", "return", "import", "from", + "as", +]; + +const STR_STARTS: &[(&str, &str)] = &[ + (r#"r""""#, r#"""""#), + (r#"b""""#, r#"""""#), + (r#"""""#, r#"""""#), + (r#"r""#, r#"""#), + (r#"b""#, r#"""#), + (r#"""#, r#"""#), +]; diff --git a/mousquet/src/lcs.rs b/mousquet/src/lcs.rs new file mode 100644 index 0000000..5d8d5bf --- /dev/null +++ b/mousquet/src/lcs.rs @@ -0,0 +1,46 @@ +use crate::lang::Span; + +pub fn longuest_common_section(a: &[T], b: &[T]) -> Option<(Span, Span)> { + let max_size = a.len().min(b.len()); + for size in (1..=max_size).rev() { + for a_start in 0..=(a.len() - size) { + let a_span = a_start..(a_start + size); + let a_section = &a[a_span.clone()]; + for b_start in 0..=(b.len() - size) { + let b_span = b_start..(b_start + size); + let b_section = &b[b_span.clone()]; + if a_section == b_section { + return Some((a_span, b_span)); + } + } + } + } + None +} + +#[test] +fn test_longuest_common_section() { + fn illustrate<'a>((a, b): (&'a [i32], &'a [i32]), (sa, sb): (Span, Span)) -> (&'a [i32], &'a [i32]) { + (&a[sa], &b[sb]) + } + + fn case(a: [i32; A], b: [i32; B], expected: [i32; E]) { + let res = longuest_common_section(&a, &b).unwrap(); + let ill = illustrate((&a, &b), res); + let exp: (&[i32], &[i32]) = (&expected, &expected); + assert_eq!(ill, exp); + } + + case( + /*****/ [1, 2, 3, 4, 5, 6, 7, 8, 9], + /**/ [8, 9, 2, 3, 4], + /********/ [2, 3, 4], + ); + + case( + // + [1, 2, 3, 4, 5, 6], + [1, 2, 3, 4, 5, 6], + [1, 2, 3, 4, 5, 6], + ); +} diff --git a/mousquet/src/lib.rs b/mousquet/src/lib.rs new file mode 100644 index 0000000..e92950b --- /dev/null +++ b/mousquet/src/lib.rs @@ -0,0 +1,92 @@ +use std::ops::Range; + +pub mod lang; +pub mod lcs; + +use crate::lang::{Lang, Located, Span, Token}; + +pub fn similarity(lang: Lang, source_a: &str, source_b: &str) -> Similarity { + let tokens_a = (lang.tokenizer)(source_a); + let tokens_b = (lang.tokenizer)(source_b); + + let exact_matches = Vec::new(); + // TODO + + let mut token_matches = Vec::new(); + { + let tokens_a = tokens_a.clone(); + let tokens_b = tokens_b.clone(); + let (tokens_a, comparables_a) = comparable_parts_of(&lang, &tokens_a); + let (tokens_b, comparables_b) = comparable_parts_of(&lang, &tokens_b); + let mut segments_a = vec![(tokens_a, comparables_a)]; + let mut segments_b = vec![(tokens_b, comparables_b)]; + + let length_threshold = 6; + while let Some(biggest_common_segment) = segments_a + .iter() + .enumerate() + .flat_map(|(segment_index_a, (_, segment_a))| { + segments_b + .iter() + .enumerate() + .filter_map(move |(segment_index_b, (_, segment_b))| { + let common = lcs::longuest_common_section(segment_a, segment_b)?; + Some(((segment_index_a, segment_index_b), common)) + }) + }) + .filter(|(_, (range_a, _))| range_a.len() > length_threshold) + .max_by_key(|(_, (range_a, _))| range_a.len()) + { + let ((segment_index_a, segment_index_b), (token_range_a, token_range_b)) = biggest_common_segment; + let segment_a = segments_a.remove(segment_index_a); + let segment_b = segments_b.remove(segment_index_b); + + let (tokens_l, tokens_a, tokens_r) = slice_range(segment_a.0, token_range_a.clone()); + let (compas_l, _comps_a, compas_r) = slice_range(segment_a.1, token_range_a); + segments_a.extend_from_slice(&[(tokens_l, compas_l), (tokens_r, compas_r)]); + + let (tokens_l, tokens_b, tokens_r) = slice_range(segment_b.0, token_range_b.clone()); + let (compas_l, _comps_b, compas_r) = slice_range(segment_b.1, token_range_b); + segments_b.extend_from_slice(&[(tokens_l, compas_l), (tokens_r, compas_r)]); + + let (first, last) = (tokens_a.first().unwrap(), tokens_a.last().unwrap()); + let character_span_a = first.0.start..last.0.end; + let (first, last) = (tokens_b.first().unwrap(), tokens_b.last().unwrap()); + let character_span_b = first.0.start..last.0.end; + token_matches.push(Match(character_span_a, character_span_b)); + } + } + + Similarity { + exact_matches, + token_matches, + } +} + +fn slice_range(mut items: Vec, range: Span) -> (Vec, Vec, Vec) { + let end = items.split_off(range.end); + let middle = items.split_off(range.start); + let start = items; + (start, middle, end) +} + +type TokenAndContent = (&'static str, Option); +fn comparable_parts_of(lang: &Lang, tokens: &[(Range, Token)]) -> (Vec>, Vec) { + tokens + .iter() + .filter_map(|token @ (_, Token { kind, content })| match kind { + k if lang.ignored_token.contains(k) => None, + k if lang.ignored_token_content.contains(k) => Some(((token.clone()), (*k, None))), + k => Some((token.clone(), (k, Some(content.clone())))), + }) + .collect() +} + +#[derive(Debug, Clone)] +pub struct Similarity { + pub exact_matches: Vec, + pub token_matches: Vec, +} + +#[derive(Debug, Clone)] +pub struct Match(pub Range, pub Range); diff --git a/mousquetaire/Cargo.toml b/mousquetaire/Cargo.toml new file mode 100644 index 0000000..bc6eb51 --- /dev/null +++ b/mousquetaire/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "mousquetaire" +version = "0.1.0" +edition = "2024" + +[dependencies] +mousquet = { path = "../mousquet" } diff --git a/mousquetaire/src/main.rs b/mousquetaire/src/main.rs new file mode 100644 index 0000000..84d2875 --- /dev/null +++ b/mousquetaire/src/main.rs @@ -0,0 +1,61 @@ +use std::{env::args, fs}; + +use mousquet::lang::Span; + +pub struct Args { + file_a: String, + file_b: String, +} + +impl Args { + pub fn parse() -> Self { + let [_, file_a, file_b] = args() + .collect::>() + .try_into() + .expect("Usage: mousquet "); + Self { file_a, file_b } + } +} + +fn main() { + let Args { file_a, file_b } = Args::parse(); + let source_a = fs::read_to_string(&file_a).unwrap(); + let source_b = fs::read_to_string(&file_b).unwrap(); + let similarities = mousquet::similarity(mousquet::lang::PYTHON, &source_a, &source_b); + + let mut similarities_in_a: Vec<_> = similarities.token_matches.iter().map(|s| s.0.clone()).collect(); + let mut similarities_in_b: Vec<_> = similarities.token_matches.iter().map(|s| s.1.clone()).collect(); + similarities_in_a.sort_by_key(|s| s.start); + similarities_in_b.sort_by_key(|s| s.start); + println!(); + print_file_with_similarities(file_a, source_a, similarities_in_a); + print_file_with_similarities(file_b, source_b, similarities_in_b); +} + +fn print_file_with_similarities(file_name: String, file_content: String, sorted_similarities: Vec) { + println!("┌────────────────────────────────────────"); + println!("│ File '{file_name}':"); + println!("├────────────────────────────────────────"); + print!("│"); + let mut prev_end = 0; + for sim in sorted_similarities { + let before = &file_content[prev_end..sim.start]; + let inside = &file_content[sim.start..sim.end]; + prev_end = sim.end; + print_formatted_text(before, "│ ", (BLUE, RESET)); + print_formatted_text(inside, "│ ", (YELLOW, RESET)); + } + print_formatted_text(&file_content[prev_end..], "│ ", (BLUE, RESET)); + println!(); + println!("└────────────────────────────────────────"); +} + +fn print_formatted_text(text: &str, prefix: &str, color: (&str, &str)) { + let (col_start, col_end) = color; + let prefixed = text.replace("\n", &format!("{col_end}\n{prefix}{col_start}")); + print!("{col_start}{prefixed}{col_end}"); +} + +const YELLOW: &str = "\x1b[0;33m"; +const BLUE: &str = "\x1b[0;34m"; +const RESET: &str = "\x1b[0m"; diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..6917979 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ + +max_width = 120 +hard_tabs = true