1 /* $Cambridge: exim/test/src/cf.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
3 /************************************************
5 ************************************************/
7 /* A program to compare two files line by line.
11 It was originally written in C, but the C under
12 Panos is still a shambles (1986). Translated therefore
13 to BCPL -- this explains some of the odd style.
15 Modified to run on Archimedes, August 1987.
16 Modified to run under MSDOS, March 1989.
17 Modified to run under CINTERP interpreter, July 1989.
18 Modified to run under Unix, October 1989.
20 Translated back into C, March 1990! */
22 /* Copyright (c) 1986, 1987, 1989, 1990, 1994, 2001 by Philip Hazel */
24 /* Previously modified: October 1994*/
25 /* Last modified: September 2001 - a long-lived bug fixed! */
36 /* ----- parameters ----- */
39 #define defaultstore 100000 /* default recovery buffer size */
40 #define minstore 500 /* minimum recovery buffer size */
42 /* ----- misc defines ----- */
53 #define EqString(s, t) (strcmp(s, t) == 0)
55 /* ----- line structure ----- */
64 /* ----- global variables ----- */
66 FILE *f_one; /* files */
70 int lines_one = 0; /* line counts */
73 int eof_one = FALSE; /* eof flags */
75 int exact = FALSE; /* TRUE => no strip spaces */
76 int echo = TRUE; /* TRUE => show mismatched lines */
77 int sync_count = 3; /* resync count */
78 int storesize = defaultstore; /* size of each buffer */
80 char *name_one = NULL; /* file names */
81 char *name_two = NULL;
84 char *bufbase_one; /* start buffer */
86 char *bufnext_one; /* next free byte */
88 char *buftop_one; /* end buffer */
91 line *rootline_one; /* mis-match point */
93 line *lastline_one; /* last in store */
95 line *pline_one; /* working line */
99 /*************************************************
101 *************************************************/
105 printf("PH's CMP v%d\n", version);
106 printf("Keywords:\n");
107 printf(" <file> ) files to compare\n");
108 printf(" <file> ) no keywords used\n");
109 printf("-to <file> output destination\n");
110 printf("-exact include trailing spaces & match tabs\n");
111 printf("-noecho don't echo differences (just give line numbers)\n");
112 printf("-s, -sync <n> set re-sync count, default 3\n");
113 printf("-buffer <n> buffer size (for each file) default 100000\n");
114 printf("-id give program version id\n");
115 printf("-h, -help give this help\n");
116 printf("\nExamples:\n");
117 printf("cmp old.f77 new.f77\n");
118 printf("cmp first second -noecho -sync 1\n");
119 printf("cmp large1 large2 -buffer 200000 -noecho -to diffs\n");
124 /************************************************
125 * Errors -- all serious *
126 ************************************************/
128 void moan(code, text)
132 fprintf(stderr, "\n** ");
136 fprintf(stderr, "Unable to open file \"%s\"", text);
139 fprintf(stderr, " - ");
142 else fprintf(stderr, "\n");
146 fprintf(stderr, "Buffer overflow for file \"%s\"\n", text);
150 fprintf(stderr, "Two file names must be given\n");
154 fprintf(stderr, "Unknown error %d\n", code);
158 fprintf(stderr, "** CMP abandoned\n");
164 /*************************************************
165 * Write line identification *
166 *************************************************/
168 void write_id(n1, n2, c, name, p1, p2)
170 char *name, *p1, *p2;
172 if (n2 < 0) n2 = -n2;
174 fprintf(f_out, "%cine", c);
175 if (n1 == n2) fprintf(f_out, " %d of \"%s\"%s", n1, name, p1);
176 else fprintf(f_out, "s %d-%d of \"%s\"%s", n1, n2, name, p2);
180 /*************************************************
181 * Write sequence of lines *
182 *************************************************/
184 void write_lines(s, t)
190 while (*p != '\n') fputc(*p++, f_out);
198 /*************************************************
199 * Write separator rule *
200 *************************************************/
205 while (l-- > 0) fprintf(f_out, "%c", s);
206 fprintf(f_out, "\n");
211 /*************************************************
212 * Write message on re-sync or eof *
213 *************************************************/
215 void write_message(tline_one, tline_two)
216 line *tline_one, *tline_two;
218 int s1 = rootline_one->number;
219 int t1 = tline_one->number;
220 int s2 = rootline_two->number;
221 int t2 = tline_two->number;
222 if (echo) rule('=', 15);
226 write_id(s2, t2, 'L', name_two, " occurs ", " occur ");
227 if (s1 < 0) fprintf(f_out, "at the end");
228 else fprintf(f_out, "before line %d", s1);
229 fprintf(f_out, " of \"%s\".\n", name_one);
233 write_lines(rootline_two, tline_two);
239 write_id(s1, t1, 'L', name_one, " occurs ", " occur ");
240 if (s2 < 0) fprintf(f_out, "at the end");
241 else fprintf(f_out, "before line %d", s2);
242 fprintf(f_out, " of \"%s\".\n", name_two);
246 write_lines(rootline_one, tline_one);
250 else if (t1 < 0 && t2 < 0)
252 fprintf(f_out, "From line %d of \"%s\" and line %d of \"%s\" ",
253 rootline_one->number, name_one, rootline_two->number, name_two);
254 fprintf(f_out, "the files are different.\n");
258 if (-t1-s1 < 21) write_lines(rootline_one, tline_one);
259 else fprintf(f_out, "... <more than 20 lines> ...\n");
261 if (-t2-s2 < 21) write_lines(rootline_two, tline_two);
262 else fprintf(f_out, "... <more than 20 lines> ...\n");
268 write_id(s1, t1, 'L', name_one, " does ", " do ");
269 fprintf(f_out, "not match ");
270 write_id(s2, t2, 'l', name_two, ".\n", ".\n");
274 write_lines(rootline_one, tline_one);
276 write_lines(rootline_two, tline_two);
284 /*************************************************
285 * Advance to next line in store *
286 *************************************************/
288 /* A separate procedure exists for each file, for
289 simplicity and efficiency. */
291 int nextline_one(pvoid)
293 if (pline_one == NULL || pline_one->next == NULL) return FALSE;
294 pline_one = pline_one->next;
298 int nextline_two(pvoid)
300 if (pline_two == NULL || pline_two->next == NULL) return FALSE;
301 pline_two = pline_two->next;
306 /*************************************************
307 * Read a line into store *
308 *************************************************/
310 /* A separate procedure exists for each file, for
311 simplicity and efficiency. */
313 void readline_one(pvoid)
316 int c = fgetc(f_one);
317 line *nextline = (line *)bufnext_one;
319 bufnext_one = nextline->text;
320 if (bufnext_one >= buftop_one) moan(2, name_one);
322 nextline->next = NULL;
328 nextline->number = -lines_one;
332 nextline->number = lines_one;
335 if (c == EOF) c = '\n';
339 while (bufnext_one > nextline->text)
340 { if (bufnext_one[-1] == ' ') bufnext_one--; else break; }
341 *(bufnext_one++) = '\n';
342 if (bufnext_one >= buftop_one) moan(2, name_one);
345 if (c == '\t' && !exact)
346 do { *(bufnext_one++) = ' '; count++; } while ((count & 7) != 0);
347 else { *(bufnext_one++) = c; count++; }
348 if (bufnext_one >= buftop_one) moan(2, name_one);
353 if (lastline_one != NULL) lastline_one->next = nextline;
354 lastline_one = nextline;
355 pline_one = nextline;
357 bufnext_one = (char *) (((int)bufnext_one+3) & (-4));
362 void readline_two(pvoid)
365 int c = fgetc(f_two);
366 line *nextline = (line *)bufnext_two;
368 bufnext_two = nextline->text;
369 if (bufnext_two >= buftop_two) moan(2, name_two);
371 nextline->next = NULL;
377 nextline->number = -lines_two;
381 nextline->number = lines_two;
384 if (c == EOF) c = '\n';
388 while (bufnext_two > nextline->text)
389 { if (bufnext_two[-1] == ' ') bufnext_two--; else break; }
390 *(bufnext_two++) = '\n';
391 if (bufnext_two >= buftop_two) moan(2, name_two);
394 if (c == '\t' && !exact)
395 do { *(bufnext_two++) = ' '; count++; } while ((count & 7) != 0);
396 else { *(bufnext_two++) = c; count++; }
397 if (bufnext_two >= buftop_two) moan(2, name_two);
402 if (lastline_two != NULL) lastline_two->next = nextline;
403 lastline_two = nextline;
404 pline_two = nextline;
406 bufnext_two = (char *) (((int)bufnext_two+3) & (-4));
411 /**************************************************
412 * Compare two lines *
413 **************************************************/
415 int compare_lines(a, b)
423 if (n1 < 0 && n2 < 0) return TRUE;
424 if (n1 < 0 || n2 < 0) return FALSE;
428 if (*s == '\n') return TRUE;
436 /*************************************************
437 * Re-synchronizing code *
438 *************************************************/
444 line *tline_one = pline_one;
445 line *tline_two = pline_two;
447 if (eof_one || eof_two) matched = FALSE; else
449 for (i = 1; i < sync_count; i++)
451 if (!nextline_one()) readline_one();
452 if (!nextline_two()) readline_two();
453 if (!compare_lines(pline_one, pline_two)) { matched = FALSE; break; }
454 if (eof_one || eof_two) { matched = FALSE; break; }
458 if (matched) write_message(tline_one, tline_two); else
460 pline_one = tline_one;
461 pline_two = tline_two;
469 /*************************************************
470 * Main compare code *
471 *************************************************/
477 /* Big main loop - exit by return or unmatched at eof */
481 /* First minor loop, while in step */
483 while (matched && !eof_one && !eof_two)
485 /* Advance or read next lines */
489 bufnext_one = bufbase_one;
496 bufnext_two = bufbase_two;
501 /* Compare and check for end of file */
503 matched = compare_lines(pline_one, pline_two);
505 } /* End first minor loop */
507 if (matched) return; /* successful end of file */
509 /* There has been a mis-match */
512 rootline_one = pline_one; /* Fail point */
513 rootline_two = pline_two;
515 /* Second minor loop, trying to regain sync */
517 while (!eof_one || !eof_two)
519 /* Advance one and scan all of two */
523 line *zline = pline_two;
524 if (!nextline_one()) readline_one();
525 pline_two = rootline_two;
528 if (compare_lines(pline_one, pline_two))
533 if (pline_two == zline) break;
534 pline_two = pline_two->next;
539 /* Advance two and scan all of one */
543 line *zline = pline_one;
544 if (!nextline_two()) readline_two();
545 pline_one = rootline_one;
548 if (compare_lines(pline_one, pline_two))
553 if (pline_one == zline) break;
554 pline_one = pline_one->next;
559 } /* End second minor loop */
561 } /* End of major loop */
563 write_message(lastline_one, lastline_two);
569 /*************************************************
571 *************************************************/
579 int arg_help = FALSE;
583 /* Scan argument strings */
587 char *arg = argv[argp];
588 char **lv_name = (name_one == NULL)? &name_one:&name_two; /* default for positional */
589 int *lv_value = NULL;
593 { /* keyed argument */
594 if (EqString(arg,"-help") || EqString(arg, "-h"))
595 { arg_help = TRUE; value = FALSE; }
596 else if (EqString(arg, "-id"))
597 { arg_id = TRUE; value = FALSE; }
598 else if (EqString(arg, "-exact"))
599 { exact = TRUE; value = FALSE; }
600 else if (EqString(arg, "-noecho"))
601 { echo = FALSE; value = FALSE; }
602 else if (EqString(arg, "-to")) lv_name = &to_name;
603 else if (EqString(arg, "-sync") || EqString(arg, "-s"))
604 lv_value = &sync_count;
605 else if (EqString(arg, "-buffer")) lv_value = &storesize;
606 else { printf("Unknown keyword %s\n", arg); exit(99); }
608 if (++argp >= argc && value)
609 { printf("Value for keyword %s missing\n", arg); exit(99); }
612 /* Deal with keys that take values */
616 if (lv_value == &sync_count || lv_value == &storesize)
620 char *argval = argv[argp++];
622 while ((ch = argval[i++]) != 0)
624 if ('0' <= ch && ch <= '9') *lv_value = 10*(*lv_value) + ch - '0'; else
626 printf("Number expected after \"%s\" but \"%s\" read\n",
633 else if (*lv_name != NULL)
635 printf("Keyword expected but \"%s\" read", arg);
636 printf(" - use \"cmp -h\" for help\n");
639 else *lv_name = argv[argp++];
643 /* Deal with help and id */
645 if (arg_id && !arg_help)
647 printf("PH's CMP v%d\n", version);
657 /* Deal with file names */
659 if (name_one == NULL || name_two == NULL) moan(3, "");
663 f_out = fopen(to_name, "w");
664 if (f_out == NULL) moan(1, to_name);
667 /* Further general initialization */
669 if (storesize < minstore) storesize = defaultstore;
670 f_one = fopen(name_one, "r");
671 if (f_one == NULL) moan(1, name_one);
672 f_two = fopen(name_two, "r");
673 if (f_two == NULL) moan(1, name_two);
675 bufbase_one = (char *)malloc(storesize);
676 buftop_one = bufbase_one + storesize;
677 bufbase_two = (char *)malloc(storesize);
678 buftop_two = bufbase_two + storesize;
686 if (return_code == 0)
687 fprintf(f_out, "\"%s\" and \"%s\" are identical.\n", name_one, name_two);
690 if (echo) rule('=', 15);
691 fprintf(f_out, "%d difference", return_code);
692 if (return_code != 1) fprintf(f_out, "s");
693 fprintf(f_out, " found.\n");
696 fprintf(f_out, "\"%s\" contains %d line", name_one, lines_one);
697 if (lines_one != 1) fprintf(f_out, "s");
700 fprintf(f_out, "; \"%s\" contains %d line", name_two, lines_two);
701 if (lines_two != 1) fprintf(f_out, "s");
702 fprintf(f_out, ".\n");
710 if (f_out != stdout) fclose(f_out);
715 /* End of PH-Compare. */