[comp.sources.misc] v02i001: Public domain diff

blarson%skat.usc.edu@oberon.usc.edu (Bob Larson) (01/07/88)

Comp.sources.misc: Volume 2, Issue 1

Archive-Name: pd-diff

Submitted-By: blarson@skat.usc.edu (Bob Larson)


Comp.sources.misc: Volume 2, Issue 1
Archive-Name: pd-diff
Submitted-By: blarson@skat.usc.edu (Bob Larson)

#!/bin/sh
# manually generated shar
cat <<SHAR_EOF >README
Here's a public domain diff with the -b and -c options.  (4.2bsd style
contex diffs.)  I wasn't aware that these wern't present in all unix
versions of diff, so I didn't think posting it was a priority.  It's
large, slow, and many of the comments are no longer true, but it does
work.  (except when it runs out of memory)  The one case I know of
where its output is incompatable with patch does seem to be pretty
rare.  No makefile is included, the 4.2bsd diff is better on the unix
system I use.  If you don't know how to compile and load a single C
program, this probably isn't the tool for you anyway.  I'd be grateful
to anyone who cleans this up and documents it properly.  It does
appear to have been separate files at some point, I'm presenting it in
a form similar to how I got it: mail headers and outdated documentation
in comments and all.  I just banged on it enough to get it doing what
I wanted.
SHAR_EOF
cat <<SHAR_EOF >diff.c
/*
 * Last Hacked by Robert A. Larson, blarson@ecla.usc.edu, Nov 29 86
 *   OSK support (except for error routines).
 *   Real context diffs, with a couple of minor problems:
 *	  If the first change is deleting leading lines, and the second
 *	such that the context overlaps the deleted lines, the deleted
 *	lines are output as context.  This is consistant with other
 *	cases of overlapping context, but patch doesn't like it.  It's
 *	not hard to manually fix the diff in this (rare?) case.
 *	  File modifacation times are not output.
 *	  At most 9 lines of context is output.
 *
 * Previously Hacked by John Gilmore, hoptoad!gnu, 26Sep86.
 * Compiles and runs under Unix.  Much faster since it doesn't reallocate
 * every data structure in the inner loop(!).  Compatible with Unix diff
 * output format, though it occasionally finds different sets of changed
 * lines (both are valid).  -c option needs work.  Also, ftell's in
 * <check> should be dumped when possible.
 *
From: EVERHART%ARISIA%rca.com@CSNET-RELAY.ARPA
Message-Id: <8608201845.AA15181@lll-crg.ARPA>
Date:     Wed, 20 Aug 86 10:34 EDT
To: hoptoad!gnu@LLL-CRG.ARPA
Subject:  Decus C DIFF (partially moved to Amiga) source.
 */

/*
 *  	  D I F F
 */

/* For VMS:
  )BUILD  $(TKBOPTIONS) = {
  	  TASK  = ...DIF
  	}
*/

/*
 *	OSK changes: since OSK doesn't have realloc, put the size 
 *	allocated in the head of each allocated region.  This code
 *	assumes int is a maximally alligned type.  There is AMIGA
 *	code to avoid realloc, but it is broken.
 */

#ifdef  DOCUMENTATION
title  diff  Differential File Comparison
index  	Differential File Comparison

synopsis

  diff [option] file1 file2

description

  Diff compares two files, showing what must be changed to make
  them identical. Either file1 or file2 (but not both) may refer
  to directories.  If that is the case, a file in the directory
  whose name is the same as the other file argument will be used.
  The standard input may be used for one of the files by replacing
  the argument by "-".  Except for the standard input, both files
  must be on disk devices.
  .s
  Options:
  .lm +8
  .s.i -8;-b  Remove trailing whitespace (blanks and tabs)
  and compress all other strings of whitespace to a single blank.
  .s.i -8;-c  Print some context -- matching lines before
  and after the non-match section.  Mark non-matched sections
  with "|".
  .s.i -8;-i  Ignore lower/upper case distinctions.
  .s.i -8;-e  Output is in an "editor script" format which
  is compatible with the Unix 'ed' editor.
  .s.lm -8
  All information needed to compare the files is maintained in main
  memory. This means that very large files (or fairly large files with
  many differences) will cause the program to abort with an "out
  of space" message.  Main memory requirements (in words) are
  approximately:
  .s
  	2 * (length of file1 + length of file2)
  .br
  	+ 3 * (number of changes)
  .s
  (Where "length" is the number of lines of data in each file.)
  .s
  The algorithm reads each file twice, once to build hash tables and once
  to check for fortuitous matches (two lines that are in fact different,
  but which have the same hash value).  CPU time requirements include
  sorting the hash tables and randomly searching memory tables for
  equivalence classes. For example, on a time-shared VAX-11/780,
  comparing two 1000 line files required about 30 seconds (elapsed
  clock time) and about 10,000 bytes of working storage.  About 90
  per-cent of the time was taken up by file I/O.

diagnostics

  .lm +8
  .s.i -8;Warning, bad option 'x'
  .s
  The option is ignored.
  .s.i -8;Usage ...
  .s
  Two input files were not specified.
  .s.i -8;Can't open input file "filename".  Can't continue.
  .s.i -8;Out of space
  .s
  The program ran out of memory while comparing the two files.
  .s.i -8;Can't read line nnn at xxx in file[A/B]
  .s
  This indicates an I/O error when seeking to the specific line.
  It should not happen.
  .s.i -8;Spurious match, output is not optimal.
  .s
  Two lines that were different yielded the same hash value.  This is
  harmless except that the difference output is not the minimum set of
  differences between the two files.  For example, instead of the output:
  .s
  	lines 1 to 5 were changed to ...
  .s
  the program will print
  .s
  	lines 1 to 3 were changed to ...
  .br
  	lines 4 to 5 were changed to ...
  .s
  The program uses a CRC16 hash code.  The likelihood of this error is
  quite small.
  .lm -8

author

  The diff algorithm was developed by J. W. Hunt and M. D. McIlroy,
  using a central algorithm defined by H. S. Stone.
  It was published in:
  .s.lm +4.nf
  Hunt, J. W., and McIlroy, M. D.,
  An Algorithm for Differential File Comparison,
  Computing Science Technical Report #41,
  Bell Laboratories, Murray Hill, NJ  07974
  .s.lm -4.f

bugs

  On RSX and DECUS C on VMS systems, diff may fail if the both
  files are not "variable-length, implied carriage control"
  format.  The scopy program can be used to convert files
  to this format if problems arise.

  When compiled under VAX C, diff handles STREAM_LF files
  properly (in addition to the canonical variable-length implied
  carriage control files).  Other variations should work, but
  have not been tested.

  When compiled under VAX C, diff is quite slow for unknown reasons
  which ought to be investigated.  On the other hand, it has access to
  effectively unlimited memory.

  Output in a form suitable for ed - the -e option - seems rather
  pointless; the analogue on DEC systems is SLP (SUMSLP on VMS).
  It would be simple to provide SLP-compatible output.  The question
  is, why bother - since the various DEC file comparison utilities
  already produce it.

#endif

/*
 * Diff maintains all information needed to compare the two files in main
 * memory.  This means that very large files (or fairly large files with
 * many differences) will cause the program to abort with an "out of space"
 * error.  Main memory requirements (in words) are approximately:
 *
 *  2 * (length of file1 + length of file2) + (3 * number of changes)
 *
 * The diff algorithm reads each file twice (once to build hash tables and
 * a second time to check for fortuitous matches), then reads the differences
 * by seeking randomly within the files.  CPU time requirements include
 * sorting the two hash vectors and randomly searching memory tables for
 * equivalence classes.  For example, running in Vax compatibility
 * mode, two 1000 line files with a fair number of differences took
 * about 25 seconds (elapsed wall clock time) for processing.  Most of this
 * time was spent in the file read routines.  This test required slightly
 * more than 6000 words of memory for internal tables.
 *
 * The diff algorithm was developed by J. W. Hunt and M. D. McIlroy,
 * using a central algorithm defined by H. S. Stone.  The algorithm
 * was described in:
 *
 *  Hunt, J. W., and McIlroy, M. D.,
 *  An Algorithm for Differential File Comparison,
 *  Computing Science Technical Report #41,
 *  Bell Laboratories, Murray Hill, NJ  07974
 *  
 * The following description is summarized from that document.  While
 * it has been slightly modified to correspond to the program source, the
 * algorithm is essentially identical.
 *
 * 1.  Read the input files, building two vectors containing the
 *  line number (serial) and hash value (hash) of each line.
 *  Data for fileA will be in a vector pointed to by fileA[],
 *  while data for fileB will be pointed to by fileB[]. The
 *  lengths (number of lines) of the files will be represented
 *  by lenA and lenB respectively.  [This is slightly different
 *  from the published algorithm.]
 *
 * 2.  Note initial and final sequences that have identical
 *  hash values to shorten subsequent processing.  Note that
 *  the "jackpot" phase (step 9.) will examine all lines in
 *  the file.  Next, sort each file using hash as the primary
 *  key and serial as the secondary key.
 *
 * 3.  Construct an array of equivalence classes (member[1..lenB])
 *  where each element contains the line number in fileB and a
 *  flag which is True if the indicated line is the first member
 *  of an equivalence class.  (An equivalence class is a set of
 *  lines which all hash to the same value.  The lines themselves
 *  are not necessarily identical.)
 *
 * 4.  Construct an array, class[1..lenA], where each element, I, is set to
 *  the index of a line, J, in fileB if member[J] is the first
 *   element in an equivalence class and the hash code of line[I] in
 *  fileA is the same as the hash code of line[J] in fileB.  Class[I]
 *  is set to zero if no such line exists.
 *
 *  If non-zero, class[I] now points in member[] to the beginning of
 *  the class of lines in fileB equivalent to line[I] in fileA.
 *
 * The next two steps implement the longest common subsequence algorithm.
 *
 * 5.  Structure CANDIDATE { a, b, previous }, where a and b are line
 *   numbers and previous a reference to a candidate, will store
 *  candidate lists as they are constructed.
 *
 *  Vector clist[] stores references to candidates.  It is dimensioned
 *  (0..min(lenA, lenB) + 1)
 *
 *  Initialize
 *  	clist[0] = CANDIDATE {   0,   0, -1 };
 *  	clist[1] = CANDIDATE { A+1, B+1, -1 };
 *  	ktop = 1;
 *
 *  clist[1] is a fence beyond the last usefully filled element
 *  and -1 is an out-of-range clist index. Ktop is the index of the
 *  fence.  Note, because of the memory allocation used, clist[]
 *  is actually composed of two vectors, clist[] containing the
 *  candidate reference, and klist[] containing pointers to clist.
 *
 * 6.  For (A = 1 to lenA) {
 *  	I = class[A];  -- the index in member[]:  beginning of
 *  	  	-- the class of lines in fileB equivalent
 *  	  	-- to this line in fileA.
 *  	if (I is non-zero) {
 *  	  Merge each member into the candidate list
 *  	  as discussed below.
 *  	}
 *
 * Unravel the chain of candidates, getting a vector of common subsequences:
 *
 * 7.  Set all elements of match[0..lenA] to zero.
 *
 * 8.  clist[ktop-1] points to the subsequence chain head.  For each element
 *  in the chain, let A and B be the line number entries.  Then, set
 *
 *  	match[A] = B;
 *
 *  The non-zero elements of match[]  now pick out a longest common
 *  subsequence chain, possibly including spurious matches due to
 *  hash coincidences.  The pairings between the two files are:
 *
 *  if (match[A] is non-zero) {
 *  	line A in fileA matches line match[A] in fileB;
 *  }
 *
 * Now, read each line of fileA and fileB to check for jackpots:
 *
 * 9.  for (A = 1 to lenA) {
 *  	if (match[A] is nonzero) {
 *  	  if (fileA[A] is not identical to fileB[match[A]])
 *  	  	match[A] = 0;  -- Hash congruence
 *  	}
 *  }
 *
 * Ignoring "squish" complications, the merge step may be defined as follows:
 *
 *  Entry:
 *  	clist[]  	Candidate pointer array
 *  	ktop  	Fence beyond last filled index
 *  	A  	Current index in fileA
 *  	member[]  Equivalence vector
 *  	I  	The index in member[] of the first element
 *  	  	  of the class of lines in fileB that are
 *  	  	  equivalent to line[A] in fileA.
 *
 * 1.  Let clist[R] be "an r-candidate" and C a reference to
 *  the last candidate found, which will always be an r-candidate.
 *  clist[R] will be updated with this reference once the previous
 *  value of clist[R] is no longer needed.  Initialize:
 *
 *  	R = 0;
 *  	C = clist[0];
 *
 * 2.  Do steps 3 through 6 repeatedly:
 *
 *   3.  set B to the line number in member[I];
 *  search clist[R..ktop] for an element, clist[S], such that
 *
 *  	clist[S-1].b < B and clist[S].b >= B
 *
 *  Note that clist[] is ordered on clist[].b so that binary
 *  search will work.  The search algorithm used requires the
 *  two "fence" entries described above.
 *
 *  If such an element is found, perform steps 4. and 5.
 *
 *  4. Extend the longest common subsequence chain:
 *
 *  	If (clist[S].b > B) {
 *  	  clist[R] = C;
 *  	  R = S;
 *  	  C = candidate(A, B, clist[S - 1]);
 *  	}
 *
 *  5. Extend the number of subsequences, moving the fence:
 *
 *  	If (S == ktop) {
 *  	  clist[ktop + 1] = clist[ktop]
 *  	  ktop = ktop + 1;
 *  	  break out of step 2's loop;
 *  	}
 *
 *   6.  I = I + 1;
 *  if (member[I] is the first element in another class) {
 *  	break out of step 2's loop;
 *  }
 *  else {
 *  	continue at step 2.
 *  }
 *
 * 7.  clist[R] = C;
 *  exit merge subroutine.
 *
 * To illustrate vector contents, here is a sample run:
 *
 * File A:
 *  line 1
 *  line 2
 *  line 3
 *  line 4
 *  line 5 gets deleted
 *  line 6
 *
 * File B:
 *  line 1
 *  line 1.5 inserted
 *  line 2
 *  line 3 changed
 *  line 4
 *  line 6
 *
 * (For clarity, the "squish" step is omitted from the following)
 *
 * On entry to equiv() (after readin and sorting), the file[] vector is
 * as follows (the first entry in each pair is the line number, the
 * second is the hash value.  Entries are sorted on hash value):
 *
 * FileA[] (1..lines in fileA):
 *   line   hash
 *  3 042400  6 043300  5 050026  1 102201  2 102701  4 103501
 * FileB[] (1..lines in fileB):
 *  6 043300  2 045600  1 102201  3 102701  5 103501  4 147166
 *
 *
 * After Equiv has processed file[]:
 *
 * FileA[] (1..lines in fileA):
 *   line value
 *  3 0  6 1  5 0  1 3  2 4  4 5
 * Member[] (0..lines in fileB)
 *  0  -6  -2  -1  -3  -5  -4
 *
 *
 * After unsort() has unwound fileB:
 *
 * Class[] (1 .. lines in fileA):
 *  3   4  0  5  0  1
 *
 * Within unravel(), match is built in the following order:
 *
 *  match[6] := 6
 *  match[4] := 5
 *  match[2] := 3
 *  match[1] := 1
 *
 * Match[] (0 .. lines in fileA):
 *
 *   0  1  3  0  5  0  6
 *
 * Output is as follows:
 *
 *  1a2
 *  > line 1.5 inserted
 *  3c4
 *  < line 3
 *  ---
 *  > line 3 changed
 *  5d5
 *  < line 5 gets deleted
 *
 *
 */

#include <stdio.h>
#include <ctype.h>

#ifdef vms
#include  	<ssdef.h>
#include  	<stsdef.h>
#define  IO_SUCCESS  (SS$_NORMAL | STS$M_INHIB_MSG)
#define  IO_ERROR  SS$_ABORT
#endif
/*
 * Note: IO_SUCCESS and IO_ERROR are defined in the Decus C stdio.h file
 */
#ifndef  IO_SUCCESS
#define  IO_SUCCESS  0
#endif
#ifndef  IO_ERROR
#define  IO_ERROR  1
#endif

#define  EOS  	0
#ifdef unix
char temfile[L_tmpnam];
char *tmpnam();
#define  TEMPFILE  (temfile[0]? temfile: (tmpnam(temfile), temfile))
#else
#define  TEMPFILE  "diff.tmp"
#endif
#define  TRUE  	1
#define  FALSE  	0

#ifdef  pdp11
#define  short  int
#endif

typedef struct candidate {
  int  b;  	  /* Line in fileB  	*/
  int  a;  	  /* Line in fileA  	*/
  int  link;  	  /* Previous candidate  	*/
} CANDIDATE;

typedef struct line {
  unsigned short  hash;  	/* Hash value etc.  	*/
  short  	serial;  	/* Line number  	  */
} LINE;

LINE  *file[2];  	  /* Hash/line for total file  */
#define  fileA  file[0]
#define  fileB  file[1]

LINE  *sfile[2];  	  /* Hash/line after prefix  */
#define  sfileA  sfile[0]
#define  sfileB  sfile[1]

int  len[2];  	  	/* Actual lines in each file  */
#define  lenA  len[0]
#define  lenB  len[1]

int  slen[2];  	  /* Squished lengths  	*/
#define  slenA  slen[0]
#define  slenB  slen[1]

int  prefix;  	  	/* Identical lines at start  */
int  suffix;  	  	/* Identical lenes at end  */

FILE  *infd[2] = { NULL, NULL };  /* Input file identifiers  */
FILE  *tempfd;  	  /* Temp for input redirection  */

extern long  ftell();
extern FILE *fopen();
extern char *malloc();

char	*fgetss();
unsigned short  	hash();

#ifdef	AMIGA
/* Define these types for Amiga C */
char	*savptr;
int	savsiz;
char	*wrk;
char	*wrk2;
int	cpysiz;
#endif
/*
 * The following vectors overlay the area defined by fileA
 */

short  	*class;  	/* Unsorted line numbers  */
int  	*klist;  	/* Index of element in clist  */
CANDIDATE  *clist;  	/* Storage pool for candidates  */
int  	clength = 0;  	/* Number of active candidates  */
#define	CSIZE_INC 50	/* How many to allocate each time we have to */
int	csize = CSIZE_INC; /* Current size of storage pool */

int  	*match;  	  /* Longest subsequence  	*/
long  	*oldseek;  	/* Seek position in file A  */

/*
 * The following vectors overlay the area defined by fileB
 */

short  	*member;  	/* Concatenated equiv. classes  */
long  	*newseek;  	/* Seek position in file B  */
char  	*textb;  	  /* Input from file2 for check  */

/*
 * Global variables
 */

int  	eflag  = FALSE;  /* Edit script requested  */
int  	bflag  = FALSE;  /* Blank supress requested  */
int  	cflag  = FALSE;  /* Context printout  	*/
int  	iflag  = FALSE;  /* Ignore case requested  */
char  	text[257];  	/* Input line from file1  */
extern char  *myalloc();  	/* Storage allocator  	*/

extern char  *compact();  	/* Storage compactor  	*/

#ifdef  DEBUG
#ifndef OSK
#define  TIMING
#endif
#endif
#ifdef  TIMING
extern long  time();
extern char  *$$mend;
long  	totaltime;
long  	sectiontime;
char  	*mstart;
#endif

main(argc, argv)
int  	argc;
char  	**argv;
/*
 * Diff main program
 */
{
  register int  i;
  register char  *ap;

#ifdef OSK
  extern int _memmins;
  _memmins = 16 * 1024;			/* tell OSK we will malloc a lot */
#endif
#ifdef  TIMING
  sectiontime = time(&totaltime);
#endif
#ifdef vms
  argc = getredirection(argc,argv);
#endif
  while (argc > 1 && *(ap = argv[1]) == '-' && *++ap != EOS) {
  	while (*ap != EOS) {
  	  switch ((*ap++)) {
  	  case 'b':
  	  	  bflag++;
  	  	  break;

  	  case 'c':
		  if (*ap > '0' && *ap <= '9') cflag = *ap++ - '0';
		  else cflag = 3;
		  break;

  	  case 'e':
  	  	  eflag++;
  	  	  break;

  	  case 'i':
  	  	  iflag++;
  	  	  break;

  	  default:
  	  	  fprintf(stderr,
  	  	  	"Warning, bad option '%c'\n",
  	  	  	ap[-1]);
  	  	  break;
  	  }
  	}
  	argc--;
  	argv++;
  }

  if (argc != 3)
  	error("Usage: diff [-options] file1 file2");
  if (cflag && eflag) {
  	fprintf(stderr,
  	  "Warning, -c and -e are incompatible, -c supressed.\n");
  	cflag = FALSE;
  }
  argv++;
  for (i = 0; i <= 1; i++) {
  	if (argv[i][0] == '-' && argv[i][1] == EOS) {
  	  infd[i] = stdin;
  	  if ((tempfd = fopen(TEMPFILE, "w")) == NULL)
  	  	cant(TEMPFILE, "work", 1);
  	}
  	else {
  	  infd[i] = fopen(argv[i], "r");
	  if (!infd[i]) cant(argv[i], "input", 2);	/* Fatal error */
  	}
  }

  if (infd[0] == stdin && infd[1] == stdin)
	error("Can't diff two things both on standard input.");

  if (infd[0] == NULL && infd[1] == NULL) {
  	cant(argv[0], "input", 0);
  	cant(argv[1], "input", 1);
  }
#ifdef vms
  else if (infd[1] == NULL)
  	opendir(1, &argv[1], infd[0]);
  else if (infd[0] == NULL)
  	opendir(0, &argv[0], infd[1]);
#endif

 /*
   * Read input, building hash tables.
   */
  input(0);
  input(1);
  squish();
#ifdef  DEBUG
  printf("before sort\n");
  for (i = 1; i <= slenA; i++)
  	printf("sfileA[%d] = %6d %06o\n",
  	  i, sfileA[i].serial, sfileA[i].hash);
  for (i = 1; i <= slenB; i++)
  	printf("sfileB[%d] = %6d %06o\n",
  	  i, sfileB[i].serial, sfileB[i].hash);
#endif
  sort(sfileA, slenA);
  sort(sfileB, slenB);
#ifdef  TIMING
  ptime("input");
#endif
#ifdef  DEBUG
  printf("after sort\n");
  for (i = 1; i <= slenA; i++)
  	printf("sfileA[%d] = %6d %06o\n",
  	  i, sfileA[i].serial, sfileB[i].hash);
  for (i = 1; i <= slenB; i++)
  	printf("sfileB[%d] = %6d %06o\n",
  	  i, sfileB[i].serial, sfileB[i].hash);
#endif
  /*
   * Build equivalence classes.
   */
  member = (short *)fileB;
  equiv();
  member = (short *)compact((char *)member, (slenB + 2) * sizeof (int),
  	  "squeezing member vector");
  /*
   * Reorder equivalence classes into array class[]
   */
  class = (short *)fileA;
  unsort();
  class = (short *)compact((char *)class, (slenA + 2) * sizeof (int),
  	  "compacting class vector");
#ifdef  TIMING
  ptime("equiv/unsort");
#endif
  /*
    * Find longest subsequences
   */
  klist = (int *)myalloc((slenA + 2) * sizeof (int), "klist");
  clist = (CANDIDATE *)myalloc(csize * sizeof (CANDIDATE), "clist");
  i = subseq();
#ifndef OSK
  free((char *)member);
  free((char *)class);
#else
  free((char *)member - sizeof(int));
  free((char *)class - sizeof(int));
#endif
  match = (int *)myalloc((lenA + 2) * sizeof (int), "match");
  unravel(klist[i]);
#ifndef OSK
  free((char *)clist);
  free((char *)klist);
#else
  free((char *)clist - sizeof(int));
  free((char *)klist - sizeof(int));
#endif
#ifdef  TIMING
  ptime("subsequence/unravel");
#endif
  /*
   * Check for fortuitous matches and output differences
   */
  oldseek = (long *)myalloc((lenA + 2) * sizeof (* oldseek), "oldseek");
  newseek = (long *)myalloc((lenB + 2) * sizeof (* newseek), "newseek");
  textb = myalloc(sizeof text, "textbuffer");
  if (check(argv[0], argv[1]))
  	fprintf(stderr, "Spurious match, output is not optimal\n");
#ifdef  TIMING
  ptime("check");
#endif
  output(argv[0], argv[1]);
#ifdef  TIMING
  ptime("output");
  printf("%ld seconds required\n", sectiontime - totaltime);
#endif
  if (tempfd != NULL) {
  	fclose(tempfd);
#ifdef unix
  	unlink(TEMPFILE);
#else
#ifdef OSK
	unlink(TEMPFILE);
#else
	remove(TEMPFILE);
#endif
#endif 
  }
}


input(which)
int  	which;  	  /* 0 or 1 to redefine infd[]  */
/*
 * Read the file, building hash table
 */
{
  register LINE  	*lentry;
  register int  	linect = 0;
  FILE  	  *fd;
#define	LSIZE_INC 200	/* # of line entries to alloc at once */
  int	lsize = LSIZE_INC;

  lentry = (LINE *)myalloc(sizeof(LINE) * (lsize+3), "line");
  fd = infd[which];
  while (!getline(fd, text)) {
	if (++linect >= lsize) {
		lsize += 200;
		lentry = (LINE *)compact((char *)lentry,
		  (lsize + 3) * sizeof(LINE),
		  "extending line vector");
	}
  	lentry[linect].hash = hash(text);
  }
  /*
   * If input was from stdin ("-" command), finish off the temp file.
   */
  if (fd == stdin) {
  	fclose(tempfd);
  	tempfd = infd[which] = fopen(TEMPFILE, "r");
  }
  /* If we wanted to be stingy with memory, we could realloc lentry down
   * to its exact size (+3 for some odd reason) here.  No need?  */
  len[which] = linect;
  file[which] = lentry;
}

squish()
/*
 * Look for initial and trailing sequences that have identical hash values.
 * Don't bother building them into the candidate vector.
 */
{
  register int  i;
  register LINE  *ap;
  register LINE  *bp;
  int  	j;
  int  	k;

  /*
   * prefix -> first line (from start) that doesn't hash identically
   */
  i = 0; ap = &fileA[1]; bp = &fileB[1];
  while (i < lenA && i < lenB && ap->hash == bp->hash) {
  	i++; ap++; bp++;
  }
  prefix = i;
  /*
    * suffix -> first line (from end) that doesn't hash identically
   */
  j = lenA - i;
  k = lenB - i;
  ap = &fileA[lenA];
  bp = &fileB[lenB];
  i = 0;
  while (i < j && i < k && ap->hash == bp->hash) {
  	i++; ap--; bp--;
  }
  suffix = i;
  /*
   * Tuck the counts away
   */
  for (k = 0; k <= 1; k++) {
  	sfile[k] = file[k] + prefix;
  	j = slen[k] = len[k] - prefix - suffix;

  	for (i = 0, ap = sfile[k]; i <= slen[k]; i++, ap++) {
  	  ap->serial = i;
  	}
  }
}

sort(vector, vecsize)
LINE  	*vector;  	/* What to sort  	  */
int  	vecsize;  	/* How many to sort  	*/
/*
 * Sort hash entries
 */
{
  register int  j;
  register LINE  *aim;
  register LINE  *ai;
  int  	mid;  
  int  	k;
  LINE  	work;

  for (j = 1; j <= vecsize; j *= 2);
  mid = (j - 1);
  while ((mid /= 2) != 0) {
  	k = vecsize - mid;
  	for (j = 1; j <= k; j++) {
  	  for (ai = &vector[j]; ai > vector; ai -= mid) {
  	  	aim = &ai[mid];
  	  	if (aim < ai)
  	  	  break;  /* ?? Why ??  	*/
  	  	if (aim->hash > ai->hash ||
  	  	  	aim->hash == ai->hash &&
  	  	  	aim->serial > ai->serial)
  	  	  break;
  	  	work.hash = ai->hash;
  	  	ai->hash = aim->hash;
  	  	aim->hash = work.hash;
  	  	work.serial = ai->serial;
  	  	ai->serial = aim->serial;
  	  	aim->serial = work.serial;
  	  }
  	}
  }
}

equiv()
/*
 * Build equivalence class vector
 */
{
  register LINE  *ap;
  register union {
  	LINE	*bp;
  	short	*mp;
  } r;
  register int  j;
  LINE	*atop;

#ifdef  DEBUG
  printf("equiv entry\n");
  for (j = 1; j <= slenA; j++)
  	printf("sfileA[%d] = %6d %06o\n",
  	  	j, sfileA[j].serial, sfileA[j].hash);
  for (j = 1; j <= slenB; j++)
  	printf("sfileB[%d] = %6d %06o\n",
  	  	j, sfileB[j].serial, sfileB[j].hash);
#endif
  j = 1;
  ap = &sfileA[1];
  r.bp = &sfileB[1];
  atop = &sfileA[slenA];
  while (ap <= atop && j <= slenB) {
  	if (ap->hash < r.bp->hash) {
  	  ap->hash = 0;
  	  ap++;
  	}
  	else if (ap->hash == r.bp->hash) {
  	  ap->hash = j;
  	  ap++;
  	}
  	else {
  	  r.bp++;
  	  j++;
  	}
  }
  while (ap <= atop) {
  	ap->hash = 0;
  	ap++;
  }
  sfileB[slenB + 1].hash = 0;
#ifdef  DEBUG
  printf("equiv exit\n");
  for (j = 1; j <= slenA; j++)
  	printf("sfileA[%d] = %6d %06o\n",
  	  	j, sfileA[j].serial, sfileA[j].hash);
  for (j = 1; j <= slenB; j++)
  	printf("sfileB[%d] = %6d %06o\n",
  	  	j, sfileB[j].serial, sfileB[j].hash);
#endif
  ap = &sfileB[0];
  atop = &sfileB[slenB];
  r.mp = &member[0];
  while (++ap <= atop) {
  	r.mp++;
  	*r.mp = -(ap->serial);
  	while (ap[1].hash == ap->hash) {
  	  ap++;
  	  r.mp++;
  	  *r.mp = ap->serial;
  	}
  }
  r.mp[1] = -1;
#ifdef  DEBUG
  for (j = 0; j <= slenB; j++)
  	printf("member[%d] = %d\n", j, member[j]);
#endif
}

unsort()
/*
 * Build class vector
 */
{
  register int  *temp;
  register int  *tp;
  register union {
  	LINE	*ap;
  	short	*cp;
  } u;
  LINE	*evec;
  short	*eclass;
#ifdef  DEBUG
  int  	i;
#endif

  temp = (int *)myalloc((slenA + 1) * sizeof(int), "unsort scratch");
  u.ap = &sfileA[1];
  evec = &sfileA[slenA];
  while (u.ap <= evec) {
#ifdef  DEBUG
  	printf("temp[%2d] := %06o\n", u.ap->serial, u.ap->hash);
#endif
  	temp[u.ap->serial] = u.ap->hash;
  	u.ap++;
  }
  /*
   * Copy into class vector and free work space
   */
  u.cp = &class[1];
  eclass = &class[slenA];
  tp = &temp[1];
  while (u.cp <= eclass)
  	*u.cp++ = *tp++;
#ifndef OSK
  free((char *) temp);
#else
  free((char *)temp - sizeof(int));
#endif
#ifdef  DEBUG
  printf("unsort exit\n");
  for (i = 1; i <= slenA; i++)
  	printf("class[%d] = %d %06o\n", i, class[i], class[i]);
#endif
}

subseq()
/*
 * Generate maximum common subsequence chain in clist[]
 */
{
  int  	  a;
  register unsigned  	ktop;
  register int  	b;
  register int  	s;
  unsigned  	  r;
  int  	  i;
  int  	  cand;

  klist[0] = newcand(0, 0, -1);
  klist[1] = newcand(slenA + 1, slenB + 1, -1);
  ktop = 1;  	  	/* -> guard entry  */
  for (a = 1; a <= slenA; a++) {
  	/*
  	 * For each non-zero element in fileA ...
  	 */
  	if ((i = class[a]) == 0)
  	  continue;
  	cand = klist[0];  	/* No candidate now  */
  	r = 0;  	  	/* Current r-candidate  */
  	do {
#ifdef  DEBUG
  	  printf("a = %d, i = %d, b = %d\n", a, i, member[i]);
#endif
  	  /*
  	   * Perform the merge algorithm
  	   */
  	  if ((b = member[i]) < 0)
  	  	b = -b;
#ifdef  DEBUG
  	  printf("search(%d, %d, %d) -> %d\n",
  	  	  r, ktop, b, search(r, ktop, b));
#endif
  	  if ((s = search(r, ktop, b)) != 0) {
  	  	if (clist[klist[s]].b > b) {
  	  	  klist[r] = cand;
  	  	  r = s;
  	  	  cand = newcand(a, b, klist[s-1]);
#ifdef  DEBUG
  	  	  dumpklist(ktop, "klist[s-1]->b > b");
#endif
  	  	}
  	  	if (s >= ktop) {
  	  	  klist[ktop + 1] = klist[ktop];
  	  	  ktop++;
#ifdef  DEBUG
  	  	  klist[r] = cand;
  	  	  dumpklist(ktop, "extend");
#endif
  	  	  break;
  	  	}
  	  }
  	} while (member[++i] > 0);
    klist[r] = cand;
  }
#ifdef  DEBUG
  printf("last entry = %d\n", ktop - 1);
#endif
  return(ktop - 1);  	  /* Last entry found  */
}

int
newcand(a, b, pred)
int  	a;  	/* Line in fileA  	  */
int  	b;  	/* Line in fileB  	  */
int  	pred;  	/* Link to predecessor, index in cand[]  */
{
  register CANDIDATE  *new;

  clength++;
  if (++clength >= csize) {
	csize += CSIZE_INC;
	clist = (CANDIDATE *)compact((char *)clist,
		  csize * sizeof (CANDIDATE),
		  "extending clist");
  }
  new = &clist[clength - 1];
  new->a = a;
  new->b = b;
  new->link = pred;
  return(clength - 1);
}

search(low, high, b)
register unsigned  low;
register unsigned  high;
register int  b;
/*
 * Search klist[low..top] (inclusive) for b.  If klist[low]->b >= b,
 * return zero.  Else return s such that klist[s-1]->b < b and
 * klist[s]->b >= b.  Note that the algorithm presupposes the two
 * preset "fence" elements, (0, 0) and (slenA, slenB).
 */
{
  register int  	temp;
  register unsigned  	mid;

  if (clist[klist[low]].b >= b)
  	return(0);
  while ((mid = (low + high) / 2) > low) {
  	if ((temp = clist[klist[mid]].b) > b)
  	  high = mid;
  	else if (temp < b)
  	  low = mid;
  	else {
  	  return(mid);
  	}
  }
  return(mid + 1);
}


unravel(k)
register int  k;
{
  register int  	i;
  register CANDIDATE  *cp;
  int  	  first_trailer;
  int  	  difference;

  first_trailer = lenA - suffix;
  difference = lenB - lenA;
#ifdef  DEBUG
  printf("first trailer = %d, difference = %d\n",
  	first_trailer, difference);
#endif
  for (i = 0; i <= lenA; i++) {
  	match[i] = (i <= prefix) ? i
  	  : (i > first_trailer) ? i + difference
  	  : 0;
  }
#ifdef  DEBUG
  printf("unravel\n");
#endif
  while (k != -1) {
  	cp = &clist[k];
#ifdef  DEBUG
  	if (k < 0 || k >= clength)
  	  error("Illegal link -> %d", k);
  	printf("match[%d] := %d\n", cp->a + prefix, cp->b + prefix);
#endif
  	match[cp->a + prefix] = cp->b + prefix;
  	k = cp->link;
  }
}


/*
 * Check for hash matches (jackpots) and collect random access indices to
 * the two files.
 *
 * It should be possible to avoid doing most of the ftell's by noticing
 * that we are not doing a context diff and noticing that if a line
 * compares equal to the other file, we will not ever need to know its
 * file position.  FIXME.
 */
check(fileAname, fileBname)
char  	*fileAname;
char  	*fileBname;
{
  register int  a;  	/* Current line in file A  */
  register int  b;  	/* Current line in file B  */
  int  	jackpot;

/*
 * The VAX C ftell() returns the address of the CURRENT record, not the
 * next one (as in DECUS C or, effectively, other C's).  Hence, the values
 * are "off by one" in the array.  OFFSET compensates for this.
 */
#ifdef vms
#define OFFSET (-1)
#else
#define OFFSET 0
#endif

  b = 1;
  rewind(infd[0]);
  rewind(infd[1]);
/*
 * See above; these would be over-written on VMS anyway.
 */
#ifndef vms
  oldseek[0] = ftell(infd[0]);
  newseek[0] = ftell(infd[1]);
#endif

  jackpot = 0;
#ifdef  DEBUG
  printf("match vector\n");
  for (a = 0; a <= lenA; a++)
  	printf("match[%d] = %d\n", a, match[a]);
#endif
  for (a = 1; a <= lenA; a++) {
  	if (match[a] == 0) {
	  /* Unique line in A */
  	  oldseek[a+OFFSET] = ftell(infd[0]);
  	  getline(infd[0], text);
  	  continue;  
  	}
  	while (b < match[a]) {
	  /* Skip over unique lines in B */
  	  newseek[b+OFFSET] = ftell(infd[1]);
  	  getline(infd[1], textb);
  	  b++;
  	}

	/*
	 * Compare the two, supposedly matching, lines.
	 * Unless we are going to print these lines, don't bother to
	 * remember where they are.  We only print matching lines
	 * if a context diff is happening, or if a jackpot occurs.
	 */
	if (cflag) {
		oldseek[a+OFFSET] = ftell(infd[0]);
		newseek[b+OFFSET] = ftell(infd[1]);
	}
  	getline(infd[0], text);
  	getline(infd[1], textb);
  	if (!streq(text, textb)) {
  	  fprintf(stderr,  "Spurious match:\n");
  	  fprintf(stderr, "line %d in %s, \"%s\"\n",
  	  	a, fileAname, text);
  	  fprintf(stderr, "line %d in %s, \"%s\"\n",
  	  	b, fileBname, textb);
  	  match[a] = 0;
  	  jackpot++;
  	}

  	b++;
  }
  for (; b <= lenB; b++) {
  	newseek[b+OFFSET] = ftell(infd[1]);
  	getline(infd[1], textb);
  }
/*
 * The logical converse to the code up above, for NON-VMS systems, to
 * store away an fseek() pointer at the beginning of the file.  For VMS,
 * we need one at EOF...
 */
#ifdef vms
  oldseek[lenA] = ftell(infd[0]);
  getline(infd[0],text);  	  /* Will hit EOF...  */
  newseek[lenB] = ftell(infd[1]);
  getline(infd[1],textb);  	  /* Will hit EOF...  */
#endif

  return(jackpot);
}

output(fileAname, fileBname)
char *fileAname, *fileBname;
{
  register int  astart;
  register int  aend = 0;
  int  	bstart;
  register int  bend;

  rewind(infd[0]);
  rewind(infd[1]);
  match[0] = 0;
  match[lenA+1] = lenB + 1;
  if (!eflag) {
	if (cflag) {
	    /*
	     * Should include ctime style dates after the file names, but
	     * this would be non-trivial on OSK.  Perhaps there should be
	     * a special case for stdin.
	     */
	    printf("*** %s\n--- %s\n", fileAname, fileBname);
	}
  	/*
  	 * Normal printout
  	 */
  	for (astart = 1; astart <= lenA; astart = aend + 1) {
  	  /*
  	   * New subsequence, skip over matching stuff
  	   */
  	  while (astart <= lenA
  	  	  && match[astart] == (match[astart - 1] + 1))
  	  	astart++;
  	  /*
  	   * Found a difference, setup range and print it
  	   */
  	  bstart = match[astart - 1] + 1;
  	  aend = astart - 1;
  	  while (aend < lenA && match[aend + 1] == 0)
  	  	aend++;
  	  bend = match[aend + 1] - 1;
  	  match[aend] = bend;
  	  change(astart, aend, bstart, bend);
  	}
  }
  else {
  	/*
  	 * Edit script output -- differences are output "backwards"
  	 * for the benefit of a line-oriented editor.
  	 */
  	for (aend = lenA; aend >= 1; aend = astart - 1) {
  	  while (aend >= 1
  	  	  && match[aend] == (match[aend + 1] - 1)
  	  	  && match[aend] != 0)
  	  	aend--;
  	  bend = match[aend + 1] - 1;
  	  astart = aend + 1;
  	  while (astart > 1 && match[astart - 1] == 0)
  	  	astart--;
  	  bstart = match[astart - 1] + 1;
  	  match[astart] = bstart;
  	  change(astart, aend, bstart, bend);
  	}
  }
  if (lenA == 0)
  	change(1, 0, 1, lenB);
}


/*
 * Output a change entry: fileA[astart..aend] changed to fileB[bstart..bend]
 */
change(astart, aend, bstart, bend)
int  	astart;
int  	aend;
int  	bstart;
int  	bend;
{
  char c;
  /*
   * This catches a "dummy" last entry
   */
  if (astart > aend && bstart > bend)
  	return;
  c = (astart > aend) ? 'a' : (bstart > bend) ? 'd' : 'c';
  if (cflag) fputs("**************\n*** ", stdout);

  if (c == 'a' && !cflag)
	range(astart-1, astart-1, 0);	/* Addition: just print one odd # */
  else
	range(astart, aend, 0);		/* Print both, if different */
  if (!cflag) {
    putchar(c);
    if (!eflag) {
	if (c == 'd')
		range(bstart-1, bstart-1, 1); /* Deletion: just print one odd # */
	else
		range(bstart, bend, 1);	/* Print both, if different */
    }
  }
  putchar('\n');
  if (!eflag) {
  	fetch(oldseek, astart, aend, lenA, infd[0], 
	    cflag ? (c=='d' ? "- " : "! ") : "< ");
	if (cflag) {
	  fputs("--- ", stdout);
	  range(bstart, bend, 1);
	  fputs(" -----\n", stdout);
	} else if (astart <= aend && bstart <= bend)
  	  printf("---\n");
  }
  fetch(newseek, bstart, bend, lenB, infd[1], 
      cflag ? (c=='a' ? "+ " : "! ") : (eflag ? "" : "> "));
  if (eflag && bstart <= bend)
  	printf(".\n");
}


range(from, to, w)
int  	from;
int  	to;
int	w;
/*
 * Print a range
 */
{
  if (cflag) {
    if((from -= cflag) <= 0) from = 1;
    if((to += cflag) > len[w]) to = len[w];
  }
  if (to > from) {
	printf("%d,%d", from, to);
  } else if (to < from) {
	printf("%d,%d", to, from);
  } else {
	printf("%d", from);
  }
}


fetch(seekvec, start, end, trueend, fd, pfx)
long  	*seekvec;
register int  start;
register int  end;
int  	trueend;
FILE  	*fd;
char  	*pfx;
/*
 * Print the appropriate text
 */
{
  register int  i;
  register int	first;
  register int	last;

  if (cflag) {
	if((first = start - cflag) <= 0) first = 1;
	if((last = end + cflag) > trueend) last = trueend;
  } else {
	first = start;
	last = end;
  }
  if (fseek(fd, seekvec[first], 0) != 0) {
  	printf("?Can't read line %d at %08lx (hex) in file%c\n",
  	  start, seekvec[first],
  	  (fd == infd[0]) ? 'A' : 'B');
  }
  else {
  	for (i = first; i <= last; i++) {
  	  if (fgetss(text, sizeof text, fd) == NULL) {
  	  	printf("** Unexpected end of file\n");
  	  	break;
  	  }
#ifdef DEBUG
  	  printf("%5d: %s%s\n", i, pfx, text);
#else
  	  fputs((cflag && (i<start || i>end)) ? "  " : pfx, stdout);
  	  fputs(text, stdout);
	  putchar('\n');
#endif
  	}
  }  
}


/*
 * Input routine, read one line to buffer[], return TRUE on eof, else FALSE.
 * The terminating newline is always removed.  If "-b" was given, trailing
 * whitespace (blanks and tabs) are removed and strings of blanks and
 * tabs are replaced by a single blank.  Getline() does all hacking for
 * redirected input files.
 */
int
getline(fd, buffer)
FILE  	*fd;
char  	*buffer;
{
  register char  *top;
  register char  *fromp;
  register char  c;

  if (fgetss(buffer, sizeof text, fd) == NULL) {
  	*buffer = EOS;
  	return(TRUE);
  }
  if (fd == stdin)
  	fputss(buffer, tempfd);
  if (bflag || iflag) {
  	top = buffer;
  	fromp = buffer;
  	while ((c = *fromp++) != EOS) {
  	  if (bflag && (c == ' ' || c == '\t')) {
  	  	c = ' ';
  	  	while (*fromp == ' ' || *fromp == '\t')
  	  	  fromp++;
  	  }
  	  if (iflag)
  	  	c = tolower(c);
  	  *top++ = c;
  	}
  	if (bflag && top[-1] == ' ')
  	  top--;
  	*top = EOS;
  }
  return(FALSE);
}
static unsigned short crc16a[] = {
  0000000,  0140301,  0140601,  0000500,
  0141401,  0001700,  0001200,  0141101,
  0143001,  0003300,  0003600,  0143501,
  0002400,  0142701,  0142201,  0002100,  
};
static unsigned short crc16b[] = {
  0000000,  0146001,  0154001,  0012000,
  0170001,  0036000,  0024000,  0162001,
  0120001,  0066000,  0074000,  0132001,
  0050000,  0116001,  0104001,  0043000,
};

unsigned short
hash(buffer)
char  	*buffer;
/*
 * Return the CRC16 hash code for the buffer
 * Algorithm from Stu Wecker (Digital memo 130-959-002-00).
 */
{
  register unsigned short  crc;
  register char  	*tp;
  register short   	temp;

  crc = 0;
  for (tp = buffer; *tp != EOS;) {
  	temp = *tp++ ^ crc;  /* XOR crc with new char  */
  	crc = (crc >> 8)
  	  ^ crc16a[(temp & 0017)]
  	  ^ crc16b[(temp & 0360) >> 4];
  }
#ifdef  DEBUG_ALL
  printf("%06o: %s\n", (crc == 0) ? 1 : crc, buffer);
#endif
  return((crc == 0) ? (unsigned short) 1 : crc);
}  	


#ifdef vms
opendir(which, arg, okfd)
int  	which;  	/* Which file to open (0 or 1)  	*/
char  	**arg;  	/* File name argument, &argv[which]  */
FILE  	*okfd;  	/* File name (already open)  	*/
{
  register char  	*tp;
  register int  	c;
  register char  	*newname;

  fgetname(okfd, text);
  /*
   * Skip over device name
   */
  for (tp = text; (c = *tp) && c != ':'; tp++);
  if (c)  tp++;
  else  tp = text;
  /*
   * Skip over [UIC] or [PPN] if present
   */
  if (*tp == '[' || *tp == '(') {
  	while ((c = *tp++) && c != ']' && c != ')');
  	if (c == 0) {
  	  fprintf(stderr, "?Bug: bad file name \"%s\"\n",
  	  	  text);
  	  tp--;
  	}
  }
  strcpy(text, tp);
  /*
   * Don't include version
   */
  for (tp = text; (c = *tp) && c != ';'; tp++);
  *tp = 0;
  /*
   * Now, text has the file name, tp - text, its length,
   * and *arg the (possible) directory name.  Create a new
   * file name for opening.
   */
#ifndef	OSK
  if ((newname = malloc(tp - text + strlen(*arg) + 1)) == NULL)
  	error("Out of space at start");
#ifdef	AMIGA
    savsiz = tp - text + strlen(*arg) + 1;
    savptr = newname;
#endif
#else
  newname = myalloc(tp - text + strlen(*arg) + 1, "Out of space at start");
#endif
  concat(newname, *arg, text, NULL);
  if ((infd[which] = fopen(newname, "r")) == NULL)
  	cant(*arg, "constructed input", 1);
  else
  	*arg = newname;
}
/* Amiga C doesn't have all these extensions for directory... */
#endif

char *
myalloc(amount, why)
int  	amount;
char  	*why;
/*
 * Allocate or crash.
 */
{
  register char  *pointer;

#ifdef OSK
  amount += sizeof(int);
#endif
  if ((pointer = malloc((unsigned) amount)) == NULL)
  	noroom(why);
#ifdef OSK
  *((int *)pointer) = amount;
  pointer += sizeof(int);
#ifdef DEBUG
  fprintf(stderr, "Myalloc: %d at %06o\n", amount, pointer);
#endif
#endif
#ifdef	AMIGA
   savsiz =  amount;
   savptr = pointer;
#endif

  return (pointer);
}


/*
 * Reallocate pointer, compacting storage
 *
 * The "compacting storage" part is probably not relevant any more.
 * There used to be horrid code here that malloc'd one byte and freed
 * it at magic times, to cause garbage collection of the freespace
 * or something.  It's safely gone now, you didn't have to see it.
 *	-- John Gilmore, Nebula Consultants, Sept 26, 1986
 */
char *
compact(pointer, new_amount, why)
char  	*pointer;
int  	new_amount;
char  	*why;
{
  register char *new_pointer;

#ifndef AMIGA
#ifndef OSK
  extern char *realloc();

  if ((new_pointer =  realloc(pointer, (unsigned) new_amount)) == NULL){
  	noroom(why);
  }
#else
  register int old_amount;
  new_amount += sizeof(int);
  if((new_pointer = malloc(new_amount)) == NULL) noroom(why);
  *(int *)new_pointer = new_amount;
  new_pointer += sizeof(int);
  old_amount = *(((int *)pointer)-1);
  /* _strass is like bcopy with the first two arguments reversted */
  _strass(new_pointer, pointer, (new_amount <= old_amount ?
  	new_amount : old_amount) - sizeof(int));
#ifdef DEBUG
  fprintf(stderr, "compact %d to %d from %06o to %06o\n", 
	old_amount, new_amount, pointer, new_pointer);
#endif
  free(pointer - sizeof(int));
#endif
#else
  /*
   * This routine is heavily dependent on C storage allocator hacks
   * For Amiga, we can't rely on storage being left alone "up to"
   * the boundary of allocation as in VMS or RSX. Therefore we have
   * to be different here and allocate a new larger segment, then
   * free the old one. Messy but hopefully it will work.
   */
  extern char  *malloc();

  /* No realloc().  Do a malloc and copy it.  */
  if ((new_pointer = malloc((unsigned) new_amount)) == NULL){
  	noroom(why);
  }
/* This MUST assume the program calls compact using the old pointer as the
  last call of malloc... Reason is that RSX version is really simpleminded */
   cpysiz=savsiz;
/* Complain if deallocate area not same as last allocate area */
 if (savptr != pointer) bogus(why);
   wrk2=new_pointer;
   for (wrk=pointer;cpysiz > 0;cpysiz--){
/* copy data to new area */
     *wrk2++ = *wrk++;
     }
/* when done, free old memory area. */
   free(pointer);
#endif

#ifndef OSK
#ifdef  DEBUG
  if (new_pointer != pointer) {
  	fprintf(stderr, "moved from %06o to %06o\n",
  	  pointer, new_pointer);
  }
/*  rdump(new_pointer, why);
*/
#endif
#endif
  return (new_pointer);
}

noroom(why)
char  	*why;
{
  fprintf(stderr, "?DIFF-F-out of room when %s\n", why);
  exit(IO_ERROR);
}

#ifdef	AMIGA
bogus(why)
char  	*why;
{
  fprintf(stderr, "?DIFF-F-invalid compaction when %s\n", why);
  exit(IO_ERROR);
}
#endif

#ifdef  DEBUG
rdump(pointer, why)
int  	*pointer;
char  	*why;
/*
 * Dump memory block
 */
{
  int  *last;
  int  count;

  last = ((int **)pointer)[-1];
  fprintf(stderr, "dump %s of %06o -> %06o, %d words",
  	  why, pointer, last, last - pointer);
  last = (int *)(((int) last) & ~1);
  for (count = 0; pointer < last; ++count) {
  	if ((count & 07) == 0) {
  	  fprintf(stderr, "\n%06o", pointer);
  	}
  	fprintf(stderr, "\t%06o", *pointer);
  	pointer++;
  }
  fprintf(stderr, "\n");
}
#endif
cant(filename, what, fatalflag)
char  	*filename;
char  	*what;
int  	fatalflag;
/*
 * Can't open file message
 */
{
  fprintf(stderr, "Can't open %s file \"%s\": ", what, filename);
#ifndef	OSK
  perror((char *)NULL);
#else
  prerr(0, errno);
#endif
  if (fatalflag) {
  	exit(fatalflag);
  }
}
#ifdef  DEBUG
dump(d_linep, d_len, d_which)
LINE  *d_linep;
{
  register int i;
  
  printf("Dump of file%c, %d elements\n", "AB"[d_which], d_len);
  printf("linep @ %06o\n", d_linep);
  for (i = 0; i <= d_len; i++) {
  	printf("%3d  %6d  %06o\n", i,
  	  	d_linep[i].serial, d_linep[i].hash);
  }
}

dumpklist(kmax, why)
int  kmax;
char  *why;
/*
 * Dump klist
 */
{
  register int  	i;
  register CANDIDATE  *cp;
  register int  	count;

  printf("\nklist[0..%d] %s, clength = %d\n", kmax, why, clength);
  for (i = 0; i <= kmax; i++) {
  	cp = &clist[klist[i]];
  	printf("%2d %2d", i, klist[i]);
  	if (cp >= &clist[0] && cp < &clist[clength])
  	  printf(" (%2d %2d -> %2d)\n", cp->a, cp->b, cp->link);
  	else if (klist[i] == -1)
  	  printf(" End of chain\n");
  	else  printf(" illegal klist element\n");
  }
  for (i = 0; i <= kmax; i++) {
  	count = -1;
  	for (cp = (CANDIDATE *)klist[i]; cp > &clist[0]; 
		cp = (CANDIDATE *)&cp->link) {
  	  if (++count >= 6) {
  	  	printf("\n    ");
  	  	count = 0;
  	  }
  	  printf(" (%2d: %2d,%2d -> %d)",
  	  	cp-clist, cp->a, cp->b, cp->link);
  	}
  	printf("\n");
  }
  printf("*\n");
}
#endif
#ifdef  TIMING
ptime(why)
char  	*why;
/*
 * Dump time buffer
 */
{
  long  ttemp;

  ttemp = time(NULL);
  printf("%ld seconds for %s\n",
  	ttemp - sectiontime, why);
  sectiontime = ttemp;
}
#endif

/*
 *  	  s t r e q . c
 */
 
/*)LIBRARY
*/

#ifdef  DOCUMENTATION

title  streq  String Equality Test
index  	String equality test

synopsis
  .s.nf
  streq(a, b);
  char  	*a;
  char  	*b;
  .s.f
Description

  Return TRUE if the strings are equal.

Bugs

#endif

/* #define  EOS  0
#define  FALSE  0
#define  TRUE  1
*/
int
streq(s1, s2)
register char  *s1;
register char  *s2;
/*
 * TRUE if strings are identical
 */
{
  while (*s1++ == *s2) {
      if (*s2++ == EOS)
  	return (TRUE);
  }
  return (FALSE);
}
/*
 *  	  e r r o r . c
 */

/*)LIBRARY
*/

#ifdef  DOCUMENTATION

title  error  Fatal Error Exit
index  	Fatal error exit

synopsis
  .s.nf
  _error()

  error(format, args)
  char  	*format;
  .s.f
documentation

  Fatal error exits.  _error() halts, error() prints something
  on stderr and then halts.

bugs

  THIS DOES NOT WORK ON MANY SYSTEMS DUE TO EXTREMLY NON-PORTABLE CODE.
  Why oh why can't people learn to use varargs properly?  This code will
  blow up on OSK.  Fortunatly, it isn't used often...

#endif

/* VARARGS */
error(format, args)
char  	*format;
/*
 * Error message before retiring.
 */
{
  fprintf(stderr, format, &args);
  putc('\n', stderr);
  _error();
}

_error()
{
  exit(1);
}

/* #include  <stdio.h> */
  
fputss(s, iop)
register char *s;
register FILE *iop;
/*
 * Like fput() except that it puts a newline at the end of the line.
 */
{
#ifndef OSK
/*
 * Why wasn't this written like the OSK section?  What's the difference between
 * fputc and putc other than I've never heard of fputc?
 */
  register c;

  while (c = *s++)
    fputc(c, iop);
  fputc('\n', iop);
#else
  fputs(s, iop);
  putc('\n', iop);
#endif
}


/*
 * Fgetss() is like fgets() except that the terminating newline
 * is removed.
 */
char *fgetss(s, n, iop)
char *s;
register FILE *iop;
{
  register c;
  register char *cs;
  
  cs = s;
  /*
   * The getc in the next line used to be an "fgetc".  Change it back if
   * getc doesn't work on your system, though that would be odd.
   */
  while ((c = getc(iop))>=0 && --n>0) {
  	if (c=='\n')
  	  break;
  	*cs++ = c;
  }
  if (c<0 && cs==s)
  	return((char *)NULL);
  *cs = '\0';  	  /* Overwrite newline as null  */
  return(s);
}
SHAR_EOF
exit 0
Bob Larson	Arpa: Blarson@Ecla.Usc.Edu	blarson@skat.usc.edu
Uucp: {sdcrdcf,cit-vax}!oberon!skat!blarson
Prime mailing list:	info-prime-request%fns1@ecla.usc.edu
			oberon!fns1!info-prime-request