; /* "execute undiff.c"
lc -j73 -cfist -L undiff.c
delete undiff.o undiff.lnk
quit
*/

/*===========================================================================
 * UNDIFF.C -- Apply output from Lattice DIFF
 * Copyright © 1991 by Robert L. Pyron. All Rights Reserved.
 *
 *	You may use this program for any purpose at all. If you use any
 *	source code from this program, I ask that you give me credit in your
 *	documentation. I would be very pleased if somebody were to make
 *	improvements to this program and pass them on to the Amiga
 *	community.
 * 
 * Lattice DIFF outputs a sequence of commands for converting one text file
 * to another. Unfortunately, there is no program available which will apply
 * these diffs to the original file and yield the modified file. (Or maybe
 * there is, and I just didn't RTFM carefully enough!)
 * 
 * Anyway, this is something I hacked together in about six hours --
 * don't expect production-quality code here.
 *
 * Usage is:
 * 
 * 	UNDIFF < diff_file
 * 
 * There are very few bells and whistles here. The only one I can think of
 * offhand is that you may concatenate all of your diff files into one big
 * file, and feed that into UNDIFF.
 * 
 * If the diff file says "TRANSFORM foo/bar.c TO sna/fu.c", then that is
 * what you get -- no redirection of input or output files to somewhere
 * else. 
 * 
 * The destination directory must already exist.
 * 
 * If the program terminates due to an error, you will be left with a
 * malformed target file.
 * 
 * Error output is rather cryptic.
 *===========================================================================
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef	unsigned long	ULONG;

typedef	enum	_ParseState
{
	IDLE = 0,
	PARSE_TRANSFORM = 10,PARSE_COMMAND,
	DO_APPEND = 20,APPENDING_1,
	DO_CHANGE = 30,CHANGING_1,CHANGING_2,CHANGING_3,CHANGING_4,CHANGING_5,
	DO_DELETE = 40,DELETING_1,DELETING_2,
	DO_NODIFF = 50,
	ERROR = -1,IO_ERROR = -2,
	DONE = 99,QUIT = 100
} ParseState;

#define	BADIO	-2
#define	ENDFILE	-1
#define	EMPTY	 0
#define	BLANK	' '
#define	TAB	'\t'
#define	ASTER	'*'
#define	NODIFF	'N'
#define	DASH	'-'
#define	SRCLINE	'<'
#define	DSTLINE	'>'

char	srcname[256];	/* name of current source file */
char	dstname[256];	/* name of current destination file */

char	srctemp[256];	/* temp; src file specified in this command */
char	dsttemp[256];	/* temp; dst file specified in this command */

char	srcrange[32];	/* temp for decoding range specification */
char	dstrange[32];	/* temp for decoding range specification */

char	cmdbuf[256];	/* input buffer for dif file */
char	linebuf[256];	/* buffer for moving lines from file A to file B */

ULONG	src1, src2;	/* source line range specified by command */
ULONG	dst1, dst2;	/* dest line range specified by command */

ULONG	cmdline;	/* current line number in dif file */
ULONG	srcline;	/* current line number in source file */
ULONG	dstline;	/* current line number in destination file */

FILE	*cmdfp;
FILE	*srcfp;
FILE	*dstfp;

void main (int argc, char **argv);
int parse_transform (char *cmdbuf);
int parse_nodiff (char *cmdbuf);
int parse_append (char *cmdbuf);
int parse_change (char *cmdbuf);
int parse_delete (char *cmdbuf);
int parse_range (char *range, unsigned long *lo, unsigned long *hi);
int getdif (char *cmdbuf);
int getsrc (char *linebuf);
int putdst (char *linebuf);

/*-------------------------------------------------------------------------
 *-------------------------------------------------------------------------
 */

void	main (int argc, char **argv)
{
	ParseState	state = IDLE, laststate;
	char		*error = "";
	int		cmd, rc;

	cmdfp = stdin;	/****** LAZY LAZY LAZY ******/

loop:
	switch (state)
	{
	case IDLE:
		laststate = state;

		/*
		 * We're just waiting around for something to happen.
		 */
		error = "unknown command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case ENDFILE:	state = DONE;		break;
		case EMPTY:	state = IDLE;		break;
		case BLANK:	state = PARSE_TRANSFORM; break;
		case TAB:	state = PARSE_TRANSFORM; break;
		case ASTER:	state = PARSE_COMMAND;	break;
		case NODIFF:	state = PARSE_COMMAND;	break;
		default:	state = ERROR;		break;
		}
		break;

	case PARSE_TRANSFORM:
		laststate = state;

		/*
		 * Copy remainder of current file.
		 */
		if (srcfp && dstfp)
		{
			do
			{
				if ((rc = getsrc(linebuf)) == 0)
					rc = putdst(linebuf);
			}
			while (rc == 0);
			if (ferror(srcfp) || ferror(dstfp))
				{ state = IO_ERROR; break; }
		}

		/*
		 * Confirm that cmdbuf contains a "TO TRANSFORM" command.
		 * Close current src and dst files, and open new files.
		 */
		error = "bad command";
		if (parse_transform(cmdbuf))
			{ state = ERROR; break; }

		printf ("TRANSFORMING %s TO %s ...\n",
			srcname, dstname);

		if (srcfp) { fclose (srcfp); srcfp = NULL; }
		if (dstfp) { fclose (dstfp); dstfp = NULL; }

		srcfp = fopen (srcname,"r");
		dstfp = fopen (dstname,"w");
		srcline = dstline = 0;

		state = (srcfp && dstfp) ? IDLE : IO_ERROR;
		break;

	case PARSE_COMMAND:
		laststate = state;

		/*
		 * cmdbuf should contain an APPEND, CHANGE, or DELETE command.
		 */
		error = "bad command";
		if (parse_append(cmdbuf) == 0)
			state = DO_APPEND;
		else if (parse_change(cmdbuf) == 0)
			state = DO_CHANGE;
		else if (parse_delete(cmdbuf) == 0)
			state = DO_DELETE;
		else if (parse_nodiff(cmdbuf) == 0)
			state = DO_NODIFF;
		else
			state = ERROR;
		break;

	case DO_NODIFF:
		laststate = state;

		error = "inconsistent file name";
		if (stricmp(srcname,srctemp) || stricmp(dstname,dsttemp))
			{ state = ERROR; break; }

		/*
		 * Copy remainder of file.
		 */
		if (srcfp && dstfp)
		{
			do
			{
				if ((rc = getsrc(linebuf)) == 0)
					rc = putdst(linebuf);
			}
			while (rc == 0);
			if (ferror(srcfp) || ferror(dstfp))
				{ state = IO_ERROR; break; }
		}

		state = IDLE;
		break;

	case DO_APPEND:
		laststate = state;

		/*
		 * cmdbuf contains an APPEND command.
		 * Make sure file name agrees.
		 */
		error = "inconsistent file name";
		if (stricmp(srcname,srctemp))
			{ state = ERROR; break; }

		/*
		 * Copy unaffected lines.
		 */
		while (srcline < src1)
		{
			if (getsrc(linebuf) != 0)
				{ state = IO_ERROR; break; }
			if (putdst(linebuf) != 0)
				{ state = IO_ERROR; break; }
		}
		error = "inconsistent line count";
		if (srcline != src1)
			{ state = ERROR; break; }

		/*
		 * Get next dif line. Should begin with '>'.
		 */
		error = "bad command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case DSTLINE:	state = APPENDING_1;	break;
		default:	state = ERROR;		break;
		}
		break;

	case APPENDING_1:
		laststate = state;

		/*
		 * cmdbuf contains a line that should be output.
		 * Do so, then get next dif line.
		 */
		if (putdst(cmdbuf+1) != 0)
			{ state = IO_ERROR; break; }

		error = "bad command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case ENDFILE:	state = DONE;		break;
		case EMPTY:	state = IDLE;		break;
		case DSTLINE:	state = APPENDING_1;	break;
		default:	state = ERROR;		break;
		}
		break;

	case DO_CHANGE:
		laststate = state;

		/*
		 * cmdbuf contains a CHANGE command.
		 * Make sure file names agree.
		 */
		error = "inconsistent file name";
		if (stricmp(srcname,srctemp) || stricmp(dstname,dsttemp))
			{ state = ERROR; break; }

		/*
		 * Copy unaffected lines.
		 */
		while (srcline < src1-1 && dstline < dst1-1)
		{
			if (getsrc(linebuf) != 0)
				{ state = IO_ERROR; break; }
			if (putdst(linebuf) != 0)
				{ state = IO_ERROR; break; }
		}
		error = "inconsistent line count";
		if (srcline != src1-1 || dstline != dst1-1)
			{ state = ERROR; break; }

		/*
		 * Get next dif line. Should begin with '<'.
		 */
		error = "bad command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case SRCLINE:	state = CHANGING_1;	break;
		default:	state = ERROR;		break;
		}
		break;

	case CHANGING_1:
		laststate = state;

		/*
		 * cmdbuf contains a line that should be "deleted".
		 * Make sure it matches current line in src file.
		 */
		if (getsrc(linebuf) != 0)
			{ state = IO_ERROR; break; }
		error = "line does not match";
		if (strcmp(linebuf,cmdbuf+1))
			{ state = ERROR; break; }

		/*
		 * Get next dif line.
		 */
		error = "bad command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case SRCLINE:	state = CHANGING_1;	break;
		case EMPTY:	state = CHANGING_2;	break;
		default:	state = ERROR;		break;
		}
		break;

	case CHANGING_2:
		laststate = state;

		/*
		 * We've finished the "delete" component of the change.
		 * Look for separator line between "delete" and "append"
		 * lines.
		 */
		error = "inconsistent line count";
		if (srcline != src2)
			{ state = ERROR; break; }

		error = "bad command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case EMPTY:	state = CHANGING_2;	break;
		case DASH:	state = CHANGING_3;	break;
		default:	state = ERROR;		break;
		}
		break;

	case CHANGING_3:
		laststate = state;

		/*
		 * cmdbuf holds separator between "delete and "append" lines.
		 * Next line should begin with '>'.
		 */
		error = "bad command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case DSTLINE:	state = CHANGING_4;	break;
		default:	state = ERROR;		break;
		}
		break;

	case CHANGING_4:
		laststate = state;

		/*
		 * cmdbuf contains a line that should be output.
		 * Do so, then get next dif line.
		 */

		if (putdst(cmdbuf+1) != 0)
			{ state = IO_ERROR; break; }

		error = "bad command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case DSTLINE:	state = CHANGING_4;	break;
		case EMPTY:	state = IDLE;		break;
		default:	state = ERROR;		break;
		}
		break;

	case CHANGING_5:
		laststate = state;

		/*
		 * Check to see that we inserted the proper number of lines.
		 */
		error = "inconsistent line count";
		state = (dstline == dst2) ? IDLE : ERROR;
		break;


	case DO_DELETE:
		laststate = state;

		/*
		 * cmdbuf contains an APPEND command.
		 * Make sure file name agrees.
		 */
		error = "inconsistent file name";
		if (stricmp(srcname,srctemp))
			{ state = ERROR; break; }

		/*
		 * Copy unaffected lines.
		 */
		while (srcline < src1-1)
		{
			if (getsrc(linebuf) != 0)
				{ state = IO_ERROR; break; }
			if (putdst(linebuf) != 0)
				{ state = IO_ERROR; break; }
		}
		error = "inconsistent line count";
		if (srcline != src1-1)
			{ state = ERROR; break; }

		/*
		 * Get next dif line. Should begin with '<'.
		 */
		error = "bad command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case SRCLINE:	state = DELETING_1;	break;
		default:	state = ERROR;		break;
		}
		break;

	case DELETING_1:
		laststate = state;

		/*
		 * cmdbuf contains a line that should be "deleted".
		 * Make sure it matches current line in src file.
		 */
		if (getsrc(linebuf) != 0)
			{ state = IO_ERROR; break; }
		error = "line does not match";
		if (strcmp(linebuf,cmdbuf+1))
			{ state = ERROR; break; }

		/*
		 * Get next dif line.
		 */
		error = "bad command";
		switch (cmd = getdif (cmdbuf))
		{
		case BADIO:	state = IO_ERROR;	break;
		case SRCLINE:	state = DELETING_1;	break;
		case EMPTY:	state = DELETING_2;	break;
		default:	state = ERROR;		break;
		}
		break;

	case DELETING_2:
		laststate = state;

		/*
		 * Check to see that we deleted the proper number of lines.
		 */
		error = "inconsistent line count";
		state = (srcline == src2) ? IDLE : ERROR;
		break;

	case ERROR:
		printf ("*** ERROR: %s\n", error);
		printf ("state = %d  cmdline = %d  srcline = %d  dstline = %d\n",
			(int) laststate, cmdline, srcline, dstline);
		printf ("src1 = %d src2 = %d  dst1 = %d dst2 = %d\n",
			src1, src2, dst1, dst2);
		printf ("cmdbuf  = %s\n", cmdbuf);
		printf ("linebuf = %s\n", linebuf);
		printf ("srcrange = %s\n", srcrange);
		printf ("dstrange = %s\n", dstrange);

		rc = 100;
		state = QUIT;
		break;

	case IO_ERROR:
		printf ("*** I/O error\n");
		rc = 200;
		state = QUIT;
		break;

	case DONE:
		/*
		 * Copy remainder of file.
		 */
		if (srcfp && dstfp)
		{
			do
			{
				if ((rc = getsrc(linebuf)) == 0)
					rc = putdst(linebuf);
			}
			while (rc == 0);
			if (ferror(srcfp) || ferror(dstfp))
				{ state = IO_ERROR; break; }
		}

		rc = 0;
		state = QUIT;
		break;

	case QUIT:
		if (srcfp) { fclose (srcfp); srcfp = NULL; }
		if (dstfp) { fclose (dstfp); dstfp = NULL; }
		exit(rc);
		break;

	default:
		error = "unknown state!";
		state = ERROR;
		break;
	}
	goto loop;
}


/*-------------------------------------------------------------------------
 * Extract srcname and dstname from TRANSFORM command.
 *	"TO TRANSFORM <srcname> INTO <dstname> ..."
 *
 * Sets global variables "srcname" and "dstname".
 * Returns 0 on success.
 *-------------------------------------------------------------------------
 */

int	parse_transform (char *cmdbuf)
{
	int nc = sscanf (cmdbuf, " TO TRANSFORM %s INTO %s ...",
			srcname, dstname);
	return (nc != 2);
}

/*-------------------------------------------------------------------------
 * Extract srcname and dstname from NODIFF command.
 *	"NO DIFFERENCE BETWEEN <dst> AND <src>"
 *
 * Sets global variables "srctemp" and "dsttemp".
 * Returns 0 on success.
 *-------------------------------------------------------------------------
 */

int	parse_nodiff (char *cmdbuf)
{
	int nc = sscanf (cmdbuf, "NO DIFFERENCE BETWEEN %s AND %s",
			dsttemp, srctemp);
	return (nc != 2);
}

/*-------------------------------------------------------------------------
 * Extract line number and file name from APPEND command.
 *	"*** APPEND AFTER <n> IN <dst> ***"
 *
 * Returns 0 on success.
 *-------------------------------------------------------------------------
 */

int	parse_append (char *cmdbuf)
{
	int nc = sscanf (cmdbuf, "*** APPEND AFTER %s IN %s ***",
			srcrange, srctemp);

	return (nc == 2) ? parse_range(srcrange,&src1,&src2) : -1;
}

/*-------------------------------------------------------------------------
 * Extract info from CHANGE command.
 *	"*** CHANGE <range> IN <src> TO <range> IN <dst> ***",
 *
 * Returns 0 on success.
 *-------------------------------------------------------------------------
 */

int	parse_change (char *cmdbuf)
{
	int nc = sscanf (cmdbuf, "*** CHANGE %s IN %s TO %s IN %s ***",
			srcrange, srctemp, dstrange, dsttemp);
	if (nc != 4)
		return -1;
	else if (parse_range(srcrange,&src1,&src2) != 0)
		return -1;
	else if (parse_range(dstrange,&dst1,&dst2) != 0)
		return -1;
	return 0;
}

/*-------------------------------------------------------------------------
 * Extract line number and file name from DELETE command.
 *	"*** DELETE <range> FROM <src> ***",
 *
 * Returns 0 on success.
 *-------------------------------------------------------------------------
 */

int	parse_delete (char *cmdbuf)
{
	int nc = sscanf (cmdbuf, "*** DELETE %s FROM %s ***",
			srcrange, srctemp);

	return (nc == 2) ? parse_range(srcrange,&src1,&src2) : -1;
}

/*-------------------------------------------------------------------------
 * Parse a range specifier in the form "n" or "[n,n]".
 * Returns 0 on success.
 *-------------------------------------------------------------------------
 */

int	parse_range (char *range, ULONG *lo, ULONG *hi)
{
	*lo = *hi = -1;
	if (sscanf (range,"[%lu,%lu]", lo, hi) == 2)
		return 0;
	else if (sscanf (range,"%lu", lo) == 1)
	{
		*hi = *lo;
		return 0;
	}
	else
	{
		printf ("****** cannot scan range %s ******\n", range);
		return -1;
	}
}

/*-------------------------------------------------------------------------
 * Read line from dif file. Strip trailing newline.
 * Return first character (or negative value for error or eof).
 *-------------------------------------------------------------------------
 */

int	getdif (char *cmdbuf)
{
	int	len;

	if (fgets(cmdbuf,255,cmdfp))
	{
		cmdbuf[255] = '\0';
		len = strlen(cmdbuf);
		if (len && cmdbuf[--len] == '\n')
			cmdbuf[len] = '\0';
		cmdline++;
		return (int) cmdbuf[0];
	}
	else if (feof(cmdfp))
		return ENDFILE;
	else
		return BADIO;
}

/*-------------------------------------------------------------------------
 * Read line from source file; increment counter.
 *-------------------------------------------------------------------------
 */

int	getsrc (char *linebuf)
{
	char *p;
	int len;
	p = fgets(linebuf,255,srcfp);
	if (p && (len = strlen(p)) && (p[--len] == '\n'))
		p[len] = '\0';

	linebuf[255] = '\0';
	srcline++;
	return (p == NULL);
}

/*-------------------------------------------------------------------------
 * Write line to destination file; increment counter.
 *-------------------------------------------------------------------------
 */

int	putdst (char *linebuf)
{
	int rc = fputs (linebuf,dstfp);
	fputc ('\n',dstfp);
	dstline++;
	return rc;
}

