# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by  <sjw@hpfcsjw> on Fri Mar  3 08:34:22 1989
#
# This archive contains:
#	Makefile	catfiles.1	catfiles.c	cpstrip.c	
#	cstrip.1	linecounter	linecounter.1	osize.1		
#	osize.c		prepsize	prepsize.1	pstrip.1	
#	shstrip.1	shstrip.c	ssize.1		ssize.c		
#
# Error checking via wc(1) will be performed.
# Error checking via sum(1) will be performed.

LANG=""; export LANG

if sum -r </dev/null >/dev/null 2>&1
then
	sumopt='-r'
else
	sumopt=''
fi

echo x - Makefile
cat >Makefile <<'@EOF'
# version @(#) $Revision: 1.6 $   $Date: 89/03/01 08:41:49 $
# Makefile for linecounter metrics package.

MISC	= Ideas Tutorial Makefile
SCRIPT	= linecounter prepsize 
SOURCE	= ssize.c osize.c cpstrip.c shstrip.c catfiles.c
MANUAL	= prepsize.1 ssize.1 osize.1 cstrip.1 pstrip.1 shstrip.1 catfiles.1 linecounter.1
OBJECT	= ssize  osize  cstrip  pstrip  shstrip catfiles
BINDIR	= /usr/local/bin
MANDIR	= /usr/local/man/man1


all:	$(OBJECT)

ssize:	ssize.c
	cc -O -s -n -o ssize ssize.c

osize:	osize.c
	cc -O -s -n -o osize osize.c

cstrip:	cpstrip.c
	cc -O -s -n -o cstrip cpstrip.c

pstrip:	cpstrip.c
	cc -O -s -n -o pstrip cpstrip.c -DPASCAL

shstrip: shstrip.c
	cc -O -s -n -o shstrip shstrip.c

catfiles: catfiles.c
	cc -O -s -o catfiles catfiles.c

clean:
	rm -f core *.o $(OBJECT) sharchive*


# For listall, just do an ls.

manuals:
	for file in $(MANUALS); do nroff -man $$file; done | lp

install: $(OBJECT) $(SCRIPT) $(MANUAL)
	mv $(OBJECT) $(BINDIR)
	ln $(SCRIPT) $(BINDIR)
	ln $(MANUAL) $(MANDIR)
@EOF
set `sum $sumopt <Makefile`; if test $1 -ne 9848
then
	echo ERROR: Makefile checksum is $1 should be 9848
fi
set `wc -lwc <Makefile`
if test $1$2$3 != 451511009
then
	echo ERROR: wc results of Makefile are $* should be 45 151 1009
fi

chmod 444 Makefile

echo x - catfiles.1
cat >catfiles.1 <<'@EOF'
.TH catfiles 1
.ad b
.SH NAME
catfiles - dump the specified files to stdout.
.SH SYNOPSIS
catfiles  [file1 file2 ... filen]
.SH HP-UX COMPATIBILITY
.TP 10
Level:
Non-Standard
.TP
Origin:
HP
.SH DESCRIPTION
.B Catfiles
emulates \fBcat(1)\fR in the sense that the contents of the specified files
are printed to stdout.  However, catfiles has the added ability to
automatically extract and print specific versions from \fBRCS\fR and
\fBSCCS\fR source trees.
.PP
Input to catfiles consists of an arbitrary number of files containing lines
of the form:
.sp 1
	\fBpathname     [revision]\fR
.sp 1
\fBPathname\fR is the path name of the file to be printed, relative path
names are legal, and \fBrevision\fR specifies which version of the file is
to be retrieved from source code control.  Catfiles can also read input from
stdin.
.PP
Catfiles automatically retrieves a specified version from an RCS or SCCS
file by calling \fBco(1)\fR or \fBget(1)\fR respectively.  A file is
considered to be an RCS file if the base name of the specified path ends
with the suffix, ",v".  Similarly, a file is considered to be an SCCS file
if the base name begins with the prefix, "s.".
.PP
The optional revision number is intended for use with RCS or SCCS files.
The revision indicates which 'release.level' number should be retrieved from
the source tree.  For RCS files, the revision field may specify a symbolic
tag that is associated with a version of the file.  If a revision is not
specified with an RCS or SCCS file, the top most version of the file is
retrieved.  If the revision is included with a non RCS/SCCS file, it is
simply ignored.
.PP
Catfiles was originally designed to operate in a source line counting tool
package.  Other tools in this package include:  linecount(1), ssize(1),
chkfiles(1), cstrip(1) and pstrip(1).
.SH RETURN VALUE
A return code of 0 is returned if the program exits normally, otherwise, 1
is returned.
.SH SEE ALSO
cat(1), co(1), get(1), linecount(1), ssize(1), chkfiles(1), cstrip(1), 
pstrip(1)
.SH DIAGNOSTICS
Catfiles issues a warning message and continues if it cannot parse an input
line.  Catfiles issues an error message and terminates if the call to co, get,
or cat fails.  These failures are normally caused by specifying a file that
does not exist or a revision number that is not valid.  The program
chkfiles(1), can be used as a front end to filter out these types of
errors.
.SH AUTHOR
Scott Warren - HP-UX DCE Lab
.SH BUGS
@EOF
set `sum $sumopt <catfiles.1`; if test $1 -ne 29957
then
	echo ERROR: catfiles.1 checksum is $1 should be 29957
fi
set `wc -lwc <catfiles.1`
if test $1$2$3 != 634102456
then
	echo ERROR: wc results of catfiles.1 are $* should be 63 410 2456
fi

chmod 444 catfiles.1

echo x - catfiles.c
cat >catfiles.c <<'@EOF'
/*	
	Synopsis: catfiles  [files ...]
	
	Author: Scott J. Warren
	Date:   2/16/89

	Description:
	    Catfiles emulates cat(1) in the sense that the contents of the 
	    specified files are printed to stdout.  However, catfiles has the 
	    added ability to automatically extract and print specific versions 
	    from RCSR and BSCCS source trees.

	    Input to catfiles consists of an arbitrary number of files 
	    containing lines of the form:

	    pathname     [revision]

	    Pathname is the path name of the file to be printed and revision
	    specifies which version of the file is to be retrieved from source 
	    code control.  Catfiles can also read input from stdin.

	    Catfiles automatically retrieves a specified version from an RCS 
	    or SCCS file by calling co(1) or get(1) respectively.  A file is
	    considered to be an RCS file if the base name of the specified path
	    ends with the suffix, ",v".  Similarly, a file is considered to be
	    an SCCS file if the base name begins with the prefix, "s.".

	    Catfiles was originally designed to operate in a source line 
	    counting tool package.  Other tools in this package include:  
	    linecount(1), ssize(1), chkfiles(1), cstrip(1) and pstrip(1).

	    A return code of 0 is returned if the program exits normally, 
	    otherwise, 1 is returned.

	    Catfiles issues a warning message and continues if it cannot parse 
	    an input line.  Catfiles issues an error message and terminates if 
	    the call to co, get, or cat fails.  These failures are normally 
	    caused by specifying a file that does not exist or a revision 
	    number that is not valid.  The program chkfiles(1), can be used as 
	    a front end to filter out these types of errors.
*/



static char *version = "@(#) $Revision: 2.4 $   $Date: 89/02/28 15:30:06 $";

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <varargs.h>


/*
**************************************************************************
**			Constant Definitions				**
**************************************************************************
*/

#define	PROC				/* null macro useful for searching. */
#define	FALSE		0
#define	TRUE		1
#define	CHNULL	  ('\0')
#define	CHNEWLINE ('\n')
#define	CPNULL	  ((char *) NULL)
#define	FILENULL  ((FILE *) NULL)
#define RCSSUFF	  ",v"
#define DEFREL	  "0"
#define	CMD  0				/* for calling RunCommand()	*/
#define	PIPE 1
#define TOKENSEP  " \t"
#define	CHSIZE	  (sizeof(char))


/*
**************************************************************************
**			Global Variables    				**
**************************************************************************
*/

	char	*calloc();
	char	*myname;			/* how program was invoked	*/
	char	*defargv[] = { "-" };		/* default argument list	*/
	char	*tempfile = "/tmp/catfXXXXXX";  /* temporary file */

	int	exitnicely();


/*
**************************************************************************
**			Macro Definitions				**
**************************************************************************
* Basename is either the full string or past last '/', if any.
* File is SCCS if basename starts with "s.".
*/

char	*cpBNTemp;			/* temporary */

#define	BASENAME(cp)	\
	(((cpBNTemp = strrchr (cp, '/')) == CPNULL) ? cp : (cpBNTemp + 1))

#define	SCCS(filename)	(strncmp (BASENAME (filename), "s.", 2) == 0)





/*
**************************************************************************
**				Error					**
**************************************************************************

Print an error message to stderr and exit nonzero.  Message is preceded
by "<myname>: " using global char *myname, and followed by a newline.

Input:	message, arg1, arg2, arg3, arg4  - components of the error message.
Output: None.
Return: None.
Globs: 	myname, tempfile
*/

/* VARARGS */
PROC Error (message, arg1, arg2, arg3, arg4)
	char	*message;
	int	arg1, arg2, arg3, arg4;
{
	fprintf (stderr, "%s: ", myname);
	fprintf (stderr, message, arg1, arg2, arg3, arg4);
	fputc   (CHNEWLINE, stderr);

	exitnicely (1, tempfile);

} /* Error */

/*
**************************************************************************
**				warn					**
**************************************************************************

Print a warning message to stderr.  Message is preceded by "<myname>: " 
using global myname, and followed by a newline.

Input:	message, arg1, arg2, arg3, arg4  - components of the error message.
Output: None.
Return: None.
Globs: 	myname
*/

/* VARARGS */
PROC warn (message, arg1, arg2, arg3, arg4)
	char	*message;
	int	arg1, arg2, arg3, arg4;
{
	fprintf (stderr, "%s: warning: ", myname);
	fprintf (stderr, message, arg1, arg2, arg3, arg4);
	fputc   (CHNEWLINE, stderr);

} /* warn */


/*
**************************************************************************
**			    exitnicely					**
**************************************************************************

Remove the temporary file, if any (ignore errors), and exit with a given
value (maybe a signal number if called due to a signal).

Input:	exitval		The value returned to the caller.
	tempfile	The temporary file to unlink.
Output: None.
Return: None.
Globs: 	No globals.
*/

PROC exitnicely (exitval, tempfile)
	int	exitval;
	char	*tempfile;
{
	unlink (tempfile);

	exit (exitval);

} /* exitnicely */


/*
**************************************************************************
**				RCS					**
**************************************************************************

Determine if the given path name represents a file under rcs(1) control.

Input:	fname		The path name in question.
Output: None.
Return: TRUE or FALSE.
Globs: 	No globals.
*/


PROC int RCS(fname)
	char	*fname;
{
	int	rc = FALSE;

	if (strlen(fname) > 1)
	    if (strcmp(fname + strlen(fname) - 2, RCSSUFF) == 0)
		rc = TRUE;

	return(rc);
}


/*
**************************************************************************
**				get_sccs_file				**
**************************************************************************

Extract the correct version of the specified file from sccs via get(1).  The
extracted code is stored into the temporary file, tempfilename.  If the user
has not provided any release number, the top of the SCCS file is retrieved.
If an error occurs during the check out, the program terminates.

Input:	tempfilename	Where to store the extracted file.
	sccsfilename	The SCCS file in question.
	release		The release to extract.
Output: None.
Return: None.
Globs: 	No globals.
*/

PROC get_sccs_file (tempfilename, sccsfilename, release)
   	char	*tempfilename;	
   	char	*sccsfilename;
   	char	*release;
		
{
   	char 	*relopt; 	/* option string for get(1)  */
	char	*cmd;
   	int	result;		/* of get command	    */

	/*
 	** Set up the release option string.
 	*/

	if (release != CPNULL)			/* release given */
	{
	    relopt = calloc(strlen("-t -r") + strlen(release) + 1, CHSIZE);
	    sprintf (relopt, "-t -r%.*s%c", strlen("-t -r") + strlen(release) + 1, release, CHNULL);
	}
	else					/* use top level, i.e relopt is blank */
	{
	    relopt = calloc(strlen(" ") + 1, CHSIZE);
	    sprintf (relopt, "%.*s%c", strlen(" ") + 1, " ", CHNULL);
	}

	/*
 	*  Execute the get(1) command.
 	*/

	cmd = calloc(strlen("get -k -s -p ") + strlen(relopt) + strlen(sccsfilename) +
		     strlen(" > ") + strlen(tempfilename) + 1, CHSIZE);

	sprintf(cmd, "get -k -s -p %s %s > %s%c", relopt, sccsfilename, tempfilename, CHNULL);

	result = system(cmd);

	if (result) 
	    Error("get %s of file \"%s\" failed", sccsfilename, tempfilename);
	
	free(cmd);

} /* get_sccs_file */


/*
**************************************************************************
**			    get_rcs_file				**
**************************************************************************

Extract the correct version of the specified file from rcs via co(1).  The
extracted code is stored into the temporary file, tempfile.  If the user
has not provided any release number, the top of the RCS file is retrieved.
If an error occurs during the check out, the program terminates.

Input:	tempfilename	Where to store the extracted file.
	rcsfilename	The RCS file in question.
	release		The release to extract.
Output: None.
Return: None.
Globs: 	No globals.
*/

PROC get_rcs_file (tempfilename, rcsfilename, release)
   	char	*tempfilename;	
   	char	*rcsfilename;
   	char	*release;
		
{
   	char 	*relopt; 	/* option string for co(1)  */
	char	*cmd;
   	int	result;		/* of co command	    */

	/*
 	** Set up the release option string.
 	*/

	if (release != CPNULL)			/* release given */
	{
	    relopt = calloc(strlen("-r") + strlen(release) + 1, CHSIZE);
	    sprintf (relopt, "-r%.*s%c", strlen("-r") + strlen(release) + 1, release, CHNULL);
	}
	else					/* use top level, i.e relopt is blank */
	{
	    relopt = calloc(strlen(" ") + 1, CHSIZE);
	    sprintf (relopt, "%.*s%c", strlen(" ") + 1, " ", CHNULL);
	}

	/*
 	*  Execute the co(1) command.
 	*/

	cmd = calloc(strlen("co -q -p ") + strlen(relopt) + strlen(rcsfilename) +
		     strlen(" > ") + strlen(tempfilename) + 1, CHSIZE);

	sprintf(cmd, "co -q -p %s %s > %s%c", relopt, rcsfilename, tempfilename, CHNULL);

	result = system(cmd);

	if (result) 
	    Error("co %s of file \"%s\" failed", rcsfilename, tempfilename);

	free(cmd);

} /* GetRcsFile */



/*
**************************************************************************
**			      read_file					**
**************************************************************************

Given a filename (for error messages) and an open stream pointer, read
data lines from the stream until exhausted, dumping the contents of each
file to stdout.  Files are extracted from source control if necessary.

char line[] is static so it's only allocated once.

Input:	filename	The name of the input file.
	filep		The input file stream.
	tempfile	The name of the temporary file where code will
			be stored if extracted from source control.
Output: None.
Return: None.
Globs: 	No globals.
*/

PROC read_file (filename, filep, tempfile)
	char	*filename;			/* for error messages 	*/
   	FILE	*filep;				/* open stream	      	*/
	char	*tempfile;
{
static	char	line [BUFSIZ];			/* input line		*/
   	char	*cp;				/* pos in line		*/
	char	*dofilename;			/* the file to dump	*/
	char	*rev;				/* revision number	*/
	char	*cmd;				/* cat command string	*/
	int	cmdlength=200;			/* length of cat cmd	*/
   	int	linenum = 0;			/* in file		*/
	int	rc;


	cmd = calloc(cmdlength, CHSIZE);

	while (fgets (line, BUFSIZ, filep) != CPNULL)
	{
	    linenum++;
	    if ((cp = strchr (line, CHNEWLINE)) != CPNULL)
		*cp = CHNULL;			/* remove newline char */
	    
	    /*
	    ** Parse the line into tokens, i.e. the file to dump and an 
	    ** optional revision number. 
	    */

	    if ((dofilename = strtok(line, TOKENSEP)) == CPNULL)
		warn("invalid input in file \"%s\", line %d", filename, linenum);

	    else {
		rev = strtok(CPNULL, TOKENSEP);

		if (RCS(dofilename)) {
		    get_rcs_file(tempfile, dofilename, rev);
		    dofilename=tempfile;
		    }
		else if (SCCS(dofilename)) {
		    get_sccs_file(tempfile, dofilename, rev); 
		    dofilename=tempfile;
		    }

		if ((strlen(dofilename) + 4) > cmdlength) {		/* allocate more space */
		    cmdlength = strlen("cat ") + strlen(dofilename) + 1;
		    cmd = calloc(cmdlength, CHSIZE);
		    }

		sprintf(cmd, "cat %s%c", dofilename, CHNULL);
		rc = system(cmd);

		if (rc != 0)
		    Error("cat of file \"%s\" failed", dofilename);
		}
	}

	if (ferror (filep))
	    warn("read failed from \"%s\"", filename);

} /* ReadFile */


/*
**************************************************************************
**				Main					**
**************************************************************************
*/



PROC main (argc, argv)
   	int	argc;
   	char	**argv;
{
extern	int	optind;			/* from getopt()	*/
extern	char	*optarg;		/* from getopt()	*/
   	FILE	*filep;			/* open input file	*/
   	char	*filename;		/* name to use		*/

	myname = *argv;
	argv ++;			/* Skip past prog name. */
	argc --;

	/*
	**  Prepare the temporary file and signals.
	**
	**  The temporary file is initialized here, but the file itself is created
	**  when first needed.  Set signals if they're not already ignored.
	*/

	mktemp (tempfile);

	if (signal (SIGHUP,  SIG_IGN) != SIG_IGN)
	    signal (SIGHUP, exitnicely, tempfile);

	if (signal (SIGINT,  SIG_IGN) != SIG_IGN)
	    signal (SIGINT, exitnicely, tempfile);

	if (signal (SIGQUIT,  SIG_IGN) != SIG_IGN)
	    signal (SIGQUIT, exitnicely, tempfile);

	signal (SIGTERM, exitnicely, tempfile);		/* unconditional */

	/*
	**  Read from list of files or stdin.
	*/

	if (argc < 1) {				/* no file names */
	    argc = 1;
	    argv = defargv;
	    }

	while (argc-- > 0) {
	    if (strcmp (*argv, "-") == 0) {
		filename = "<stdin>";
		filep	 = stdin;
		}
	    else if ((filep = fopen ((filename = *argv), "r")) == FILENULL)
		Error("can't open \"%s\" to read it", filename);

	    read_file(filename, filep, tempfile);

	    if (filep != stdin)
		fclose (filep);			/* assume it works */

	    argv++;
	    }	/* while */

	exitnicely (0, tempfile);

} /* main */
@EOF
set `sum $sumopt <catfiles.c`; if test $1 -ne 53466
then
	echo ERROR: catfiles.c checksum is $1 should be 53466
fi
set `wc -lwc <catfiles.c`
if test $1$2$3 != 491176113187
then
	echo ERROR: wc results of catfiles.c are $* should be 491 1761 13187
fi

chmod 444 catfiles.c

echo x - cpstrip.c
cat >cpstrip.c <<'@EOF'
static char *version = "@(#) $Revision: 3.3 $   $Date: 89/02/23 16:01:06 $";
/*
 * Strip comments and blank lines from C [Pascal] source.
 * See the manual entry for details.
 *
 * Compile with -DPASCAL to get a Pascal stripper instead, in which case
 * some comments in "[]" apply, and see the other manual entry for details.
 */

#include <stdio.h>

#ifdef PASCAL
#undef C
#else
#define C 1
#endif

#define	PROC				/* null; easy to find procs */
#define	FALSE	  0
#define	TRUE	  1
#define	CHNULL	  ('\0')
#define	CHNEWLINE ('\n')
#define	CPNULL	  ((char *) NULL)
#define	FILENULL  ((FILE *) NULL)
#define	REG	  register

char	*myname;			/* how program was invoked */
char	*defargv[] = { "-" };		/* default argument list   */
int	rflag = FALSE;			/* -r (report) option	   */

#ifdef C
#define	CHOPEN  '/'			/* part of "/*"			   */
#define	CHCLOSE '/'			/* part of <can't mention it here> */

#else /* PASCAL */
#define	CHOPEN  '('			/* part of "(*" */
#define	CHCLOSE ')'			/* part of "*)" */

int	mflag = FALSE;			/* -m (MODCAL) option */
#endif


/************************************************************************
 * STATE DEFINITIONS:
 *
 * Each input character drives a state machine with these states (starting
 * each file at NORMAL).  The state machine itself is embedded in code.
 *
 * Assumes TRANSOUT resets to COMMENT at end of line; all other states except
 * COMMENT and DBSLASH reset to NORMAL.  In other words, transitions and
 * quoting don't continue across a newline (not allowed in C [or Pascal]).
 *
 * Naked backslashes are not legal in C *except* in macro definitions,
 * where they can convert a "comment" to a non-comment, e.g.:
 *
 *	#define X abc\/*test
 *
 * It's obscure, but must be supported.
 */

#define	NORMAL		0	/* now in typical text			*/
#define	TRANSIN		1	/* found CHOPEN, looking for '*'	*/
#define	COMMENT		2	/* now in comment			*/
#define	TRANSOUT	3	/* found '*', looking for CHCLOSE	*/
#define	BSLASH		4	/* naked backslash [C only]		*/
#define	SQUOTE		5	/* in single quotes			*/
#define	DQUOTE		6	/* in double quotes [C only]		*/
#define	SBSLASH		7	/* backslash in single quotes [C only]	*/
#define	DBSLASH		8	/* backslash in double quotes [C only]	*/


/************************************************************************
 * M A I N
 *
 * Open each file, read it, check for error, close it.
 */

PROC main (argc, argv)
REG	int	argc;
REG	char	**argv;
{
REG	FILE	*filep;			/* open input file	        */
REG	char	*filename;		/* name to use		        */
	int	lcount  = 0;		/* total number of lines        */
	int	blcount = 0;		/* number of blank lines        */
	int	comcount= 0;		/* number of whole comment lines*/

/*
 * CHECK ARGUMENTS:
 */

	myname = *argv++;

#ifdef C
	if ((argc >= 2) && (strcmp (*argv, "-r") == 0))
	{
	    rflag = TRUE;
	    argc--;
	    argv++;
	}

#else	/* PASCAL */

	if ((argc >= 2) && ((strcmp (*argv, "-r") == 0) || (strcmp(*argv, "-m") == 0)))
	{
	    if (strcmp(*argv, "-r") == 0)
		rflag = TRUE;
	    else
		mflag = TRUE;
	    argc--;
	    argv++;
	}

	if ((argc >= 2) && ((strcmp (*argv, "-r") == 0) || (strcmp(*argv, "-m") == 0)))
	{
	    if (strcmp(*argv, "-r") == 0)
		rflag = TRUE;
	    else
		mflag = TRUE;
	    argc--;
	    argv++;
	}
#endif

	if (--argc < 1)				/* no file names */
	{
	    argc = 1;
	    argv = defargv;			/* use default */
	}

/*
 * OPEN, READ, AND CLOSE EACH FILE:
 */

	while (argc-- > 0)
	{
	    if (strcmp (*argv, "-") == 0)	/* read stdin */
	    {
		filename = "<stdin>";
		filep	 = stdin;
	    }
	    else if ((filep = fopen ((filename = *argv), "r")) == FILENULL)
		Warn ("can't open file \"%s\" to read it", filename);

	    if (filep != FILENULL) 
	    {
		if (ReadFile (filep, &lcount, &blcount, &comcount))
		    Warn("file \"%s\" ends within a comment\n", filename);

		if (ferror (filep))
		    Warn ("read failed from file \"%s\"", filename);

		else if (filep != stdin)
		    fclose (filep);			/* assume it works */
	    }

	    argv++;
	}

	if (rflag) 
	{
	    printf("Total lines:           %6d\n", lcount);
	    printf("Whole comment lines:   %6d\n", comcount);
	    printf("Blank lines:           %6d\n", blcount);
	}

	exit (0);

} /* main */


/************************************************************************
 * W A R N
 *
 * Print a warning message to stdout.  Message is preceded
 * by "<myname>: " using global char *myname, and followed by a newline.
 */

/* VARARGS */
PROC Warn (message, arg1, arg2, arg3, arg4)
	char	*message;
	int	arg1, arg2, arg3, arg4;
{
	fprintf (stderr, "%s: ", myname);
	fprintf (stderr, message, arg1, arg2, arg3, arg4);
	fputc   (CHNEWLINE, stderr);

} /* Error */


/************************************************************************
 * R E A D   F I L E
 *
 * Given an open stream pointer, read characters from the stream until
 * exhausted, driving the state machine and emitting only non-comment
 * characters on non-blank lines to stdout.  The caller must check for
 * read errors.  Return non-zero if the file ends in the middle of a
 * comment.
 *
 * This is one large procedure (gag) for efficiency.
 */

PROC int ReadFile (filep, lcount, blcount, comcount)
REG	FILE	*filep;			/* open stream to read	  */
	int	*lcount;
	int	*blcount;
	int	*comcount;
{
REG	int	state = NORMAL;		/* current machine state  */
REG	int	stuff = FALSE;		/* "stuff" on input line? */
REG	int	ch = ' ';		/* current input "char"   */
	int	lastcom = 0;
	int	comend = 0;

static	char	outline [BUFSIZ];	/* allocate only once	  */
REG	char	*cp = outline;		/* place in outline	  */

#ifdef PASCAL
REG	int	brace;			/* comment type is brace? */
#endif

#define	NONBLANK(ch)	((ch != ' ') && (ch != '\t'))

#define	PUTCHAR(ch)	{ *cp++ = ch; if (NONBLANK (ch)) stuff = TRUE; }
#define	PUTCONS(ch)	{ *cp++ = ch; stuff = TRUE; }


	while (ch != EOF)
	{

/**************************************
 * HANDLE END OF FILE OR INPUT LINE:  *
 **************************************/

	    if (((ch = getc (filep)) == EOF) || (ch == CHNEWLINE))
	    {
		if (state == TRANSIN)		/* were in transition, but */
		    PUTCONS (CHOPEN);		/* wasn't a comment start  */

		if (state == TRANSOUT)		/* reset state if needed */
		    state = COMMENT;
#ifdef C
		else if (state == DBSLASH)	/* special case, stay quoted */
		    state = DQUOTE;
#endif
		else if (state != COMMENT)
		    state = NORMAL;

/*
 * PRINT LINE IF NEEDED:
 */


		if (stuff)			/* non-blank char(s) on line */
		{
		    stuff = FALSE;
		    *cp   = CHNULL;		/* mark end of line	   */
		    comend = lastcom = *lcount;
		    if (! rflag)
		        puts (outline);		/* adds newline if missing */
		}
		else
		    if (ch != EOF)
		    {
			if (state == COMMENT)
			{
			    (*comcount)++;
			    lastcom = *lcount;
			}
			else if (comend > lastcom)	/* comment ended, nothing else on the line. */
			{
			    (*comcount)++;
			    comend = lastcom = *lcount;
			}
			else
			    (*blcount)++;
		    }

		cp = outline;
		(*lcount)++;
	    }
	    else
	    {


/**************************************
 * HANDLE CHARACTERS ON THE LINE      *
 **************************************/

/*
 * TRANSITION TO COMMENT, LOOK FOR SECOND CHAR:
 */

		switch (state)
		{
		    case TRANSIN:
			if (ch == '*')
			{
			    state = COMMENT;
#ifdef PASCAL
			    brace = FALSE;
#endif
			    break;			/* do next char */
			}

			PUTCONS (CHOPEN);		/* previous char */
			state = NORMAL;

			/* fall through! */

/*
 * IN NORMAL STATE, LOOK FOR COMMENT OR QUOTE:
 */

		    case NORMAL:
			if (ch == CHOPEN)
			{
			    state = TRANSIN;
			    break;			/* skip this char */
			}
			else if (ch == '\'')
			    state = SQUOTE;
#ifdef C
			else if (ch == '"')
			    state = DQUOTE;
			else if (ch == '\\')
			    state = BSLASH;
#else /* PASCAL */
			else if (ch == '{')
			{
			    state = COMMENT;
			    brace = TRUE;
			    break;			/* skip this char */
			}
#endif
		    /* else nothing special */

			PUTCHAR (ch);
			break;

/*
 * IN COMMENT, LOOK FOR END:
 */

		    case COMMENT:
#ifdef C
			if (ch == '*')
			    state = TRANSOUT;
#else /* PASCAL */
			if ((ch == '*') && ((! mflag) || (! brace)))
			    state = TRANSOUT;

			else if ((ch == '}') && ((! mflag) || brace))
			{
			    state = NORMAL;
			    comend = *lcount;
			}
#endif
			break;

/*
 * TRANSITION OUT, LOOK FOR SECOND CHAR:
 */

		    case TRANSOUT:
			if (ch == CHCLOSE)
			{
			    state = NORMAL;
			    comend = *lcount;
			}

			else if (ch != '*')
			    state = COMMENT;

			break;

#ifdef C
/*
 * AFTER BACKSLASH, "IGNORE" NEXT CHAR:
 */

		    case BSLASH:
			state = NORMAL;
			PUTCHAR (ch);
			break;
#endif

/*
 * IN QUOTES, LOOK FOR CLOSE QUOTE:
 */

		    case SQUOTE:
			if (ch == '\'')
			    state = NORMAL;
#ifdef C
			else if (ch == '\\')
			    state = SBSLASH;
#endif
			PUTCHAR (ch);
			break;

#ifdef C
		    case DQUOTE:
			if (ch == '"')
			    state = NORMAL;
			else if (ch == '\\')
			    state = DBSLASH;
			PUTCHAR (ch);
			break;

/*
 * IN QUOTES AFTER BACKSLASH, "IGNORE" NEXT CHAR:
 */

		    case SBSLASH:
			state = SQUOTE;
			PUTCHAR (ch);
			break;

		    case DBSLASH:
			state = DQUOTE;
			PUTCHAR (ch);
			break;
#endif
		} /* switch */
	    }
	} /* while */

	(*lcount)--;
	return (state == COMMENT);

} /* ReadFile */
@EOF
set `sum $sumopt <cpstrip.c`; if test $1 -ne 23329
then
	echo ERROR: cpstrip.c checksum is $1 should be 23329
fi
set `wc -lwc <cpstrip.c`
if test $1$2$3 != 43214799217
then
	echo ERROR: wc results of cpstrip.c are $* should be 432 1479 9217
fi

chmod 444 cpstrip.c

echo x - cstrip.1
cat >cstrip.1 <<'@EOF'
.TH CSTRIP 1 "HP Experimental, Version 2"
.SH NAME
cstrip \- strip comments and blank lines from C source
.SH SYNOPSIS
.B cstrip
[
.I -r
]
[
.I files...
]
.ad b
.SH DESCRIPTION
This command reads the named files
(you can use "\fB\-\fR" for standard input),
or from standard input if no filenames are given,
expecting syntactically correct C source code.
It strips comments and blank lines and writes results to standard output.
Output consists solely of "non-comment source lines"
(including compiler directives).
A source file processed by
.I cstrip
can be compiled with the same results as compiling the original version.
.PP
All characters in comments,
including the comment delimiters,
are deleted.
Comments begin with the unquoted string (token) "/\(**".
The token is quoted (thus ignored) if preceded by a backslash
(as in a macro definition)
or if inside a character or string constant,
i.e. after a single or double quote mark on the same input line
(or a line continued with a backslash)
which is itself unbalanced and unquoted.
Comments end with the next "\(**/" string, regardless of quoting.
This is carefully arranged to agree with
the C preprocessor,
.IR cpp (1).
.PP
After comments are removed,
.I cstrip
removes any blank lines,
i.e. those containing only blanks or tabs, if anything.
However, trailing whitespace on lines,
such as that between a line of code and its trailing comment,
is not removed.
.PP
If the \fB-r\fR (report) option is specified, cstrip reports how many blank
lines, whole comment lines and the total number of lines that are found in the 
input.  The stripped code is not generated in this case.
.SH SEE ALSO
cpp(1), pstrip(1), shstrip(1)
.SH WARNINGS
If the last input line does not end in a newline,
one is added as a side-effect.
.PP
The C preprocessor gives precedence to comments or conditionals
(e.g. "#ifdef")
in the order encountered.
A commented-out conditional does not lead to problems,
but a conditionally-incomplete comment
(i.e. only the start or end of a comment is inside the conditional)
can lead to non-compilable stripped code because
.I cstrip
doesn't recognize conditionals as anything special.
.SH DIAGNOSTICS
Prints a warning message to stderr and continues if it
cannot open a named file or if a read fails.
Prints a message to stderr and continues if any input file ends
in the middle of a comment (something might be wrong).
@EOF
set `sum $sumopt <cstrip.1`; if test $1 -ne 21572
then
	echo ERROR: cstrip.1 checksum is $1 should be 21572
fi
set `wc -lwc <cstrip.1`
if test $1$2$3 != 703992390
then
	echo ERROR: wc results of cstrip.1 are $* should be 70 399 2390
fi

chmod 444 cstrip.1

echo x - linecounter
cat >linecounter <<'@EOF'
#!/bin/ksh
# Version: @(#)$Revision: 1.14 $   $Date: 89/03/02 14:34:10 $
# 	
# Synopsis:
#	
# Author: Scott J. Warren
# Date:	  2/27/89
#
# Description:
#	Assumes catfiles, cstrip, ssize, ckfiles are in path.



# 	******************************************************************
# 	*() 			   quit					**
# 	******************************************************************
# 
# 	Quit is called when a trappable error is detected.  Any temporary
#	files that may have been created are removed.

quit() {
	rm -rf $TEMPDIR
	exit  $1
} 



# 	******************************************************************
# 	*() 			   main 				**
# 	******************************************************************
#
##	Set up the quit routine as the interrupt handler and initialize
##	variables.

	echo "Initializing" 1>&2
	trap "quit 1" 1 2 3 4 6 7 12 13 15 16 17 19

	TEMPDIR="/tmp/LinCNt$$"
	rm -rf $TEMPDIR
	mkdir $TEMPDIR
	INPUT="$TEMPDIR/input"
	BAD="$TEMPDIR/bad"
	GOOD="$TEMPDIR/good"
	TEMP="$TEMPDIR/temp"
	STRIP="$TEMPDIR/strip"

	MODE="all"
	CHECK="on"
	SOPT="-S"
	STRIPPER="cstrip -r"
	TITLE=" "
	BOPT=" "

##	Parse the command line parameters.  Stop as soon as anything that
##	is not a recognized parameter is found.  This is assumed to be an
##	input file name.

	DONE="false"
	while [ $DONE = "false" ] ; do
	    if [ $# -gt 0 ] ; then		# Something left to parse.
		case $1 in
		    "-bcoff"	) MODE="bcoff"
	    			  shift;;	
		    "-bconly"	) MODE="bconly"
	    			  shift;;	
		    "-ckonly"	) MODE="ckonly"
	    			  shift;;	
		    "-ckoff"	) CHECK="off"
	    			  shift;;	
		    "-P"	) STRIPPER="pstrip -r"
				  shift;;
		    "-T"	) shift
				  if [ $# -ne 0 ] ; then
				      TITLE=$1
				      shift
				  else
				      echo "linecounter: -T option specified but no title provided."
				      quit 1
				  fi ;;
		    "-b"	) BOPT="-b"
				  shift;;
		    "-s"	) shift
				  if [ $# -ne 0 ] ; then
				      STRIPPER="$1 -r"
				      SOPT="-s $1"
				      shift
				  else
				      echo "linecounter: -s option specified but no stripper provided."
				      quit 1
				  fi ;;
		    *		) DONE="true"
		esac
	    else
		DONE="true"
	    fi
	done

##	Build an input file, either by merging all of the user specified
##	files together, or by reading from stdin.

	if [ $# -gt 0 ] ; then		# user has specified file(s).
	    while [ $# -gt 0 ] ; do
		cat $1 >> $INPUT
		shift
	    done
	else				# read from stdin.
	    DONE="false"
	    while read LINE ; do
		echo $LINE >> $INPUT
	    done
	fi

##	Run the given file names through the existance checker, capturing
##	the bad names in $BAD.  Then remove the bad input lines from 
##	$INPUT.  Terminate if the "check only" flag is set.

	if [ $CHECK = "on" ] ; then
	    echo "Checking files" 1>&2

	    # >>>>>>>>>>Use tee in the real thing. <<<<<<<<<<<

#	    cp bogus $BAD
#
#   	    cat $BAD |& 
#   	    while read -p X1 X2 X3 NAME ; do	# ignore first 3 fields.
#		fgrep -v "$NAME" $INPUT > $TEMP
#		mv $TEMP $INPUT
#	    done

	    if [ $MODE = "ckonly" ] ; then
		quit 0
	    fi
	fi
	
##	Count the number of blank lines and whole comment lines are in the
##	good files.

	if [ $MODE != "bcoff" ] ; then
	    echo "Counting blank lines and whole comment lines." 1>&2
	    cat $INPUT | awk '{print $2 " " $3}' | catfiles | $STRIPPER > $STRIP

	    if [ $MODE = "bconly" ] ; then
		cat $STRIP
		quit 0
	    fi
	fi

##	Determine the number of added/deleted/modified lines by running the
##	input through ssize.  Post process the output from ssize to show 
##	NISS (Newly Integrated Source Statements).

	echo "Counting added, deleted and modified lines." 1>&2
	cat $INPUT | ssize $SOPT $BOPT > $TEMP

	cat $TEMP |&
	read -p JUNK			# skip past first two lines.
	read -p JUNK
	read -p NEW UNCHANGED ADDED DELETED MODIFIED NCSL

	if [ $NEW = "." ] ; then
	    NEW=0
	fi
	if [ $UNCHANGED = "." ] ; then
	    UNCHANGED=0
	fi
	if [ $ADDED = "." ] ; then
	    ADDED=0
	fi
	if [ $DELETED = "." ] ; then
	    DELETED=0
	fi
	if [ $MODIFIED = "." ] ; then
	    MODIFIED=0
	fi
	if [ $NCSL = "." ] ; then
	    NCSL=0
	fi

	NISL=`expr $NEW + $ADDED + $MODIFIED`
	REUSED=`expr $NCSL - $NISL`
	TOTAL=0

	echo "$TITLE" | adjust -c
	echo "------------------------------------------------------------------------------"
	echo " "
	echo Lines Percentage of Total | awk '{printf "\t\t\t     %s\t%s %s %s\n", $1, $2, $3, $4}'

	if [ $MODE != "bcoff" ] ; then
	    cat $STRIP |&
	    read -p TOTLINES
	    read -p COMLINES
	    read -p BLINES
	    TOTAL=`echo $TOTLINES    | awk '{print $3}'`
	    if [ $TOTAL -ne 0 ] ; then
		echo $COMLINES $TOTAL | \
		awk '{printf "    %s %s %s%9d\t     %7.2f\n", $1, $2, $3, $4, ($4/$5)*100 }'
		echo $BLINES $TOTAL | \
		awk '{printf "    %s %s\t%9d\t     %7.2f\n", $1, $2, $3, ($3/$4)*100 }'
	    else
		echo $COMLINES | awk '{printf "    %s %s %s%9d\t      -\n", $1, $2, $3, $4 }'
		echo $BLINES | awk '{printf "    %s %s\t%9d\t      -\n", $1, $2, $3 }'
	    fi
	fi

	if [ $TOTAL -ne 0 ] ; then
	    echo NCSL [1]: $NCSL $TOTAL | \
	    awk '{printf "    %s %s\t\t%9d\t     %7.2f\n", $1, $2, $3, ($3/$4)*100}'
	else
	    echo NCSL [1]: $NCSL | awk '{printf "    %s %s\t\t%9d\t      -\n", $1, $2, $3}'
	fi

	if [ $MODE != "bcoff" ] ; then
	    echo $TOTLINES $TOTAL    | awk '{printf "    %s %s\t%9d\n", $1, $2, $3 }'
	fi

	echo " "
	echo Lines Percentage of NCSL | awk '{printf "\t\t\t     %s\t%s %s %s\n", $1, $2, $3, $4}'

	if [ $NCSL -ne 0 ] ; then
	    echo New lines [2]: $NEW $NCSL | \
	    awk '{printf "    %s %s %s\t%9d\t     %7.2f\n", $1, $2, $3, $4, ($4/$5)*100}'
	    echo Added lines: $ADDED $NCSL  | \
	    awk '{printf "    %s %s \t%9d\t     %7.2f\n", $1, $2, $3, ($3/$4)*100}'
	    echo Deleted lines: $DELETED $NCSL | \
	    awk '{printf "    %s %s \t%9d\t     %7.2f\n", $1, $2, $3, ($3/$4)*100}'
	    echo Modified lines: $MODIFIED $NCSL  | \
	    awk '{printf "    %s %s \t%9d\t     %7.2f\n", $1, $2, $3, ($3/$4)*100}'
	    echo Unchanged lines: $UNCHANGED $NCSL | \
	    awk '{printf "    %s %s \t%9d\t     %7.2f\n", $1, $2, $3, ($3/$4)*100}'
	    echo " "
	    echo NISL [3]: $NISL $NCSL | \
	    awk '{printf "    %s %s \t\t%9d\t     %7.2f\n", $1, $2, $3, ($3/$4)*100}'
	    echo Reused:   $REUSED $NCSL | \
	    awk '{printf "    %s\t\t%9d\t     %7.2f\n", $1, $2, ($2/$3)*100}'
	else
	    echo New lines [2]: $NEW     | awk '{printf "    %s %s %s\t%9d\t      -\n", $1, $2, $3, $4}'
	    echo Added lines:   $ADDED   | awk '{printf "    %s %s\t%9d\t      -\n", $1, $2, $3}'
	    echo Deleted lines: $DELETED | awk '{printf "    %s %s\t%9d\t      -\n", $1, $2, $3}'
	    echo Modified lines: $MODIFIED   | awk '{printf "    %s %s\t%9d\t      -\n", $1, $2, $3}'
	    echo Unchanged lines: $UNCHANGED | awk '{printf "    %s %s\t%9d\t      -\n", $1, $2, $3}'
	    echo " "
	    echo NISL [3]: $NISL  | awk '{printf "    %s %s\t\t%9d\t      -\n", $1, $2, $3}'
	    echo Reused:   $REUSED| awk '{printf "    %s \t\t%9d\t      -\n", $1, $2, $3}'
	fi
	echo "------------------------------------------------------------------------------"
	echo " "
	cat <<-!
	Notes:

	[1] NCSL (Non Commented Source Lines) is defined as Total - (Comments + Blanks)
	    which equals New + Added + Modified + Unchanged.

	[2] 'New' lines are only those lines found in 'new' files, i.e. files with
	    type-code 'n'.  Added lines are those lines that have been added to 
	    files containing reused code, i.e. files with type-code 'r' or 'u'.

	[3] NISL (Newly Integrated Source Lines) is defines as New + Added + Modified.

!

	quit 0
@EOF
set `sum $sumopt <linecounter`; if test $1 -ne 26874
then
	echo ERROR: linecounter checksum is $1 should be 26874
fi
set `wc -lwc <linecounter`
if test $1$2$3 != 26111787516
then
	echo ERROR: wc results of linecounter are $* should be 261 1178 7516
fi

chmod 555 linecounter

echo x - linecounter.1
cat >linecounter.1 <<'@EOF'
.TH linecounter 1
.ad b
.SH NAME
linecounter - generate source line statistics for C, Pascal or Shell script 
source.
.SH SYNOPSIS
linecounter [options] [file(s)]
.SH HP-UX COMPATIBILITY
.TP 10
Level:
Non-Standard
.TP
Origin:
HP
.SH DESCRIPTION
.B Linecounter
is a shell script that functions as a coordinator for the following source line
counting utility programs: \fBssize(1), cstrip(1), pstrip(1),
shstrip(1), catfiles(1)\fR and \fBckfiles(1)\fR.  These programs are 
assumed to be in the user's path.
.PP
The basic steps taken by linecounter can be roughly described as:
.in +5
.sp
1. The specified file names are checked by ckfiles.
.sp
2.  The number of blank lines and whole comment lines
are computed by cstrip (or pstrip or shstrip) with the
-r option.
.sp
3.  The number of new lines, added lines, deleted lines, modified lines and NCSL
are computed by calling ssize -S.
.sp
4.  Some post processing is done to manipulate the
output from ssize and the stripper into the format
described below.
.in -5
.PP
Linecounter reads its input from the named file(s) or from stdin.  Each
input line must be of the form:
.sp
	\fItype-letter   filename   [qualifiers]\fR
.sp
\fIType-letter\fR must be one of:
.in +5
.sp 
\fBn\fR - meaning the file contains new source code.
.sp 
\fBr\fR - meaning the file contains reused source code and old code is
available for comparison.
.sp 
\fBu\fR - meaning the file contains reused source code and old code is
not available for comparison.
.in -5
.PP
\fIFilename\fR specifies what file is to be counted.  The file may be under
\fBSCCS\fR or \fBRCS\fR source code control, in which case, the appropriate
version is extracted before any counting is done (see ssize).
\fIFilename\fR is considered to be an SCCS file if and only if its basename
begins with the prefix "s.".  \fIFilename\fR is considered to be an RCS file 
if and only if its basename ends with the suffix ",v".
.PP
The optional qualifier for an \fBn\fR or \fBu\fR file under \fBSCCS\fR control
is a "release.level" number indicating which version of the file to
count.  If no qualifier is given, the top-most version is counted.
For an \fBr\fR file under SCCS control, the qualifier is a pair of 
release.level numbers specifying which versions of the file to compare.
The first qualifier is
assumed to be the new version while the second qualifier is assumed to be
the old version.  The qualifiers for \fBr\fR files under SCCS control cannot
be file names.  Note, while not an error, it does not make sense 
for the qualifiers of an \fBr\fR file to be the same.  This would cause ssize 
to compare the specified version of the file with itself.  Typical examples of 
SCCS file usage would look like:
.sp
 	n    s.file1.c    2.1
 	u    s.file2      8.7
 	r    s.file3      4.1     3.2
.sp
The optional qualifier for an \fBn\fR or \fBu\fR file under \fBRCS\fR control
is a "release.level" number or a symbolic tag (created via the -n option of ci(1)) indicating which version of the file to
count.  If no qualifier is given, the top-most version is counted.
For an \fBr\fR file under RCS control, the qualifier is a pair of 
rev_numbers/symbolic_tags specifying which versions of the file to compare.
The first qualifier is
assumed to be the new version while the second qualifier is assumed to be
the old version.  The qualifiers for \fBr\fR files under RCS control cannot
be file names.  Note, while not an error, it does not make sense 
for the qualifiers of an \fBr\fR file to be the same.  This would cause ssize 
to compare the specified version of the file with itself.  Typical examples of 
RCS file usage would look like:
.sp 1
 	n    file1.c,v    2.1
 	u    file2,v      release_2
 	r    file3,v      4.1     3.2
 	r    file4,v      rel3    rel2
.sp 1
For files that are not under SCCS or RCS control, the qualifier for an \fBn\fR 
or \fBu\fR file is ignored.
For an \fBr\fR file not under SCCS or RCS control, the qualifier is the name
of the file which is compared to the original.
Typical usage would look like:
.sp 1
 	n    file1.c
 	u    file2
 	r    file3 file4
.PP
Output from linecounter is printed on stdout.  The output is divided into 
three sections.  The first section contains information about the files
named in the input.  This information consists of the number of files named,
how many of the names are valid, how many of the names are not valid and a
list of all the invalid names.
The second section contains the cumulative line counts, broken down into
different categories, for all of the valid file names.  The third section
contains some commentary notes about the various counts.
.PP
There are several options that affect linecounter's behavior.  These options
are:
.TP
\fB-bcoff\fR - 
Suppress blank and whole comment line counting.  By default,
linecounter reports how many blank and whole comment lines are found in the
input.  This option stops this calculation.
.TP
\fB-bconly\fR - 
Only report how many blank and whole comment lines are
present in the input.  The other counts are
suppressed.
.TP
\fB-ckoff\fR - 
Suppress the checking done by ckfiles.  If the input data is
known to be correct, this option will increase performance.  However, if a
"bad" file name or revision number is not screened out by ckfiles then
linecounter halts without generating any counts.
.TP
\fB-ckonly\fR - 
Simply pass the input files through the checker, ckfiles.
No counting is performed.  This is useful when the user simply wants to
verify the input is valid.
.TP
\fB-P\fR - 
Use the Pascal comment stripper, pstrip.  By default, the C
comment stripper, cstrip is used.
.TP
\fB-T title\fR - 
Use the given string as a title to be printed at the top of the report.
Note, if the title consists of more than one word, it must be surrounded by
double quotes.
.TP
\fB-b\fR - 
Invoke ssize with the -b option.  This causes differences in a line that are
due to changes in white space to be ignored.
.TP
\fB-s stripper\fR - 
Use the specified comment/blank-line stripping program instead of cstrip, 
pstrip or shstrip.  The specified program is used in two ways. 
\fBStripper\fR is passed directly to ssize via ssize's -s option.  
Linecounter also invokes \fBstripper\fR directly to calculate the
total number of lines, the number of blank lines and the number of whole
comment lines found in the input.  As a result, linecounter assumes
that \fBstripper\fR has a "-r" option (like cstrip's), and, the output
from invoking \fBstripper\fR with the -r option is exactly of the form
(without the leading and trailing blank lines):
.sp 1
 	Total lines:              156
 	Whole comment lines:        0
 	Blank lines:                0
.sp 1
If \fBstripper\fR does not meet these two requirements then
linecounter will need to be modified.
.SH WARNINGS
Linecounter makes two key assumptions about the behavior of the tools used
to stip out comments and blank lines.  See the -s option discussion above
for more details.
.SH SEE ALSO
ssize(1), cstrip(1), pstrip(1), shstrip(1), catfiles(1) and ckfiles(1)
.SH AUTHOR
Scott Warren - UDL Productivity
@EOF
set `sum $sumopt <linecounter.1`; if test $1 -ne 30786
then
	echo ERROR: linecounter.1 checksum is $1 should be 30786
fi
set `wc -lwc <linecounter.1`
if test $1$2$3 != 17811677066
then
	echo ERROR: wc results of linecounter.1 are $* should be 178 1167 7066
fi

chmod 444 linecounter.1

echo x - osize.1
cat >osize.1 <<'@EOF'
.TH OSIZE 1 "HP Experimental, Version 1.1"
.SH NAME
osize \- print object code size metrics report
.SH SYNOPSIS
.B osize
[
.I files...
]
.ad b
.SH DESCRIPTION
This command prints a report on object metrics for a set of object files,
suitable for use in partially filling out the
.SM HP
Corporate Metrics Council code-size metrics form.
It reads standard input if you give no
.I file
arguments, or you can use "\fB\-\fR" for standard input,
and it writes a report to standard output.
.PP
Input lines may be generated with the
.IR prepsize (1)
tool before handing them to this program.
Each input line must be of one of these forms:
.nf
.RS

n filename
r filename oldfile
u filename
.RE
.fi
.PP
Blank lines and comment lines are not allowed,
which is reasonable since input is usually generated automatically.
.PP
Meanings of first fields (type letters) are:
.TP 4
.B n
File contains new object code.
.TP
.B r
File contains reused object code and old code is available for comparison.
.TP
.B u
File contains reused object code and old code is not available.
.ne 10
.PP
Output is of the form:
.nf
.RS

OBJECT BYTES          TOTAL
New code                  X
Reused, have old          X  (delta +/-X)
Reused, no old            X
TOTAL                     X
.RE
.fi
.PP
The sizes generated by this program are "text" sizes as returned by the
.IR size (1)
program.
They are sensitive to the "optimize" compiler option.
They do not include data or bss sizes.
.PP
.I osize
calls
.I size
by basename only, not full pathname, so it is possible to substitute a
special version.
It is called once for each type of file,
with a long list of filenames,
after reading all input lines.
This may fail if the length of any list exceeds the maximum argument list
size in
.IR exec (2),
or if there is not enough memory to hold all lists.
.PP
.I osize
expects
.I size
to be well-behaved (as the official version is).
It must produce uniform output where the text size is the first field if only
one file is sized,
else the second field of each line.
Errors from
.I size
must be printed directly to standard error.
.SH EXAMPLES
.TP
osize mylist
Print a report on the object files listed in mylist.
.SH SEE ALSO
prepsize(1), size(1), ssize(1), cstrip(1), pstrip(1), shstrip(1)
.SH DIAGNOSTICS
Prints a message to standard error and exits non-zero if
invoked wrong or detects an error in an input file.
.IR size (1)
prints its own error if any file isn't found,
can't be opened,
or isn't recognized object code.
@EOF
set `sum $sumopt <osize.1`; if test $1 -ne 37319
then
	echo ERROR: osize.1 checksum is $1 should be 37319
fi
set `wc -lwc <osize.1`
if test $1$2$3 != 1014412488
then
	echo ERROR: wc results of osize.1 are $* should be 101 441 2488
fi

chmod 444 osize.1

echo x - osize.c
cat >osize.c <<'@EOF'
static char *version = "@(#) 1.1 851118";
/*
 * Print object code size metrics report.
 * See the manual entry for details.
 */

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

char	*malloc();
char	*realloc();

#define	PROC				/* null; easy to find procs */
#define	FALSE	  0
#define	TRUE	  1
#define	CHNULL	  ('\0')
#define	CHNEWLINE ('\n')
#define	CPNULL	  ((char *) NULL)
#define	FILENULL  ((FILE *) NULL)
#define	REG	  register

char	*myname;			/* how program was invoked	*/

char	*defargv[] = { "-" };		/* default argument list	*/


/************************************************************************
 * DATA COLLECTION VARIABLES:
 */

#define	NEW	0			/* for counting new files	*/
#define	RENEW	1			/* reused files, new versions	*/
#define	REOLD	2			/* reused files, old versions	*/
#define	REUNK	3			/* reused files, no old		*/
#define	CLASSES	(REUNK + 1)		/* total number of classes	*/

#define	COMMAND "size"			/* start of command lines	*/
#define	CMDLEN  5			/* initial string length	*/

char	* command [CLASSES];		/* commands and file args	*/

int	cmdlen [CLASSES] = { CMDLEN, CMDLEN, CMDLEN, CMDLEN };
int	files  [CLASSES] = { 0, 0, 0, 0 };	/* files in each class	*/
long	bytes  [CLASSES] = { 0, 0, 0, 0 };	/* bytes in each class	*/


/************************************************************************
 * M A I N
 *
 * Initialize, read data, get sizes, and print report.
 */

PROC main (argc, argv)
REG	int	argc;
REG	char	**argv;
{
REG	int	class;			/* current class	*/
REG	FILE	*filep;			/* open input file	*/
REG	char	*filename;		/* name to use		*/

/*
 * INITIALIZE:
 *
 * command[] values must reside in malloc'd space so they can be realloc'd.
 * cmdlen[] and files[] are pre-initialized.
 */

	myname = *argv++;

	for (class = 0; class < CLASSES; class++)
	{
	    if ((command [class] = malloc (CMDLEN)) == CPNULL)
		Error ("malloc failed");

	    strcpy (command [class], COMMAND);
	}

/*
 * READ FROM LIST OF FILES OR STDIN:
 */

	if (--argc < 1)				/* no file names */
	{
	    argc = 1;
	    argv = defargv;
	}

	while (argc-- > 0)
	{
	    if (strcmp (*argv, "-") == 0)	/* read stdin */
	    {
		filename = "<stdin>";
		filep	 = stdin;
	    }
	    else if ((filep = fopen ((filename = *argv), "r")) == FILENULL)
		Error ("can't open file \"%s\" to read it", filename);

	    ReadFile (filename, filep);

	    if (filep != stdin)
		fclose (filep);			/* assume it works */

	    argv++;
	}

/*
 * GET SIZES, PRINT REPORT:
 */

	for (class = 0; class < CLASSES; class++)
	    GetSizes (class);

	PrintReport();
	exit (0);

} /* main */


/************************************************************************
 * E R R O R
 *
 * Print an error message to stderr and exit nonzero.  Message is preceded
 * by "<myname>: " using global char *myname, and followed by a newline.
 */

/* VARARGS */
PROC Error (message, arg1, arg2, arg3, arg4)
	char	*message;
	int	arg1, arg2, arg3, arg4;
{
	fprintf (stderr, "%s: ", myname);
	fprintf (stderr, message, arg1, arg2, arg3, arg4);
	fputc   (CHNEWLINE, stderr);

	exit (1);

} /* Error */


/************************************************************************
 * R E A D   F I L E
 *
 * Given a filename (for error messages) and an open stream pointer, read
 * data lines from the stream until exhausted, accumulating filenames from
 * each one, then check for error.
 *
 * char line[] is static so it's only allocated once.
 */

PROC ReadFile (filename, filep)
	char	*filename;	/* for errors	*/
REG	FILE	*filep;		/* open stream	*/
{
static	char	line [BUFSIZ];	/* input line	*/
REG	char	*cp;		/* pos in line	*/
REG	int	linenum = 0;	/* in file	*/

	while (fgets (line, BUFSIZ, filep) != CPNULL)
	{
	    if ((cp = strchr (line, CHNEWLINE)) != CPNULL)
		*cp = CHNULL;			/* terminate at newline */

	    DoInputLine (filename, ++linenum, line);
	}

	if (ferror (filep))
	    Error ("read failed from \"%s\"", filename);

} /* ReadFile */


/************************************************************************
 * D O   I N P U T   L I N E
 *
 * Given a filename and linenumber (for error messages) and an input line,
 * parse the line and accumulate the filename(s) from that line in global
 * char *command[] for the class, also updating globals cmdlen[] and files[].
 * Error out if anything is noticeably wrong with the line.
 */

PROC DoInputLine (filename, linenum, line)
	char	*filename;		/* for errors	*/
	int	linenum;		/* for errors	*/
	char	*line;			/* to parse	*/
{
REG	char	*tokensep = " \t";	/* token separator	*/
	char	*ltp;			/* linetype pointer	*/
REG	char	linetype;		/* 'n' | 'r' | 'u'	*/
	char	*dofilename;		/* file to size		*/
	char	*oldfilename;		/* from third field	*/

/*
 * PARSE LINE INTO TOKENS:
 */

	if (((ltp = strtok (line, tokensep)) == CPNULL)
	 || (((linetype = *ltp) != 'n')
	  && (linetype != 'r') && (linetype != 'u')))
	{
	    Error ("missing or invalid type-letter in file \"%s\", line %d",
		   filename, linenum);
	}

	if ((dofilename = strtok (CPNULL, tokensep)) == CPNULL)
	    Error ("missing filename in file \"%s\", line %d",
		filename, linenum);

	if (((oldfilename = strtok (CPNULL, tokensep)) != CPNULL)
	 && ((linetype != 'r') || (strtok (CPNULL, tokensep) != CPNULL)))
	{
	    Error ("too many tokens in file \"%s\", line %d",
		filename, linenum);
	}

/*
 * HANDLE DIFFERENT LINE TYPES:
 */

	switch (linetype)
	{
	    case 'n':	AppendFilename (dofilename, NEW);	break;
	    case 'u':	AppendFilename (dofilename, REUNK);	break;

	    case 'r':
		if (oldfilename == CPNULL)
		{
		    Error ("old filename is required in file \"%s\", line %d",
			filename, linenum);
		}

		AppendFilename (dofilename,  RENEW);
		AppendFilename (oldfilename, REOLD);
		break;
	}

} /* DoInputLine */


/************************************************************************
 * A P P E N D   F I L E N A M E
 *
 * Given a filename and a class to save it under, append it to the command
 * line for that class (global char *command[]) and update globals cmdlen[]
 * and files[].
 */

PROC AppendFilename (filename, class)
	char	*filename;	/* to append */
REG	int	class;		/* which one */
{
	int	oldlen = cmdlen [class];
	int	newlen = oldlen + 1 + strlen (filename);

	if ((command [class] = realloc (command [class], newlen)) == CPNULL)
	    Error ("realloc failed");

	command [class] [oldlen - 1] = ' ';		/* append separator */
	strcpy (command [class] + oldlen, filename);	/* append filename  */

	cmdlen [class] = newlen;
	files  [class] ++;

} /* AppendFilename */


/************************************************************************
 * G E T   S I Z E S
 *
 * Given a class and globals char *command[] and int files[], run the
 * command for that class, add up sizes from the output, and save the
 * total in global long bytes[].
 */

PROC GetSizes (class)
REG	int	class;			/* which one		*/
{
REG	FILE	*pipep;			/* to read pipe		*/
static	char	line [BUFSIZ];		/* only allocate once	*/
REG	int	field2;			/* use output field 2?	*/
REG	char	*tokensep = " \t";	/* token separator	*/
REG	char	*cp;			/* place in output line	*/
REG	long	total = 0;		/* to accumulate	*/

/*
 * RUN COMMAND, ADD SIZES:
 */

	if (files [class] < 1)		/* no files in class */
	    return;			/* no action needed  */

	field2 = (files [class] > 1);

	if ((pipep = popen (command [class], "r")) == FILENULL)
	    Error ("can't run %s command", COMMAND);

	while (fgets (line, BUFSIZ, pipep) != CPNULL)
	{
	    cp = strtok (line, tokensep);

	    if (field2)
		cp = strtok (CPNULL, tokensep);

	    total += atol (cp);		/* assume field exists and is usable */
	}

/*
 * CHECK ERRORS, FINISH UP:
 */

	if (ferror (pipep))
	    Error ("read failed from %s command", COMMAND);

	if (pclose (pipep))			/* any non-zero return */
	    Error ("%s command failed", COMMAND);

	bytes [class] = total;

} /* GetSizes */


/************************************************************************
 * P R I N T   R E P O R T
 *
 * Given global totals, print a report.
 */

char	*format1 = "%-16s%11ld\n";		/* to print values */
char	*format2 = "%-16s%11ld   (delta %+ld)\n";

PROC PrintReport()
{
	puts ("OBJECT BYTES          TOTAL");

	printf (format1, "New code",	     bytes [NEW]);

	printf (format2, "Reused, have old", bytes [RENEW],
					     bytes [RENEW] - bytes [REOLD]);

	printf (format1, "Reused, no old",   bytes [REUNK]);

	printf (format1, "TOTAL", bytes [NEW] + bytes [RENEW] + bytes [REUNK]);

} /* PrintReport */
@EOF
set `sum $sumopt <osize.c`; if test $1 -ne 37476
then
	echo ERROR: osize.c checksum is $1 should be 37476
fi
set `wc -lwc <osize.c`
if test $1$2$3 != 34313438399
then
	echo ERROR: wc results of osize.c are $* should be 343 1343 8399
fi

chmod 444 osize.c

echo x - prepsize
cat >prepsize <<'@EOF'

# version @(#) 1 851025
# Prepare files list for size metrics.
# See the manual entry for details.

# This may be slow and inefficient, but good performance was not a design goal.


# INITIALIZE:

	nlist=''			# filename patterns.
	usage=''			# flag: usage error?
	marker='*%&^$#'			# special marker value.


# PARSE ARGUMENTS:

	set -f				# don't expand filenames.
	set -- `getopt n: "$@"`
	set +f

	if test $? != 0			# error in getopt.
	then
	    usage='bad'
	else
	    while test $# != 0		# still have args.
	    do
		case "$1" in

		-n)	if test "x$nlist" != x		# already have some.
			then
			    nlist="$nlist -o -name $2"	# append with "or".
			else
			    nlist="-name $2"		# save new.
			fi

			shift 2
			;;

		--)	shift; break ;;			# end of args.

		*)	usage='bad'; break ;;		# unknown arg.

		esac
	    done
	fi


# TELL CORRECT USAGE:

	if test "$usage"
	then
	    # this is an interpreted here-document.
	    cat >&2 <<-!
			usage: $0 [-n pattern] [files...]
			-n filenames must match pattern ("or" of all patterns)
			files contain input data in proper format
		!

	    exit 1
	fi


# READ INPUT LINES:

	cat $* |

	while read type file qual
	do
	    if test "x$type" = x  -o  x`expr substr "$type" 1 1` = "x#"
	    then
		continue				# blank or comment.

	    elif test "x$type" != xn  -a  "x$type" != xr  -a  "x$type" != xu
	    then
		echo "$0: invalid type letter \"$type\" in input" >&2
		exit 1

	    elif test "x$file" = x
	    then
		echo "$0: input line missing filename" >&2
		exit 1

# EXPAND FILENAMES:

	    else
		echo "$marker $type $file $qual"	# original data.

		if test "x$nlist" = x			# no patterns.
		then
		    find "$file" -type f -print		# file list.
		else
		    set -f			# don't expand filenames.
		    find "$file" \( $nlist \) -type f -print
		    set +f
		fi
	    fi
	done |						# note pipe.

	# Output is marker lines ("marker type file qual") or filenames.
	# Type fields are known to be one letter long.


# CONDENSE EXPANDED FILENAME LIST:

	awk '

	# Save original data from marked line:

	($1 == "'"$marker"'") {
		type = $2;			# save values.
		file = $3;
		qual = $4;

		for (field = 5; field <= NF; field++)
		    qual = qual " " $field;

		fields = NF;
		next;
	}

	# Save data from filename line:

	{
	    if ((type != "r") || (file == $0) || (fields != 4))	# ordinary.
	    {
		# overwrite old or save new, with concatenation:
		data [$0] = type qual;
	    }
	    else	# filename was expanded and qual is a single word.
	    {
		# tack on to qual the expanded part of file:
		data [$0] = type qual substr ($0, length (file));
	    }
	}
    
	# Print condensed data:

	END {
	    for (file in data)			# split type from qual:
		print substr (data [file], 1, 1), file, \
		      substr (data [file], 2);
	}'
@EOF
set `sum $sumopt <prepsize`; if test $1 -ne 44933
then
	echo ERROR: prepsize checksum is $1 should be 44933
fi
set `wc -lwc <prepsize`
if test $1$2$3 != 1434722761
then
	echo ERROR: wc results of prepsize are $* should be 143 472 2761
fi

chmod 555 prepsize

echo x - prepsize.1
cat >prepsize.1 <<'@EOF'
.TH PREPSIZE 1 "HP Experimental, Version 1.1"
.SH NAME
prepsize \- prepare files list for size metrics
.SH SYNOPSIS
.B prepsize
[
.BI \-n \0pattern
] [
.I files...
]
.ad b
.SH DESCRIPTION
This command prepares a files list for use by
.IR ssize (1)
or
.IR osize (1)
by expanding directory names to filenames and resolving file categories.
It reads standard input for the list if you give no
.I file
arguments,
or you can use "\fB\-\fR" for standard input.
It writes a resulting list (often much longer) to standard output.
.PP
Each input line must be of the form:
.nf
.RS

type-letter file-or-directory [qualifiers]
.RE
.fi
.PP
Blank lines and comment lines (first non-blank character is a "#") are ignored.
.PP
Each directory name is recursively expanded to a list of ordinary files using
.IR find (1).
The ordinary filenames are saved along with their attributes
.RI ( type-letter s
and optional
.I qualifiers
as simple strings).
.PP
If
.I type-letter
is
.BR r ,
and the second field is a directory name which is expanded, and
.I qualifier
is a single word (a filename),
it is also expanded for each ordinary file by appending the part of the
pathname which was added to the directory name to make the ordinary filename.
For example, an input line of
.nf
.RS

r /usr/src /old/src
.RE
.fi
.PP
might be expanded to several lines, one of which might be:
.nf
.RS

r /usr/src/cmd/x.c /old/src/cmd/x.c
.RE
.fi
.PP
As each new input line is expanded,
all saved ordinary filenames are searched to see if each new filename
(full name given, not just the basename)
has been seen before.
If so, it is not saved anew,
but instead its old attributes are changed
to the values for the new input line.
In other words, later occurrences of the same filename
override attributes earlier associated with it.
This allows you to specify attributes for a whole tree,
then alter them for specific sub-tree(s) or file(s).
.PP
When the input file is exhausted,
.I prepsize
writes its list of net filenames and attributes to standard output
in the same format as the input,
but not necessarily in the same order encountered.
(You can sort it if you want;
keeping order is not important considering that each
.I find
produces random-order data anyway.)
.PP
Note that passing the same data through
.I prepsize
more than once is a no-op as long as the file tree doesn't change.
.PP
The option is:
.TP
.BI \-n \0pattern
Select and report on only those files whose names match the given shell-style
filename pattern (quoted if necessary to hide it from a command interpreter).
You can give more than one
.B \-n
option, and the results are or'd together.
.SH EXAMPLES
.TP
prepsize mylist > newlist
Expand the filenames in mylist and save the results in newlist.
.TP
prepsize \-n'*.c' \-n'*.h' a b c d
Prepare filenames listed in files a, b, c, and d,
including only C source and include files.
.SH SEE ALSO
find(1), osize(1), ssize(1), cstrip(1), pstrip(1), shstrip(1)
.SH DIAGNOSTICS
Prints a message to standard error and exits non-zero if
invoked wrong,
can't open a named file,
detects an error in an input file,
or detects an error from
.IR find .
.SH LIMITATIONS
Since all filenames are passed through
.IR find ,
those which are invalid (or directories which contain no ordinary files)
don't show up at all in the output,
not even as errors.
.PP
Since
.I prepsize
is a shell script it is somewhat slow and not very robust.
You get a strange
.I awk
error if no data is processed.
Filenames containing blank cause real confusion.
@EOF
set `sum $sumopt <prepsize.1`; if test $1 -ne 20741
then
	echo ERROR: prepsize.1 checksum is $1 should be 20741
fi
set `wc -lwc <prepsize.1`
if test $1$2$3 != 1316153504
then
	echo ERROR: wc results of prepsize.1 are $* should be 131 615 3504
fi

chmod 444 prepsize.1

echo x - pstrip.1
cat >pstrip.1 <<'@EOF'
.TH PSTRIP 1 "HP Experimental, Version 2"
.SH NAME
pstrip \- strip comments and blank lines from Pascal source
.SH SYNOPSIS
.B pstrip
[
.B \-r
]
[
.B \-m
] [
.I files...
]
.ad b
.SH DESCRIPTION
This command reads the named files
(you can use "\fB\-\fR" for standard input),
or from standard input if no filenames are given,
expecting syntactically correct Pascal source code.
It strips comments and blank lines and writes results to standard output.
Output consists solely of "non-comment source lines"
(including compiler directives).
A source file processed by
.I pstrip
can be compiled with the same results as compiling the original version.
.PP
The options are:
.TP
.B \-m
Handle
.SM MODCAL-style
comments; see below.
.sp 1
.TP
.B \-r
Generate a report showing how many blank lines, whole comment lines and the
total number of lines there are in the input.  The stripped source is not 
generated in this case.
.PP
All characters in comments,
including the comment delimiters,
are deleted.
Comments begin with the unquoted string (token) "(\(**",
or the unquoted character "{".
The tokens are quoted (thus ignored) if
inside a string constant,
i.e. after a single quote mark on the same input line.
.PP
Comments end with the next "\(**)" string or "}" character,
regardless of quoting,
i.e. they cannot be nested.
By default (S300 and S500 compiler products) the two tokens are equivalent,
e.g. a comment begun with "(\(**" can end with "}".
.PP
The
.B \-m
option is compatible with the S500 internal
.SM MODCAL
compiler,
where the two token types are not equivalent.
"(\(**" comments are only terminated by a "\(**)",
and "{" comments by a "}".
Nesting is still not recognized;
once in a comment, only the proper terminator is special.
.PP
After comments are removed,
.I pstrip
removes any blank lines,
i.e. those containing only blanks or tabs, if anything.
However, trailing whitespace on lines,
such as that between a line of code and its trailing comment,
is not removed.
.SH SEE ALSO
cstrip(1), shstrip(1)
.SH WARNINGS
If the last input line does not end in a newline,
one is added as a side-effect.
.PP
You may encounter a third commenting style which is not supported,
one which is enabled by compiler directives.
Its use is "discouraged" in the S500 compiler documentation.
It makes the two token styles non-equivalent,
but also allows syntactic nesting.
The
.B \-m
option will handle this type of code as long as there are no triply
nested comments, e.g. "(\(** one { two \(**) huh? } \(**)".
.SH DIAGNOSTICS
Prints a warning message to stderr and continues if it
cannot open a named file or if a read fails.
Prints a message to stderr and continues if any input file ends
in the middle of a comment (something might be wrong).
@EOF
set `sum $sumopt <pstrip.1`; if test $1 -ne 33729
then
	echo ERROR: pstrip.1 checksum is $1 should be 33729
fi
set `wc -lwc <pstrip.1`
if test $1$2$3 != 924712742
then
	echo ERROR: wc results of pstrip.1 are $* should be 92 471 2742
fi

chmod 444 pstrip.1

echo x - shstrip.1
cat >shstrip.1 <<'@EOF'
.TH SHSTRIP 1 "HP Experimental, Version 2"
.SH NAME
shstrip \- strip comments and blank lines from shell scripts
.SH SYNOPSIS
.B shstrip
[
.I files...
]
.ad b
.SH DESCRIPTION
This command reads the named files
(you can use "\fB\-\fR" for standard input),
or from standard input if no filenames are given,
expecting legal Bourne shell commands (scripts).
It strips comments and blank lines and writes results to standard output.
Output consists solely of "non-comment shell source lines",
which can still be run by the shell.
.PP
All characters in comments,
including the delimiter,
are deleted.
.I shstrip
follows the same commenting and quoting syntax rules as does
.IR sh (1),
except as described in
.SM BUGS
below.
It does not know about comments embedded in quotes, e.g. in inline
.IR awk (1)
scripts.
.PP
After comments are removed,
.I shstrip
removes any blank lines (except in comments),
i.e. those containing only blanks or tabs, if anything.
However, trailing whitespace on lines,
such as that between a line of text and its trailing comment,
is not removed.
.SH SEE ALSO
cstrip(1), pstrip(1), sh(1)
.SH BUGS
.I shstrip
does not recognize here-documents (embedded text after "<<").
Anything that looks like a comment inside one is stripped,
whereas the shell would treat it as text, not a comment.
.PP
The shell permits some wonderfully complex nesting of quotes,
such as double quotes within back quotes within double quotes.
.I cstrip
does not try to understand this.
It only notices quoting one level deep,
and ignores "comments" inside the "outermost" level.
Also, the shell recognizes and ignores comments inside backquotes, but
.I shstrip
does not recognize them.
.SH DIAGNOSTICS
Prints a message to standard error and exits non-zero if it
cannot open a named file or if a read fails.
@EOF
set `sum $sumopt <shstrip.1`; if test $1 -ne 51451
then
	echo ERROR: shstrip.1 checksum is $1 should be 51451
fi
set `wc -lwc <shstrip.1`
if test $1$2$3 != 582981800
then
	echo ERROR: wc results of shstrip.1 are $* should be 58 298 1800
fi

chmod 444 shstrip.1

echo x - shstrip.c
cat >shstrip.c <<'@EOF'
static char *version = "@(#) 2 860911";
/*
 * Strip comments and blank lines from shell source.
 * See the manual entry for details.
 */

#include <stdio.h>

#define	PROC				/* null; easy to find procs */
#define	FALSE	  0
#define	TRUE	  1
#define	CHNULL	  ('\0')
#define	CHNEWLINE ('\n')
#define	CPNULL	  ((char *) NULL)
#define	FILENULL  ((FILE *) NULL)
#define	REG	  register

char	*myname;			/* how program was invoked	*/

char	*defargv[] = { "-" };		/* default argument list	*/


/************************************************************************
 * STATE DEFINITIONS:
 *
 * Each input character drives a state machine with these states (starting
 * each file at WHITESP).  The state machine itself is embedded in code.
 * The shell allows complex nested quoting, but this program doesn't try
 * to handle it.
 *
 * All states except the QUOTE and [DB]BSLASH states reset to WHITESP at
 * end of line.  In other words, they don't continue across a newline.
 */

#define	WHITESP		0	/* at whitespace or file start	*/
#define	TEXT		1	/* in text, can't start comment	*/
#define	COMMENT		2	/* now in comment		*/
#define	BSLASH		3	/* naked backslash		*/
#define	SQUOTE		4	/* in single quotes		*/
#define	DQUOTE		5	/* in double quotes		*/
#define	BQUOTE		6	/* in backwards quotes		*/
#define	DBSLASH		7	/* backslash in double quotes	*/
#define	BBSLASH		8	/* backslash in back quotes	*/


/************************************************************************
 * M A I N
 *
 * Open each file, read it, check for error, close it.
 */

PROC main (argc, argv)
REG	int	argc;
REG	char	**argv;
{
REG	FILE	*filep;			/* open input file	*/
REG	char	*filename;		/* name to use		*/

	myname = *argv++;

	if (--argc < 1)				/* no file names */
	{
	    argc = 1;
	    argv = defargv;			/* use default */
	}

	while (argc-- > 0)
	{
	    if (strcmp (*argv, "-") == 0)	/* read stdin */
	    {
		filename = "<stdin>";
		filep	 = stdin;
	    }
	    else if ((filep = fopen ((filename = *argv), "r")) == FILENULL)
		Error ("can't open file \"%s\" to read it", filename);

	    ReadFile (filep);

	    if (ferror (filep))
		Error ("read failed from file \"%s\"", filename);

	    if (filep != stdin)
		fclose (filep);			/* assume it works */

	    argv++;
	}

	exit (0);

} /* main */


/************************************************************************
 * E R R O R
 *
 * Print an error message to stderr and exit nonzero.  Message is preceded
 * by "<myname>: " using global char *myname, and followed by a newline.
 */

/* VARARGS */
PROC Error (message, arg1, arg2, arg3, arg4)
	char	*message;
	int	arg1, arg2, arg3, arg4;
{
	fprintf (stderr, "%s: ", myname);
	fprintf (stderr, message, arg1, arg2, arg3, arg4);
	fputc   (CHNEWLINE, stderr);

	exit (1);

} /* Error */


/************************************************************************
 * R E A D   F I L E
 *
 * Given an open stream pointer, read characters from the stream until
 * exhausted, driving the state machine and emitting only non-comment
 * characters on non-blank lines to stdout.  The caller must check for
 * read errors.
 *
 * This is one large procedure (gag) for efficiency.
 */

PROC ReadFile (filep)
REG	FILE	*filep;			/* open stream to read	 */
{
REG	int	state = WHITESP;	/* current machine state */
REG	int	stuff = FALSE;		/* non-blank input line? */
REG	int	ch;			/* current input "char"  */

static	char	outline [BUFSIZ];	/* allocate only once	 */
REG	char	*cp = outline;		/* place in outline	 */

#define	NONBLANK(ch)	((ch != ' ') && (ch != '\t'))

#define	PUTCHAR(ch)	{ *cp++ = ch; if (NONBLANK (ch)) stuff = TRUE; }


	while (TRUE)			/* until return */
	{

/*
 * HANDLE END OF FILE OR INPUT LINE:
 *
 * There's one weird special case.  If we get a newline and the state is
 * DBSLASH or BBSLASH, don't even recognize the newline as special; it's
 * just another character, escaped by the preceding slash.  It will go into
 * outline just fine and we can continue after the appropriate state change.
 */

	    if (((ch = getc (filep)) == EOF)
	     || ((ch == CHNEWLINE) && (state != DBSLASH) && (state != BBSLASH)))
	    {
		if ((state != SQUOTE) && (state != DQUOTE) && (state != BQUOTE))
		    state = WHITESP;		/* reset to default */

		if ((stuff)			/* non-blank char(s) on line */
		 || (state != WHITESP))		/* in quotes, so do print it */
		{
		    stuff = FALSE;
		    *cp   = CHNULL;		/* mark end of line	   */
		    puts (outline);		/* adds newline if missing */
		}

		if (ch == EOF)
		    return;

		cp = outline;			/* reset line	*/
		continue;			/* do next char	*/
	    }

/*
 * IN WHITESPACE, LOOK FOR COMMENT CHAR:
 */

	    switch (state)
	    {
		case WHITESP:
		    if (ch == '#')
		    {
			state = COMMENT;
			break;			/* do next char */
		    }

		    state = TEXT;		/* assume for now */

		    /* then fall through! */

/*
 * IN WHITESPACE OR TEXT, LOOK AT NEXT CHAR:
 */

		case TEXT:
		    switch (ch)
		    {
			case ' ':   /* fall through */
			case '\t':  state = WHITESP;	break;
			case '\\':  state = BSLASH;	break;
			case '\'':  state = SQUOTE;	break;
			case '"':   state = DQUOTE;	break;
			case '`':   state = BQUOTE;	break;
			/* default: do nothing */
		    }

		    PUTCHAR (ch);
		    break;

/*
 * IN COMMENT:
 */

	     /* case COMMENT: do nothing, ignore char */

/*
 * AFTER BACKSLASH, TREAT NEXT CHAR AS TEXT:
 */

		case BSLASH:
		    state = TEXT;
		    PUTCHAR (ch);
		    break;

/*
 * IN SINGLE QUOTES, LOOK FOR CLOSE QUOTE:
 */

		case SQUOTE:
		    if (ch == '\'')
			state = TEXT;

		    PUTCHAR (ch);
		    break;

/*
 * IN DOUBLE OR BACK QUOTES, LOOK FOR CLOSE QUOTE OR BACKSLASH:
 */

		case DQUOTE:
		    if (ch == '"')
			state = TEXT;

		    else if (ch == '\\')
			state = DBSLASH;

		    PUTCHAR (ch);
		    break;

		case BQUOTE:
		    if (ch == '`')
			state = TEXT;

		    else if (ch == '\\')
			state = BBSLASH;

		    PUTCHAR (ch);
		    break;

/*
 * IN DOUBLE OR BACK QUOTES AFTER BACKSLASH, "IGNORE" NEXT CHAR:
 */

		case DBSLASH:
		    state = DQUOTE;
		    PUTCHAR (ch);
		    break;

		case BBSLASH:
		    state = BQUOTE;
		    PUTCHAR (ch);
		    break;

	    } /* switch */
	} /* while */

} /* ReadFile */
@EOF
set `sum $sumopt <shstrip.c`; if test $1 -ne 49417
then
	echo ERROR: shstrip.c checksum is $1 should be 49417
fi
set `wc -lwc <shstrip.c`
if test $1$2$3 != 27610156165
then
	echo ERROR: wc results of shstrip.c are $* should be 276 1015 6165
fi

chmod 444 shstrip.c

echo x - ssize.1
sed 's/^@//' >ssize.1 <<'@EOF'
.TH SSIZE 1 "HP Experimental"
.SH NAME
ssize \- print source code size metrics report
.SH SYNOPSIS
.B ssize
[
.B \-ab
] [
.BI \-n \0newrel
] [
.BI \-o \0oldrel
]
.br
[
.BI \-p \0unchanged,touched
] [
.BI \-s \0command
] [
.B \-S
] [
.I files...
]
.ad b
.SH DESCRIPTION
This command prints a report on source metrics for a set of source files,
and is suitable for use in partially filling out the
.SM HP
Corporate Metrics Council code-size metrics form.
The source files may be kept under
.SM SCCS
or
.SM RCS
source control systems.
The command reads standard input if you give no
.I file
arguments, or you can use "\fB\-\fR" for standard input,
and it writes a report to standard output.
.PP
Input lines may be generated with the
.IR prepsize (1)
tool before handing them to this program.
Each input line must be of the form:
.nf
.RS

type-letter filename [qualifiers]
.RE
.fi
.PP
Blank lines and comment lines are not allowed.
(This is reasonable considering that input is normally generated automatically.)
.PP
.I type-letter
must be one of:
.TP 3
.B n
File contains new source code.
.TP
.B r
File contains reused source code and old code is available for comparison.
.TP
.B u
File contains reused source code and old code is not available.
.PP
.I filename
is considered to be an
.SM SCCS
file if and only if its basename starts with "s.".
A
.IR get (1)
with the
.B \-k
option (don't substitute version number, etc.) is done on any
.SM SCCS
file (into a temporary file) before computing any sizes.
.I filename
is considered to be an
.SM RCS
file if and only if its basename ends with the suffix ",v".
A
.IR co (1)
is done on any
.SM RCS
file (into a temporary file) before computing any sizes.
(Temporary files are not created until actually needed.
They are removed before exit, or on receipt of
.SM SIGHUP,
.SM SIGINT,
.SM SIGQUIT,
or
.SM SIGTERM.)
.PP
The optional qualifier for an \fBn\fR or \fBu\fR file under \fBSCCS\fR control
is a "release.level" number indicating which version of the file to
get(1).  If no qualifier is given, the top-most version is retrieved.
For an \fBr\fR file under SCCS control, the qualifier is a pair of 
release.level numbers specifying which versions of the file to compare.
The first qualifier is
assumed to be the new version while the second qualifier is assumed to be
the old version.  The qualifiers for \fBr\fR files under SCCS control cannot
be file names.  Note, while not an error, it does not make sense to have an
\fBr\fR file without qualifiers.  This would cause ssize to compare the top
level version of the file with itself.  Typical examples of SCCS file
usage would look like:
.sp 1
 	n    s.file1.c    2.1
 	u    s.file2      8.7
 	r    s.file3      4.1     3.2
.PP
The optional qualifier for an \fBn\fR or \fBu\fR file under \fBRCS\fR control
is a "release.level" number or a symbolic tag (created via the -n option of ci) indicating which version of the file to
co(1).  If no qualifier is given, the top-most version is retrieved.
For an \fBr\fR file under RCS control, the qualifier is a pair of 
rev_numbers/symbolic_tags specifying which versions of the file to compare.
The first qualifier is
assumed to be the new version while the second qualifier is assumed to be
the old version.  The qualifiers for \fBr\fR files under RCS control cannot
be file names.  Note, while not an error, it does not make sense to have an
\fBr\fR file without qualifiers.  This would cause ssize to compare the top
level version of the file with itself.  Typical examples of RCS file
usage would look like:
.sp 1
 	n    file1.c,v    2.1
 	u    file2,v      release_2
 	r    file3,v      4.1     3.2
 	r    file4,v      rel3    rel2
.sp 1
For files that are not under SCCS or RCS control, the qualifier for an \fBn\fR 
or \fBu\fR file is ignored.
For an \fBr\fR file not under SCCS or RCS control, the qualifier is the name
of the file which is compared to the original.
Typical usage would look like:
.sp 1
 	n    file1.c
 	u    file2
 	r    file3 file4
.PP
For
.B r
files,
.I ssize
runs
.IR diff (1)
on the old and new files to generate numbers of lines unchanged,
added, deleted, and modified.
Lines are considered modified when
.I diff
shows a "change" consisting of I insertions and D deletions.
The number modified is min(I,D),
and the excess are considered either added or deleted, as appropriate.
.ne 18
.PP
Output is of the form:
.nf

             -------------- REUSED CODE ---------------    CURRENT
   NEW CODE  UNCHANGED      ADDED    DELETED   MODIFIED      TOTAL
          X          X          X          X          X          X

SOURCE LINES       UNCHANGED    TOUCHED        NEW
New code                   .          .          X
Reused, unchanged          X          .          .
        added              .          X          X
        deleted            .          X          .
        modified           .          X          .
Reused, unknown            X          X          X
TOTAL                      X          X          X
.fi
.PP
The first section is the data of interest for the
.SM SIZE
form.
.SM CURRENT TOTAL
is the sum of all line counts except
.SM DELETED.
.PP
The second section is a breakdown which separates out the effects of
.B u
files and gives another way to look at the data.
Lines in
.B u
files are broken into
.SM UNCHANGED,
.SM TOUCHED,
and
.SM NEW
according to global distribution percentages (see the
.B \-p
option below).
.SM TOUCHED
lines from
.B u
files are included in
.SM MODIFIED
in the first section,
while
.SM NEW
lines are included in
.SM ADDED.
.PP
Excess additions associated with
.I diff
changes are counted as lines
.SM TOUCHED,
while purely added lines are considered
.SM NEW.
.PP
Options are:
.TP
.B \-a
Lines purely added to
.B r
files are accrued under the
.SM TOUCHED
column instead of the
.SM NEW
column, along with those added as parts of changes.
This only affects the second section of the report.
.TP
.B \-b
Ignore whitespace in
.B r
files by using the
.I diff
.B \-b
option.
This hides code changes that only affect whitespace,
including those made inside quoted strings, etc.
.TP
.BI \-n \0newrel
Set global default release (and optional level) number to use when doing a
.I get
of a new version from an
.SM SCCS
file or a 
.I co
from an
.SM RCS
file.
.TP
.BI \-o \0oldrel
Set global default release (and optional level) number to use when doing a
.I get
of an old version from an
.SM SCCS
file or a 
.I co
from an
.SM RCS
file.
.PP
.I newrel
and
.I oldrel
both default to zero,
and can be overridden for each file in the input as described elsewhere.
If either is zero,
.I ssize
does the corresponding
.I get
without qualifications.
.TP
.BI \-p \0unchanged,touched
Define overall distribution percentages for
.B u
files.
The option arguments must be integers 0-100 which don't add up to more than 100.
Each file's lines are divided into
.SM UNCHANGED
(first percentage),
.SM TOUCHED
(second percentage),
and
.SM NEW
(remainder).
Fractions are rounded off, so the total contribution from
.B u
files may not be exact.
Default percentages are "100,0",
i.e. treat all lines as
.SM UNCHANGED.
.TP
.BI \-s \0command
Strip sources so as to count/compare non-commented source lines
.SM (NCSL)
only.
All files are passed through
.I command
(e.g.
.IR cstrip (1),
.IR pstrip (1),
or
.IR shstrip (1)
to remove comments and blank lines
(those containing only whitespace)),
before counting lines or running
.IR diff .
Whatever
.I command
may be,
it must be possible to run it with no additional arguments
followed by input and output redirections.
.TP
.B \-S
Run
.IR cstrip ,
.IR pstrip ,
or
.I shstrip
as appropriate.
If the basename of a given file ends in ".c" or ".h",
.I cstrip
is used.
If the basename of a given file ends in ".p",
.I pstrip
is used.
If it ends in ".sh"
or if it does not contain a period
.I shstrip
is used.
Otherwise no stripping is done.
.PP
You can only give one of
.B \-s
and
.B \-S
at once.
.PP
All commands executed by
.I ssize
are run by basename (or for the
.BI \-s \0command,
as given), so you can potentially substitute special versions.
.SH EXAMPLES
.TP
ssize mylist
Print a report on the files listed in mylist.
.TP
ssize \-ab \-n28 \-o27.1 \-p0,100 \-scstrip a b c d
Report on files listed in files a, b, c, and d,
with purely added lines in reused files counted as
.SM TOUCHED
(not
.SM NEW),
with whitespace ignored when comparing
.B r
file versions,
doing top-level
.IR get s
of revision 28 for new versions and 27.1 for old versions,
with all lines in
.B u
files counted as
.SM TOUCHED
(not
.SM UNCHANGED
or
.SM NEW,
therefore as
.SM MODIFIED
in the first section),
and comparing non-comment source lines only (using
.IR cstrip ).
.SH FILES
.TP
/tmp/csolda<pid>
Temporary copy of old version gotten from
.SM SCCS/RCS
and/or prepared for
.IR diff .
.TP
/tmp/csnewa<pid>
Temporary copy of new version.
.SH SEE ALSO
diff(1), get(1), co(1), osize(1), prepsize(1), cstrip(1), pstrip(1), shstrip(1)
.SH WARNINGS
.B ssize
removes temporary source files gotten from
.SM SCCS/RCS
versions and/or run through a strip command.
For safety, do not run this program as any user capable of removing any of
the files being sized.
.SH LIMITATIONS
If you need to comment strip more than one type of file in one run,
and the
.B \-S
option is inadequate,
you must write some sort of front-end which is invoked with
.BI \-s \0command.
It could be a shell script which calls an appropriate comment stripper based
on the type of its input.
It must at least
.IR cat (1)
its input to its output.
.PP
The input line syntax is not very flexible.
For
.B r
files it is not possible to have one version be under
.SM SCCS/RCS
and the other not.
It is not possible to specify only one version number (e.g. just for the
old file). 
It is not possible to specify different distribution percentages for each
.B u
file.
.PP
If any source file is too large for
.IR diff ,
.I ssize
fails.
Also, it assumes
.I diff
output is valid,
and does not check it.
.PP
.I ssize
does not check numbers given with options or input lines for validity.
It just converts them with
.IR atoi (3)
or
.IR atof (3).
.SH DIAGNOSTICS
Prints a message to standard error and exits non-zero if
invoked wrong,
can't open a named file,
can't get needed memory,
detects an error in checkout command,
a strip command, or
.IR diff .
If it catches a signal,
its exit value is the signal number.
@EOF
set `sum $sumopt <ssize.1`; if test $1 -ne 57714
then
	echo ERROR: ssize.1 checksum is $1 should be 57714
fi
set `wc -lwc <ssize.1`
if test $1$2$3 != 432180810370
then
	echo ERROR: wc results of ssize.1 are $* should be 432 1808 10370
fi

chmod 444 ssize.1

echo x - ssize.c
cat >ssize.c <<'@EOF'
static char *version = "@(#) $Revision: 3.2 $   $Date: 89/02/27 11:16:10 $";
/*
 * Print source code size metrics report.
 * See the manual entry for details.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <varargs.h>

char	*malloc();
char	*realloc();

#define	PROC				/* null; easy to find procs */
#define	FALSE	  0
#define	TRUE	  1
#define	CHNULL	  ('\0')
#define	CHNEWLINE ('\n')
#define	CPNULL	  ((char *) NULL)
#define	FILENULL  ((FILE *) NULL)
#define RCSSUFF	  ",v"
#define	REG	  register
#define DEFREL	  "0"


/*********************************************************************
 * USAGE AND INVOCATION:
 */

char *usage[] = {
    "usage: %s [-ab] [-n newrel] [-o oldrel] [-p unchanged,touched]",
    "       [-s command] [-S] [files...]",
    "-a lines purely added to r files accrue under TOUCHED (default: NEW)",
    "-b ignore whitespace in r files (diff -b, treat as a single blank)",
    "-n global default new release (and optional level) number (default: 0)",
    "-o global default old release (and optional level) number (default: 0)",
    "-p percentages for UNCHANGED and TOUCHED for u files (default: 100,0)",
    "-s command to use to strip reused sources before comparing",
    "-S strip using cstrip, pstrip, or shstrip according to file basename",
    CPNULL,
};

char	*myname;			/* how program was invoked	*/

int	aflag = FALSE;			/* -a (added lines) option	*/
int	bflag = FALSE;			/* -b (ignore whitespace) opt	*/
int	Sflag = FALSE;			/* -S (auto-strip) option	*/

char	*newrel = DEFREL;		/* from -n (newrel) option	*/
char	*oldrel = DEFREL;		/* from -o (oldrel) option	*/

float	percent[] = { 1, 0, 0 };	/* values from -p option / 100	*/

#define	PCT_UNCHANGED	0		/* indices for percent[]	*/
#define	PCT_TOUCHED	1
#define	PCT_NEW		2

char	*scommand = CPNULL;		/* from -s (strip command) opt	*/


/*********************************************************************
 * MISCELLANEOUS GLOBAL VALUES:
 */

char	*defargv[] = { "-" };		/* default argument list	*/

char	*oldtempfile = "/tmp/csoldXXXXXX";  /* temporary file versions	*/
char	*newtempfile = "/tmp/csnewXXXXXX";

char	*cstrip  = "cstrip";		/* C source code stripper	*/
char	*pstrip  = "pstrip";		/* Pascal source code stripper	*/
char	*shstrip = "shstrip";		/* shell script stripper	*/

#define	CMD  0				/* for calling RunCommand()	*/
#define	PIPE 1

char	*PickSCommand();
long	RunCommand();
long	CountLines();
int	ExitNicely();


/*********************************************************************
 * TOTALS DATA STRUCTURE:
 *
 * The array accrues total line counts for printing.
 * The (extra) last row is used for grand totals.
 */

#define	ROW_NEW		0		/* row indices */
#define	ROW_UNCHANGED	1
#define	ROW_ADDED	2
#define	ROW_DELETED	3
#define	ROW_MODIFIED	4
#define	ROW_UNKNOWN	5
#define	ROW_TOTAL	6
#define	ROWMAX		ROW_TOTAL

#define	COL_UNCHANGED	0		/* column indices */
#define	COL_TOUCHED	1
#define	COL_NEW		2
#define	COLMAX		COL_NEW

long	total [ROWMAX + 1] [COLMAX + 1];	/* rows, columns */


/*********************************************************************
 * MACRO PROCEDURES:
 *
 * Basename is either the full string or past last '/', if any.
 * File is SCCS if basename starts with "s.".
 */

char	*cpBNTemp;			/* temporary */

#define	BASENAME(cp)	\
	(((cpBNTemp = strrchr (cp, '/')) == CPNULL) ? cp : (cpBNTemp + 1))

#define	SCCS(filename)	(strncmp (BASENAME (filename), "s.", 2) == 0)


/************************************************************************
 * M A I N
 *
 * Parse arguments, initialize, read data, and print report.
 */

PROC main (argc, argv)
REG	int	argc;
REG	char	**argv;
{
extern	int	optind;			/* from getopt()	*/
extern	char	*optarg;		/* from getopt()	*/
REG	int	option;			/* option "letter"	*/
REG	FILE	*filep;			/* open input file	*/
REG	char	*filename;		/* name to use		*/

/*
 * PARSE ARGUMENTS:
 */

	myname = *argv;

	while ((option = getopt (argc, argv, "abn:o:p:s:S")) != EOF)
	{
	    switch (option)
	    {
	    case 'a':	aflag = TRUE;		break;
	    case 'b':	bflag = TRUE;		break;
	    case 'S':	Sflag = TRUE;		break;

	    case 'n':	newrel	 = optarg;	break;
	    case 'o':	oldrel	 = optarg;	break;
	    case 's':	scommand = optarg;	break;

	    case 'p':	GetPercents (optarg);	break;

	    default:	Usage();
	    }
	}

	if ((scommand != CPNULL) && Sflag)
	    Error ("both -s and -S not allowed at same time");

	argc -= optind;			/* skip options	*/
	argv += optind;

/*
 * PREPARE TEMPORARY FILES AND SIGNALS:
 *
 * Temporary file names are initialized here, but the files themselves are
 * created when first needed.  Set signals IF they're not already ignored.
 */

	mktemp (oldtempfile);
	mktemp (newtempfile);

	if (signal (SIGHUP,  SIG_IGN) != SIG_IGN)
	    signal (SIGHUP, ExitNicely);

	if (signal (SIGINT,  SIG_IGN) != SIG_IGN)
	    signal (SIGINT, ExitNicely);

	if (signal (SIGQUIT,  SIG_IGN) != SIG_IGN)
	    signal (SIGQUIT, ExitNicely);

	signal (SIGTERM, ExitNicely);		/* unconditional */

/*
 * READ FROM LIST OF FILES OR STDIN:
 */

	if (argc < 1)				/* no file names */
	{
	    argc = 1;
	    argv = defargv;
	}

	while (argc-- > 0)
	{
	    if (strcmp (*argv, "-") == 0)	/* read stdin */
	    {
		filename = "<stdin>";
		filep	 = stdin;
	    }
	    else if ((filep = fopen ((filename = *argv), "r")) == FILENULL)
		Error ("can't open \"%s\" to read it", filename);

	    ReadFile (filename, filep);

	    if (filep != stdin)
		fclose (filep);			/* assume it works */

	    argv++;
	}

/*
 * PRINT REPORT:
 */

	PrintReport();
	ExitNicely (0);

} /* main */


/************************************************************************
 * U S A G E
 *
 * Print usage messages (char *usage[]) to stderr and exit nonzero.
 * Each message is followed by a newline.
 */

PROC Usage()
{
REG	int	which = 0;		/* current line */

	while (usage [which] != CPNULL)
	{
	    fprintf (stderr, usage [which++], myname);
	    fputc   (CHNEWLINE, stderr);
	}

	ExitNicely (1);

} /* Usage */


/************************************************************************
 * E R R O R
 *
 * Print an error message to stderr and exit nonzero.  Message is preceded
 * by "<myname>: " using global char *myname, and followed by a newline.
 */

/* VARARGS */
PROC Error (message, arg1, arg2, arg3, arg4)
	char	*message;
	int	arg1, arg2, arg3, arg4;
{
	fprintf (stderr, "%s: ", myname);
	fprintf (stderr, message, arg1, arg2, arg3, arg4);
	fputc   (CHNEWLINE, stderr);

	ExitNicely (1);

} /* Error */


/************************************************************************
 * W A R N
 *
 * Print a warning message to stderr.  Message is preceded
 * by "<myname>: " using global char *myname, and followed by a newline.
 */

/* VARARGS */
PROC Warn (message, arg1, arg2, arg3, arg4)
	char	*message;
	int	arg1, arg2, arg3, arg4;
{
	fprintf (stderr, "%s: warning: ", myname);
	fprintf (stderr, message, arg1, arg2, arg3, arg4);
	fputc   (CHNEWLINE, stderr);

} /* Warn */


/************************************************************************
 * E X I T   N I C E L Y
 *
 * Remove temporary files if any (ignore errors), and exit with a given
 * value (maybe a signal number if called due to a signal).
 */

PROC ExitNicely (exitval)
	int	exitval;
{
	unlink (oldtempfile);
	unlink (newtempfile);

	exit (exitval);

} /* ExitNicely */


PROC int RCS(fname)
	char	*fname;
{
	int	rc = FALSE;

	if (strlen(fname) > 1)
	    if (strcmp(fname + strlen(fname) - 2, RCSSUFF) == 0)
		rc = TRUE;

	return(rc);
}


/************************************************************************
 * G E T   P E R C E N T S
 *
 * Given a string which should contain "<number>,<number>", being two non-
 * negative integers adding to less than 101, set the values in global
 * float percent[] according to the given values.  Does not insure that the
 * integers are valid, only that comma is present and atoi() return values
 * don't add to more than 100.  Converts the comma to a CHNULL.
 */

PROC GetPercents (string)
	char	*string;	/* string to parse */
{
REG	char	*cp = string;	/* place in string */
	float	sum;		/* of first two	   */

	while (*cp != ',')
	    if (*cp++ == CHNULL)	/* no comma */
		Error ("missing comma in -p percentage values");

	*cp = CHNULL;

	percent [PCT_UNCHANGED] = atoi (string) / 100.0;  /* first number   */
	percent [PCT_TOUCHED  ] = atoi (cp + 1) / 100.0;  /* rest of string */

	if ((sum = (percent [PCT_UNCHANGED] + percent [PCT_TOUCHED])) > 1)
	    Error ("-p percentages add to more than 100");

	percent [PCT_NEW] = 1 - sum;

} /* GetPercents */


/************************************************************************
 * R E A D   F I L E
 *
 * Given a filename (for error messages) and an open stream pointer, read
 * data lines from the stream until exhausted, accumulating statistics for
 * each one, then check for error.
 *
 * char line[] is static so it's only allocated once.
 */

PROC ReadFile (filename, filep)
	char	*filename;	/* for errors	*/
REG	FILE	*filep;		/* open stream	*/
{
static	char	line [BUFSIZ];	/* input line	*/
REG	char	*cp;		/* pos in line	*/
REG	int	linenum = 0;	/* in file	*/

	while (fgets (line, BUFSIZ, filep) != CPNULL)
	{
	    if ((cp = strchr (line, CHNEWLINE)) != CPNULL)
		*cp = CHNULL;			/* terminate at newline */

	    DoInputLine (filename, ++linenum, line);
	}

	if (ferror (filep))
	    Error ("read failed from \"%s\"", filename);

} /* ReadFile */


/************************************************************************
 * D O   I N P U T   L I N E
 *
 * Given a filename and linenumber (for error messages) and an input line,
 * parse the line and accumulate statistics for one file.  Error out if
 * anything is noticeably wrong with the line.
 */

PROC DoInputLine (filename, linenum, line)
	char	*filename;		/* for errors	*/
	int	linenum;		/* for errors	*/
	char	*line;			/* to parse	*/
{
REG	char	*tokensep = " \t";	/* token separator	*/
	char	*ltp;			/* linetype pointer	*/
REG	char	linetype;		/* 'n' | 'r' | 'u'	*/
	char	*dofilename;		/* file to size		*/
	char	*qual1, *qual2;		/* fields 3 and 4	*/

/*
 * PARSE LINE INTO TOKENS:
 */

	if (((ltp = strtok (line, tokensep)) == CPNULL)
	 || (((linetype = *ltp) != 'n')
	  && (linetype != 'r') && (linetype != 'u')))
	{
	    Warn ("missing or invalid type-letter in file \"%s\", line %d",
		   filename, linenum);
	}

	else if ((dofilename = strtok (CPNULL, tokensep)) == CPNULL)
	    Warn ("missing filename in file \"%s\", line %d",
		filename, linenum);

	else if (((qual1 = strtok (CPNULL, tokensep)) != CPNULL)
	 && ((qual2 = strtok (CPNULL, tokensep)) != CPNULL)
	 && ((linetype != 'r') || (strtok (CPNULL, tokensep) != CPNULL)))
	{
	    Warn ("too many tokens in file \"%s\", line %d",
		filename, linenum);
	}

/*
 * HANDLE DIFFERENT LINE TYPES:
 */

	else if (linetype == 'r')
	    DoReused (filename, linenum, dofilename, qual1, qual2);
	else
	    DoNewUnknown (linetype, dofilename, qual1);

} /* DoInputLine */


/************************************************************************
 * D O   N E W   U N K N O W N
 *
 * Given a line type, a filename to size, and a qualifier string (may be
 * null), accumulate statistics for one new or reused-unknown file,
 * possibly comment-stripped first, into global long total[], possibly
 * using global float percent[].
 */

PROC DoNewUnknown (linetype, dofilename, qual1)
	char	linetype;	/* 'n' | 'u'	*/
REG	char	*dofilename;	/* file to size	*/
	char	*qual1;		/* qualifier	*/
{
REG	long	lines;		/* in the file	*/
	char	*scmd = PickSCommand (dofilename);

/*
 * COUNT LINES IN FILE (retrieve files from SCCS or RCS as necessary).:
 */

	if (SCCS (dofilename))
	{
	    GetSccsFile (newtempfile, dofilename, qual1, newrel);
	    dofilename = newtempfile;
	}
	else if (RCS (dofilename))
	{
	    GetRcsFile (newtempfile, dofilename, qual1, newrel);
	    dofilename = newtempfile;
	}
	else if (scmd != CPNULL)		/* need to strip file */
	{
	    StripFile (scmd, dofilename, newtempfile);
	    dofilename = newtempfile;
	}

	lines = CountLines (dofilename);

/*
 * SAVE LINE COUNT IN APPROPRIATE PLACE:
 */

	if (linetype == 'n')			/* new file */
	    (total [ROW_NEW] [COL_NEW]) += lines;
	else					/* unknown, distribute count */
	{
	    (total [ROW_UNKNOWN] [COL_UNCHANGED]) +=
		(long) ((lines * percent [PCT_UNCHANGED]) + 0.5);  /* round */

	    (total [ROW_UNKNOWN] [COL_TOUCHED]) +=
		(long) ((lines * percent [PCT_TOUCHED]) + 0.5);

	    (total [ROW_UNKNOWN] [COL_NEW]) +=
		(long) ((lines * percent [PCT_NEW]) + 0.5);
	}

} /* DoNewUnknown */


/************************************************************************
 * D O   R E U S E D
 *
 * Given a filename and linenumber (for error messages), a filename to size
 * and qualifier strings (second or both may be null), accumulate statistics
 * for two versions of one file into global long total[].  Warn if
 * anything goes wrong.
 */

PROC DoReused (filename, linenum, dofilename, qual1, qual2)
	char	*filename;	/* for errors	*/
	int	linenum;	/* for errors	*/
REG	char	*dofilename;	/* file to size	*/
	char	*qual1, *qual2;	/* qualifiers	*/
{
REG	char	*oldfilename;	/* files to diff */
REG	char	*newfilename;

	char	*scmd = PickSCommand (dofilename);
	int	onequal;	/* exactly one qualifier given? */
	int	error = FALSE;

	long	unchanged = 0;	/* line count	*/
	long	addpure	  = 0;	/* pure inserts	*/
	long	addmod	  = 0;	/* part of mods	*/
	long	deleted	  = 0;	/* line count	*/
	long	modified  = 0;	/* line count	*/

	onequal = ((qual1 != CPNULL) && (qual2 == CPNULL));

/*
 * SCCS FILE: PREPARE FILES AND FILENAMES:
 *
 * Files are also run through a strip command if needed.
 */

	if (SCCS (dofilename))
	{
	    if (onequal)
	    {
		Warn (
	    "wrong number of qualifiers for SCCS r file, file \"%s\", line %d",
			filename, linenum);
		error = TRUE;
	    }
	    else
	    {
		newfilename = newtempfile;
		oldfilename = oldtempfile;

		GetSccsFile (newfilename, dofilename, qual1, newrel);
		GetSccsFile (oldfilename, dofilename, qual2, oldrel);
	    }
	}

/*
 * RCS FILE: PREPARE FILES AND FILENAMES:
 *
 * Files are also run through a strip command if needed.
 */

	else if (RCS (dofilename))
	{
	    if (onequal)
	    {
		Warn (
	    "wrong number of qualifiers for RCS r file, file \"%s\", line %d",
			filename, linenum);
		error = TRUE;
	    }
	    else
	    {
		newfilename = newtempfile;
		oldfilename = oldtempfile;

		GetRcsFile (newfilename, dofilename, qual1, newrel);
		GetRcsFile (oldfilename, dofilename, qual2, oldrel);
	    }
	}

/*
 * NON-SCCS/RCS FILE: PREPARE FILES AND FILENAMES:
 *
 * Files are specially run through a strip command if needed.
 */

	else
	{
	    if (! onequal)
	    {
		Warn (
	"wrong number of qualifiers for non-SCCS r file, file \"%s\", line %d",
			filename, linenum);
		error = TRUE;      
	    }
	    else 
	    {
		if (scmd == CPNULL)			/* no strip command */
		{
		    newfilename = dofilename;	/* use given names */
		    oldfilename = qual1;
		}
		else				/* do strip files */
		{
		    StripFile (scmd, dofilename, (newfilename = newtempfile));
		    StripFile (scmd, qual1,      (oldfilename = oldtempfile));
		}
	    }
	}

	/* now newfilename and oldfilename are set */

	if (! error) {
/*
 * COMPARE OLD AND NEW FILES:
 */

	    CountDiffs (oldfilename, newfilename,
			& addpure, & addmod, & deleted, & modified);

	    unchanged = CountLines (oldfilename) - deleted - modified;

/*
 * SAVE STATISTICS IN APPROPRIATE PLACES:
 */

	    (total [ROW_UNCHANGED] [COL_UNCHANGED])			+= unchanged;
	    (total [ROW_ADDED    ] [aflag ? COL_TOUCHED : COL_NEW])	+= addpure;
	    (total [ROW_ADDED    ] [COL_TOUCHED])			+= addmod;
	    (total [ROW_DELETED  ] [COL_TOUCHED])			+= deleted;
	    (total [ROW_MODIFIED ] [COL_TOUCHED])			+= modified;
	}

} /* DoReused */


/************************************************************************
 * G E T   S C C S   F I L E
 *
 * Given a temporary filename, an SCCS filename (pathname), a release [and
 * level] number, and a default number to use if null, do a get(1) and
 * possible comment strip of the file into the temp file, possibly at the
 * top of the given (or default) release/level.  Terminate if the get fails.
 */

PROC GetSccsFile (tempfilename, sccsfilename, release, defrel)
REG	char	*tempfilename;	/* where to write data	*/
REG	char	*sccsfilename;	/* file to get		*/
REG	char	*release;	/* number or null	*/
	char	*defrel;	/* default release num	*/
{
	char	*scmd = PickSCommand (sccsfilename);
	char	*relopt;
REG	int	result;		/* of get command	*/

	if (release == CPNULL)			/* not given   */
	    release = defrel;			/* use default */


/*
 * SET UP RELEASE OPTION STRING.
 */

	if (atof (release) == 0)		/* no number in effect */
	{
	    relopt = malloc(5 + strlen(" ") + 1);
	    sprintf (relopt, "%.*s", 5 + strlen(" ") + 1, " ");
	}
	else					/* release number known */
	{
	    relopt = malloc(5 + strlen(release) + 1);
	    sprintf (relopt, "-t -r%.*s", 5 + strlen(release) + 1, release);
	}

	/* now result is set to the return from a command line */

/*
 * EXECUTE COMMAND, PIPING RESULTS THROUGH STRIPPER IF NECESSARY.
 */

	if (scmd == CPNULL)			/* no strip command */
	    result = RunCommand (CMD, "get -k -s -p", relopt, sccsfilename, 
				 " > ", tempfilename, CPNULL);
	else
	    result = RunCommand (CMD, "get -k -s -p", relopt, sccsfilename, 
				 "|", scmd, ">", tempfilename, CPNULL);

/*
 * CHECK RESULTS:
 */

	if (result)
	    Error ("get %sof file \"%s\" into file \"%s\" failed",
		  (scmd == CPNULL) ? "" : "and strip ",
		  sccsfilename, tempfilename);

} /* GetSccsFile */


/************************************************************************
 * G E T   R C S   F I L E
 *
 * Given a temporary filename, an RCS filename (pathname), a release [and
 * level] number, and a default number to use if null, do a co(1) and
 * possible comment strip of the file into the temp file, possibly at the
 * top of the given (or default) release/level.  Terminate if the co fails.
 */

PROC GetRcsFile (tempfilename, rcsfilename, release, defrel)
REG	char	*tempfilename;	/* where to write data	*/
REG	char	*rcsfilename;	/* file to get		*/
REG	char	*release;	/* number or null	*/
	char	*defrel;	/* default release num	*/
{
	char	*scmd;
	char	*tempname;
REG	int	result;		/* of get command	*/
   	char 	*relopt; 	/* option string for co(1)  */

	tempname = malloc(strlen(rcsfilename));
	strncpy(tempname, rcsfilename, (strlen(rcsfilename) - 2));
	tempname[strlen(rcsfilename) - 2] = CHNULL;
	scmd = PickSCommand(tempname);

/*
 * SET UP RELEASE OPTION STRING.
 */

	if (release != CPNULL)			/* release given */
	{
	    relopt = malloc(5 + strlen(release) + 1);
	    sprintf (relopt, "-r%.*s", 5 + strlen(release) + 1, release);
	}
	else if (strcmp(defrel, DEFREL) != 0)   /* default release given */
	{
	    relopt = malloc(5 + strlen(defrel) + 1);
	    sprintf (relopt, "-r%.*s", 5 + strlen(defrel) + 1, defrel);
	}
	else					/* use top level, i.e relopt is blank */
	{
	    relopt = malloc(5 + strlen(" ") + 1);
	    sprintf (relopt, "%.*s", 5 + strlen(" ") + 1, " ");
	}

/*
 * EXECUTE COMMAND, PIPING RESULTS THROUGH STRIPPER IF NECESSARY.
 */

	if (scmd == CPNULL)			/* no strip command */
	    result = RunCommand (CMD, "co  -q -p ", relopt, rcsfilename, 
				 " > ", tempfilename, CPNULL);
	else
	    result = RunCommand (CMD, "co  -q -p ", relopt, rcsfilename,
				 "|", scmd, ">", tempfilename, CPNULL);

/*
 * CHECK RESULTS:
 */

	if (result)
	    Error ("co %sof file \"%s\" into file \"%s\" failed",
		  (scmd == CPNULL) ? "" : "and strip ",
		  rcsfilename, tempfilename);

} /* GetRcsFile */


/************************************************************************
 * S T R I P   F I L E
 *
 * Given the names of a strip command (non-null) and source and temporary
 * files, strip the source file into the temp file using the command.
 * Error out if it fails.
 */

PROC StripFile (scommand, sourcefile, tempfile)
	char	*scommand;
	char	*sourcefile;
	char	*tempfile;
{
	if (RunCommand (CMD, scommand, "<", sourcefile, ">", tempfile, CPNULL))
	{
	    Error ("strip from file \"%s\" to file \"%s\" failed",
		    sourcefile, tempfile);
	}
} /* StripFile */


/************************************************************************
 * P I C K   S   C O M M A N D
 *
 * Given the name of a source file, and global char* scommand, cstrip, pstrip,
 * and shstrip, return the name of a strip command to use for the file, or
 * CPNULL if none is needed.
 *
 * Stripping is needed if scommand != CPNULL (a name was given by the user) or,
 * if Sflag is true, if the basename ends in ".c", ".h", ".p", or ".sh", or if
 * it contains no "."  and the file is executable (probably a shell script).
 */

PROC char * PickSCommand (filename)
	char	*filename;
{
	char	*basename;	/* of filename */
	int	length;		/* of basename */

	if (scommand != CPNULL)
	    return (scommand);		/* use given command */

	if (! Sflag)
	    return (CPNULL);		/* no stripping needed */

	length = strlen (basename = BASENAME (filename));

	if ((length >= 2)
	 && ((strcmp (basename + length - 2, ".c") == 0)
	  || (strcmp (basename + length - 2, ".h") == 0)))
	{
	    return (cstrip);		/* use C code stripper */
	}

	if ((length >= 2) && (strcmp (basename + length - 2, ".p") == 0))
	{
	    return (pstrip);		/* use Pascal code stripper */
	}

	if (((length >= 3) && (strcmp (basename + length - 3, ".sh") == 0))
	 || (strchr (basename, '.') == CPNULL))
	{
	    return (shstrip);		/* use shell script stripper */
	}

	return (CPNULL);		/* no stripping needed */

} /* PickSCommand */


/************************************************************************
 * C O U N T   L I N E S
 *
 * Given a filename, open the file and count and return how many lines
 * (newline characters) it contains.  Error out if open or read fails.
 */

PROC long CountLines (filename)
	char	*filename;	/* file to count  */
{
REG	FILE	*filep;		/* for opening it */
REG	char	ch;		/* read from file */
REG	long	lines = 0;	/* count of lines */

	if ((filep = fopen (filename, "r")) == FILENULL)
	    Error ("can't open file \"%s\" to count lines", filename);

	while ((ch = getc (filep)) != EOF)	/* count lines */
	    if (ch == CHNEWLINE)
		lines++;

	if (ferror (filep))
	    Error ("read from file \"%s\" failed", filename);

	fclose (filep);				/* assume it works */
	return (lines);

} /* CountLines */


/************************************************************************
 * C O U N T   D I F F S
 *
 * Given old and new filenames to compare, and pointers to four values
 * to return, diff the two files, analyze the results, and return values
 * through the pointers.  Error out if diff can't be run or it fails.
 */

PROC CountDiffs (oldfilename, newfilename,
		 addpurep, addmodp, deletedp, modifiedp)

	char	*oldfilename;	/* for diff		*/
	char	*newfilename;	/* for diff		*/
	long	*addpurep;	/* purely inserted	*/
	long	*addmodp;	/* inserted with modify	*/
	long	*deletedp;	/* deleted lines	*/
	long	*modifiedp;	/* modified lines	*/
{
REG	FILE	*pipep;		/* to read pipeline	*/
	int	result;		/* of pipeline		*/

	if ((pipep = (FILE *) RunCommand (PIPE, "diff", (bflag ? "-b" : ""),
					  oldfilename, newfilename, CPNULL))
	    == FILENULL)
	{
	    Error ("can't run diff on files \"%s\" and \"%s\"", 
		      oldfilename, newfilename);
	}

	CountDiffLines (pipep, addpurep, addmodp, deletedp, modifiedp);

	if (ferror (pipep))
	    Error ("read from diff command failed");

	if (((result = pclose (pipep)) < 0)	/* pclose() failed	  */
	 || (result && (result != (1 << 8))))	/* not exit(0) or exit(1) */
	{
	    Error ("diff of files \"%s\" and \"%s\" failed",
		   oldfilename, newfilename);
	}

} /* CountDiffs */


/************************************************************************
 * C O U N T   D I F F   L I N E S
 *
 * Given a read pipeline from a diff command, and pointers to four values
 * to return, analyze the diff output, and return values through the
 * pointers.  It's up to the caller to check for I/O errors.
 *
 * Each diff output line must begin with one of:
 *
 *	-	separator line (ignored)
 *	[0-9]	mode line, contains "a", "d", or "c"
 *		(meaning an add, delete, or change section follows)
 *	<	deletion
 *	>	insertion
 *
 * Lines in "delete" sections must start with "<"; they accrue to "del".
 * Lines in "add"    sections must start with ">"; they accrue to "add".
 * Lines in "change" sections start with either; they accrue accordingly.
 *
 * NOT VERY ROBUST:  Does not check diff output for validity.
 * Also, expects the first line to be a digit (mode) line.
 */

PROC CountDiffLines (pipep, addpurep, addmodp, deletedp, modifiedp)
REG	FILE	*pipep;		/* to read input	*/
	long	*addpurep;	/* purely inserted	*/
	long	*addmodp;	/* inserted with modify	*/
	long	*deletedp;	/* deleted lines	*/
	long	*modifiedp;	/* modified lines	*/
{
static	char	line [BUFSIZ];	/* only allocate once	*/
REG	char	chtype;		/* first char of line	*/
REG	char	chmode = '?';	/* of current section	*/
REG	int	add, del;	/* line counts		*/

/*
 * READ LINES UNTIL EOF:
 */

	while (fgets (line, BUFSIZ, pipep) != CPNULL)
	{
	    if ((chtype = line [0]) == '-')	/* separator line */
		continue;			/* ignore it	  */

/*
 * START OF NEW SECTION:
 */

	    if ((chtype >= '0') && (chtype <= '9'))
	    {
		REG char *cp = line;

		/* save previous values, if any (none the first time) */

		SaveDiffCounts (chmode, add, del,
				addpurep, addmodp, deletedp, modifiedp);

		add = del = 0;			/* for next section */

		do {				/* find new mode */
		    chmode = *(++cp);
		} while ((chmode != 'a') && (chmode != 'd') && (chmode != 'c'));

		continue;			/* next input line */
	    }

/*
 * ACCRUE CHANGE COUNTS:
 *
 * At this point we must have an insert (">") or delete ("<") line.
 * Assume that all lines in "add" sections are insertions and all in "delete"
 * sections are deletions.
 */

	    if ((chmode == 'a') || (chtype == '>'))
		add++;
	    else
		del++;

	} /* while */

/*
 * SAVE COUNTS FOR LAST SECTION:
 */

	SaveDiffCounts (chmode, add, del,
			addpurep, addmodp, deletedp, modifiedp);

} /* CountDiffLines */


/************************************************************************
 * S A V E   D I F F   C O U N T S
 *
 * Given a section mode letter, added and deleted line counts for the
 * section, and pointers to four totals values, accrue the line counts to
 * the totals as appropriate.  chmode may be invalid (when just starting
 * the first section), in which case, save nothing.
 *
 * Lines in "delete" sections accrue to deleted.
 * Lines in "add"    sections accrue to addpure.
 * Lines in "change" sections accrue min(del,add) to modified and the
 * excess to either addmod or deleted, as appropriate.
 */

PROC SaveDiffCounts (chmode, add, del, addpurep, addmodp, deletedp, modifiedp)
REG	char	chmode;		/* section mode letter	*/
REG	int	add;		/* lines added		*/
REG	int	del;		/* lines deleted	*/
	long	*addpurep;	/* purely inserted	*/
	long	*addmodp;	/* inserted with modify	*/
	long	*deletedp;	/* deleted lines	*/
	long	*modifiedp;	/* modified lines	*/
{
	if (chmode == 'a')		/* an add section */
	    *addpurep += add;

	else if (chmode == 'd')		/* a delete section */
	    *deletedp += del;

	else if (chmode == 'c')		/* a change section */
	{
	    if (del < add)		/* excess additions */
	    {
		*modifiedp += del;
		*addmodp   += add - del;
	    }
	    else			/* excess deletions (if any) */
	    {
		*modifiedp += add;
		*deletedp  += del - add;
	    }
	}

} /* SaveDiffCounts */


/************************************************************************
 * R U N   C O M M A N D
 *
 * Given a series of arguments terminated by CPNULL, build a command line
 * in malloc'd space starting with the second argument (type char *),
 * concatenated with blank separators.  Call the command line using
 * system(3) or popen(3) (according to the first argument, an int with
 * value CMD or PIPE), free the space, and return the return value from
 * system() (a wait(2) return value) or popen() (a FILE *).  In the popen()
 * case, the pipeline is left open for reading.
 *
 * HARDWARE DEPENDENT, might fail if sizeof (FILE *) > sizeof (long).
 */

/* VARARGS */
PROC long RunCommand (va_alist)
	va_dcl
{
REG	va_list	argp;		/* to traverse args	 */
REG	char	*cp;		/* one arg string	 */
	int	runtype;	/* type of call to do	 */
REG	char	*command;	/* command line to run	 */
REG	int	size = 1;	/* of cmd including null */
REG	int	newsize;	/* of new part		 */
	long	result;		/* of running command	 */

/*
 * INITIALIZE:
 */

	if ((command = malloc (size)) == CPNULL)
	    Error ("malloc failed");

	*command = CHNULL;

	va_start (argp);

	runtype = va_arg (argp, int);

/*
 * BUILD COMMAND LINE:
 *
 * Tack on each new part after a blank separator.
 */

	while ((cp = va_arg (argp, char *)) != CPNULL)
	{
	    if ((command =
		    realloc (command, size + 1 + (newsize = strlen (cp))))
		== CPNULL)
	    {
		Error ("realloc failed");
	    }

	    command [size - 1] = ' ';		/* separator	   */
	    strcpy (command + size, cp);	/* concat new part */
	    size += 1 + newsize;
	}

	va_end (argp);

/*
 * RUN COMMAND LINE:
 */

	result = (runtype == CMD) ? system (command)
				  : (long) popen (command, "r");

	free (command);
	return (result);

} /* RunCommand */


/************************************************************************
 * P R I N T   R E P O R T
 *
 * Given global long total[], print a report using those totals.
 * For printing the first section, assumes that certain values in total[]
 * are zero; does not bother to add them.
 */

#define	TITLEWIDTH 17			/* size of row titles	 */
#define	NUMWIDTH   11			/* size of number fields */

char	*rowtitle[] = {			/* for printing second section */
		"New code",
		"Reused, unchanged",
		"        added",
		"        deleted",
		"        modified",
		"Reused, unknown",
		"TOTAL",
};

PROC PrintReport()
{
	long	new, unchanged, added, deleted, modified;
REG	int	row, col;		/* current entry */

/*
 * PRINT FIRST SECTION:
 */

    puts ("             -------------- REUSED CODE ---------------    CURRENT");
    puts ("   NEW CODE  UNCHANGED      ADDED    DELETED   MODIFIED      TOTAL");

	new	  = total [ROW_NEW]	  [COL_NEW];

	unchanged = total [ROW_UNCHANGED] [COL_UNCHANGED] +
		    total [ROW_UNKNOWN]   [COL_UNCHANGED];

	added	  = total [ROW_ADDED]	  [COL_TOUCHED]	+
		    total [ROW_ADDED]	  [COL_NEW]	+
		    total [ROW_UNKNOWN]   [COL_NEW];

	deleted	  = total [ROW_DELETED]   [COL_TOUCHED];

	modified  = total [ROW_MODIFIED]  [COL_TOUCHED]	+
		    total [ROW_UNKNOWN]   [COL_TOUCHED];

	PrintNumber (new);
	PrintNumber (unchanged);
	PrintNumber (added);
	PrintNumber (deleted);
	PrintNumber (modified);
	PrintNumber (new + unchanged + added + modified);  /* no deleted */

/*
 * PRINT SECOND SECTION:
 */

	puts ("\n\nSOURCE LINES       UNCHANGED    TOUCHED        NEW");

	for (row = 0; row <= ROWMAX; row++)	/* does totals line too */
	{
	    printf ("%-*s", TITLEWIDTH, rowtitle [row]);

	    for (col = 0; col <= COLMAX; col++)
	    {
		PrintNumber (total [row] [col]);
		total [ROWMAX] [col] += total [row] [col];
	    }

	    putchar (CHNEWLINE);
	}

} /* PrintReport */


/************************************************************************
 * P R I N T   N U M B E R
 *
 * Given a number, print it in width NUMWIDTH if non-zero, else print a
 * dot in the field.
 */

PROC PrintNumber (number)
	long	number;
{
	if (number)	printf ("%*ld", NUMWIDTH, number);
	else		printf ("%*s",  NUMWIDTH, ".");

} /* PrintNumber */
@EOF
set `sum $sumopt <ssize.c`; if test $1 -ne 14192
then
	echo ERROR: ssize.c checksum is $1 should be 14192
fi
set `wc -lwc <ssize.c`
if test $1$2$3 != 1214494231502
then
	echo ERROR: wc results of ssize.c are $* should be 1214 4942 31502
fi

chmod 444 ssize.c

exit 0

