407 lines
9.5 KiB
C
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;
|
|
}
|