Files
hustoj/core/sim/sim_3_01/compare.c
2024-10-10 12:56:56 +08:00

407 lines
9.5 KiB
C

/* This file is part of the software similarity tester SIM.
Written by Dick Grune, Vrije Universiteit, Amsterdam.
$Id: compare.c,v 2.38 2016-08-07 15:24:17 dick Exp $
*/
#include "sim.h"
#include "text.h"
#include "token.h"
#include "tokenarray.h"
#include "hash.h"
#include "language.h"
#include "options.h"
#include "add_run.h"
#include "compare.h"
#include "debug.par"
/* The overall structure of the routine Compare_Files() is:
for all new files
for all texts it must be compared to
for all positions in the new file
for all positions in the text
for ever increasing sizes
try to match and keep the best
*/
/* LOCAL VARIABLES */
static size_t beginning_of_text;
static size_t beginning_of_old_text;
static size_t end_of_text;
/* RANGE HANDLING */
struct range {
size_t rg_start;
size_t rg_limit;
int rg_sticky;
};
static int
in_range(size_t i, const struct range *rg) {
if (rg->rg_start <= rg->rg_limit) {
/* one single range */
return (rg->rg_start <= i && i < rg->rg_limit);
} else {
/* looped-around range */
return (rg->rg_start <= i && i < end_of_text
|| beginning_of_text <= i && i < rg->rg_limit);
}
}
static int
is_empty_range(const struct range *rg) {
return rg->rg_start == rg->rg_limit;
}
/* COMPARE FILES */
static void compare_one_text(int n, struct range *rg);
static void compare_one_on_one(int n, int m, struct range *rg);
static size_t lcs(
struct text *txt0, size_t i0, struct range *rg,
struct text **tx_bp, size_t *i_bp
);
void
Compare_Files(void) {
int n;
for ( /* all new texts */
n = 0; n < Number_of_New_Texts; n++
) {
struct range range;
/* construct default range */
beginning_of_text = Text[0].tx_start;
beginning_of_old_text = Text[Number_of_New_Texts-1].tx_limit;
end_of_text = Text[Number_of_Texts-1].tx_limit;
range.rg_start = Text[n].tx_start + 1;
range.rg_limit = end_of_text;
range.rg_sticky = 0;
/* update range for options */
if (is_set_option('a')) {
/* all text */
range.rg_start = Text[n].tx_start + 1;
range.rg_limit = Text[n].tx_start;
range.rg_sticky = 1;
}
if (is_set_option('S')) {
/* old text only */
range.rg_start = beginning_of_old_text;
range.rg_limit = end_of_text;
range.rg_sticky = 0;
}
if (is_set_option('s')) {
if ( /* n in range */
range.rg_start == Text[n].tx_start + 1
) { /* take it out */
range.rg_start = Text[n].tx_limit;
range.rg_sticky = 0;
}
}
if (is_empty_range(&range)) continue;
/* compare the files */
if (is_set_option('e')) {
/* over the range in steps of one */
int m;
for (m = n; m < Number_of_Texts; m++) {
compare_one_on_one(n, m, &range);
}
for (m = 0; m < n; m++) {
compare_one_on_one(n, m, &range);
}
}
else {
/* the whole range in one action */
compare_one_text(n, &range);
}
}
}
static void
compare_one_on_one(
int n, /* index of text to be compared */
int m, /* index of text to be compared to */
struct range *rg /* pointer to search range */
) {
const struct text *txt1 = &Text[m];
if (!in_range(txt1->tx_start+1, rg)) return;
/* construct private range consisting of Text[m] */
struct range range_m;
range_m.rg_start = txt1->tx_start;
range_m.rg_limit = txt1->tx_limit;
range_m.rg_sticky = 0;
/* compare Text[n] and Text[m] */
compare_one_text(n, &range_m);
}
static void
compare_one_text(
int n, /* index of text to be compared */
struct range *rg /* pointer to search range */
) {
struct text *txt0 = &Text[n];
size_t i0 = txt0->tx_start;
#ifdef DB_COMP
fprintf(Debug_File, "compare_one_text(%s", txt0->tx_fname);
fprintf(Debug_File,
", i0 = %d, rg_start = %d, rg_limit = %d, sticky = %d)\n",
i0, rg->rg_start, rg->rg_limit, rg->rg_sticky);
#endif
while ( /* there is room for a run */
i0 + Min_Run_Size <= txt0->tx_limit
) {
if (!May_Be_Start_Of_Run(Token_Array[i0])) {
/* no point in looking; try the next token */
i0++;
} else {
/* see if there really is a run */
struct text *txt_run;
size_t i_run;
size_t run_size =
lcs(txt0, i0, rg, &txt_run, &i_run);
if (run_size) {
/* run found; enter it */
#ifdef DB_COMP
fprintf(Debug_File,
"add_run(%s, %d, %s, %d, %d)\n",
txt0->tx_fname, i0,
txt_run->tx_fname, i_run, run_size);
#endif
add_run(txt0, i0, txt_run, i_run, run_size);
/* and skip it */
i0 += run_size;
}
else {
/* we try our luck at the next token */
i0++;
}
}
if (rg->rg_sticky) {
/* drag rg->rg_start along */
rg->rg_start = i0 - 1;
}
}
}
#ifdef DB_COMP_2
static void
fprint_tokens(FILE *o, size_t p0, size_t p1) {
fprintf(o, "\n \"");
while (p0 < p1) {
fprintf(o, " %3d", Token_Array[p0]);
p0++;
}
fprintf(o, "\"");
}
#endif
static size_t
first_forward_ref_for(size_t i0, const struct range *rg) {
size_t res = Forward_Reference(i0, i0);
while (res && !in_range(res, rg)) {
res = Forward_Reference(res, i0);
}
return res;
}
static size_t
lcs( struct text *txt0, /* text to be compared */
size_t i0, /* starting pos. in txt0 */
struct range *rg, /* search range */
/* two output parameters, set if return value > 0: */
struct text **tx_bp, /* output, text of best run */
size_t *i_bp /* starting pos. in text of best run */
) {
/* Finds the longest common substring (not subsequence) in:
txt0, starting precisely at i0 and
all the text in the search range rg.
Writes the position in tx_bp and i_bp and returns the size.
Returns 0 if no common substring is found.
*/
size_t i1;
size_t size_best = 0;
if (!(txt0->tx_start <= i0 && i0 < txt0->tx_limit))
fatal("i0 not inside txt0");
#ifdef DB_COMP
fprintf(Debug_File,
"lcs(i0 = %d, rg_start = %d, rg_limit = %d), FWR[i0] = %d, ffr[i0] = %d\n",
i0, rg->rg_start, rg->rg_limit,
Forward_Reference(i0, i0),
first_forward_ref_for(i0, rg)
);
#endif
for ( i1 = first_forward_ref_for(i0, rg);
i1 && in_range(i1, rg);
i1 = Forward_Reference(i1, i0)
) {
/* i1 is always on the forward reference chain of i0 */
/* Find the text txt1 into which i1 points. */
#if 0
struct text *txt1 = txt0;
while (i1 < txt1->tx_start) {
txt1--;
}
while (i1 >= txt1->tx_limit) {
txt1++;
}
#else
struct text *txt1;
{ struct text *txt_b = &Text[0];
struct text *txt_e = &Text[Number_of_Texts-1];
while (txt_b != txt_e) {
struct text *txt_m = txt_b + (txt_e-txt_b) / 2;
if (i1 < txt_m->tx_limit) {
txt_e = txt_m;
} else {
txt_b = txt_m + 1;
}
}
txt1 = txt_b;
}
#endif
if (!(txt1->tx_start <= i1 && i1 < txt1->tx_limit))
fatal("i1 not inside txt1");
#ifdef DB_COMP
fprintf(Debug_File, "for i1: %s, i0=%d,%s, i1=%d\n",
txt0->tx_fname, i0, txt1->tx_fname, i1);
#endif
size_t better_size = (size_best ? size_best+1 : Min_Run_Size);
/* Are we looking at something better than we have got? */
{ /* we compare backwards from the end of
a putative better match starting at i1
*/
size_t j0 = i0 + better_size - 1;
size_t j1 = i1 + better_size - 1;
#ifdef DB_COMP
fprintf(Debug_File, "init: j0 = %d, j1 = %d\n", j0, j1);
#endif
/* would there be room for a better match? */
if ( /* j0 still inside txt0 */
j0 < txt0->tx_limit
&& /* j1 still inside txt1 */
j1 < txt1->tx_limit
&& /* better 0 and better 1 don't overlap */
(j0 < i1 || j1 < i0)
) {
/* yes, there is room enough for a match */
/* see if the text matches for at least
better_size tokens
*/
/* since we have perfect forward references and
chack backwards, we do not have to check the
last Min_Run_Size tokens:
*/
size_t cnt = better_size - Min_Run_Size;
#ifdef DB_COMP
fprintf(Debug_File,
"init verification: cnt = %d", cnt);
#ifdef DB_COMP_2
/* we don't want this all the time under
DB_COMP, but we want it linted
*/
fprint_tokens(Debug_File, i0, i0+better_size);
fprint_tokens(Debug_File, i1, i1+better_size);
#endif
fprintf(Debug_File, "\n");
#endif
while ( cnt
&& Token_EQ(Token_Array[j0],
Token_Array[j1])
) {
cnt--, j0--, j1--;
}
#ifdef DB_COMP
fprintf(Debug_File,
"end verification: cnt = %d\n", cnt);
#endif
if (cnt) {
/* not all tokens matched,
so forget it */
continue;
}
} else {
/* no, there is not enough room for a better
match, so forget it */
continue;
}
}
/* Yes, we are looking at a better match;
how long can we make it?
*/
size_t new_size = better_size;
{ /* extending forwards */
size_t j0 = i0 + better_size;
size_t j1 = i1 + better_size;
while ( /* j0 still inside txt0 */
j0 < txt0->tx_limit
&& /* j1 still inside txt1 */
j1 < txt1->tx_limit
&& /* j0 and j1 don't overlap */
(j0 < i1 || j1 < i0)
&& /* tokens are the same */
Token_EQ(Token_Array[j0], Token_Array[j1])
) {
j0++, j1++, new_size++;
}
}
#ifdef DB_COMP
fprintf(Debug_File,
"end forward extension: new_size = %d\n", new_size);
#endif
/* Offer the run to the Language Department which may
reject it or may cut its tail.
*/
new_size = Best_Run_Size(&Token_Array[i0], new_size);
if ( /* we still have an acceptable run */
new_size >= Min_Run_Size
&& /* it is still better than what we had */
new_size > size_best
) {
/* record it */
#ifdef DB_COMP
fprintf(Debug_File,
"possible run: %s, %d, %s, %d, %d\n",
txt0->tx_fname, i0,
txt1->tx_fname, i1, new_size);
#endif
*tx_bp = txt1;
*i_bp = i1;
size_best = new_size;
}
/* and see if it can be improved with a different i1 */
}
#ifdef DB_COMP
fprintf(Debug_File, "lcs out, size_best = %d\n",
size_best);
#endif
return size_best;
}