scpp - a selective C preprocessor (Part 2 of 2)

sources-request at panda.UUCP sources-request at panda.UUCP
Fri Sep 20 23:24:21 AEST 1985


Mod.sources:  Volume 3, Issue 14
Submitted by: decvax!tektronix!tekig4!bradn


This is the second half of scpp.  The first half contains a brief explanation
of the purpose of the program.

Brad Needham
Tektronix, Inc.
...decvax!tektronix!tekig4!bradn

-------- cut along the dashed line -----
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	parse.y
#	scpp.c
#	scpp.h
# This archive created: Thu Sep 19 12:35:40 1985
export PATH; PATH=/bin:$PATH
echo shar: extracting "'parse.y'" '(10067 characters)'
if test -f 'parse.y'
then
	echo shar: will not over-write existing file "'parse.y'"
else
sed 's/^	X//' << \SHAR_EOF > 'parse.y'
	X/*
	X * parse.y - #if parser for the selective C preprocessor, scpp.
	X *
	X * Copyright (c) 1985 by
	X * Tektronix, Incorporated Beaverton, Oregon 97077
	X * All rights reserved.
	X *
	X * Permission is hereby granted for personal, non-commercial
	X * reproduction and use of this program, provided that this
	X * notice and all copyright notices are included in any copy.
	X */
	X
	X%term	MUL		/* *		*/
	X%term	DIV		/* /		*/
	X%term	MOD		/* %		*/
	X%term	PLUS		/* +		*/
	X%term	MINUS		/* -		*/
	X%term	LS		/* <<		*/
	X%term	RS		/* >>		*/
	X%term	AND		/* &		*/
	X%term	OR		/* |		*/
	X%term	ER		/* ^		*/
	X%term	LT		/* <		*/
	X%term	LE		/* <=		*/
	X%term	GT		/* >		*/
	X%term	GE		/* >=		*/
	X%term	EQ		/* ==		*/
	X%term	NE		/* !=		*/
	X%term	ANDAND		/* &&		*/
	X%term	OROR		/* ||		*/
	X%term	CM		/* , (comma)	*/
	X%term	QUEST		/* ?		*/
	X%term	COLON		/* :		*/
	X%term	NOT		/* !		*/
	X%term	COMPL		/* ~		*/
	X%term	LP		/* (		*/
	X%term	RP		/* )		*/
	X%term	INT		/* an integer	*/
	X%term	FLOAT		/* a float	*/
	X%term	IDENT		/* a identifier	*/
	X%term	QUOTE		/* ' (apostrophe) */
	X%term	DQUOTE		/* "		*/
	X%term	BACKS		/* \ (backslash) */
	X%term	OPENC		/* open comment sequence */
	X%term	CLOSEC		/* close comment sequence */
	X%term	WHITE		/* whitespace	*/
	X%term	NL		/* newline	*/
	X%term	QNL		/* escaped (quoted) newline	*/
	X%term	COMMENT		/* a comment	*/
	X%term	OTHER		/* anything else */
	X%term	STRING		/* a double-quote enclosed string constant	*/
	X%term	CHARS		/* a single-quote enclosed char constant	*/
	X%term	POUNDLINE	/*
	X			 * The initial '#' of a preprocessor directive
	X			 * (as opposed to a normal '#', which is of type OTHER).
	X			 */
	X%term	DEFMAC		/* an uninterpreted 'defined(x)' invocation	*/
	X
	X%left	CM
	X%right	QUEST COLON
	X%left	OROR
	X%left	ANDAND
	X%left	OR
	X%left	ER
	X%left	AND
	X%left	EQ NE
	X%left	LT LE GE GT
	X%left	LS RS
	X%left	PLUS MINUS
	X%left	MUL DIV MOD
	X%right	NOT COMPL
	X%left	LP
	X
	X%union {
	X	int intval;		/* yacc stack entries	*/
	X	struct anode *lexval;	/* everything in this file	*/
	X}
	X
	X%type <lexval>	exp e term
	X%type <lexval> MUL DIV MOD PLUS MINUS LS RS AND OR ER LT LE GT GE EQ NE
	X		ANDAND OROR CM QUEST COLON NOT COMPL LP RP INT FLOAT IDENT
	X		QUOTE DQUOTE BACKS OPENC CLOSEC WHITE NL QNL COMMENT OTHER
	X		STRING CHARS POUNDLINE DEFMAC
	X
	X%{
	X# include "scpp.h"
	X
	X/*
	X * struct anode - the structure used to pass strings.
	X *  Allocated by mknode();
	X *  Deallocated by freenode().
	X * The string described will be in pend[] and is NOT NULL-TERMINATED.
	X */
	X
	Xstruct anode {
	X	int an_val;	/*
	X			 * lexical (token) value of this string.
	X			 * A value of 0 == this node is free.
	X			 */
	X	int an_ifval;	/* integer result of this expression */
	X};
	X
	X# define NODESIZ 100	/* max number of nodes in a #if expresssion	*/
	Xstruct anode nodepool[NODESIZ];
	X
	Xstruct anode *mknode();
	X
	X# define NIL ((struct anode *) 0)
	X%}
	X
	X%start exp
	X%%
	Xexp:	e
	X		{
	X			/*
	X			 * If the expression can be evaluated, set the result
	X			 */
	X
	X			if ($1->an_val == INT) {
	X				*curif |= $1->an_ifval != 0 ?
	X				  IF_TRUE : IF_FALSE;
	X			}
	X			freenode($1);
	X		}
	X	;
	Xe:	  e MUL e
	X		{
	X			$1->an_ifval = $1->an_ifval * $3->an_ifval;
	X		binop:
	X			$$ = $1;
	X			if ($1->an_val == INT && $3->an_val == INT) {
	X				$$->an_val == INT;
	X			} else {
	X				$$->an_val = OTHER;
	X			}
	X			freenode($2);
	X			freenode($3);
	X		}
	X	| e DIV e
	X		{
	X		  if ($3->an_ifval == 0 && $3->an_val == INT) {
	X			$3->an_val = OTHER;
	X			warnf("division by zero in #if");
	X		  } else {
	X			$1->an_ifval = $1->an_ifval / $3->an_ifval;
	X		  }
	X		  goto binop;
	X		}
	X	| e MOD e
	X		{
	X		  if ($3->an_ifval == 0 && $3->an_val == INT) {
	X			$3->an_val = OTHER;
	X			warnf("mod by zero in #if");
	X		  } else {
	X			$1->an_ifval = $1->an_ifval % $3->an_ifval;
	X		  }
	X		  goto binop;
	X		}
	X	| e PLUS e
	X		{$1->an_ifval = $1->an_ifval + $3->an_ifval; goto binop;}
	X	| e MINUS e
	X		{$1->an_ifval = $1->an_ifval - $3->an_ifval; goto binop;}
	X	| e LS e
	X		{$1->an_ifval = $1->an_ifval << $3->an_ifval; goto binop;}
	X	| e RS e
	X		{$1->an_ifval = $1->an_ifval >> $3->an_ifval; goto binop;}
	X	| e LT e
	X		{$1->an_ifval = $1->an_ifval < $3->an_ifval; goto binop;}
	X	| e GT e
	X		{$1->an_ifval = $1->an_ifval > $3->an_ifval; goto binop;}
	X	| e LE e
	X		{$1->an_ifval = $1->an_ifval <= $3->an_ifval; goto binop;}
	X	| e GE e
	X		{$1->an_ifval = $1->an_ifval >= $3->an_ifval; goto binop;}
	X	| e EQ e
	X		{$1->an_ifval = $1->an_ifval == $3->an_ifval; goto binop;}
	X	| e NE e
	X		{$1->an_ifval = $1->an_ifval != $3->an_ifval; goto binop;}
	X	| e AND e
	X		{$1->an_ifval = $1->an_ifval & $3->an_ifval; goto binop;}
	X	| e ER e
	X		{$1->an_ifval = $1->an_ifval ^ $3->an_ifval; goto binop;}
	X	| e OR e
	X		{$1->an_ifval = $1->an_ifval | $3->an_ifval; goto binop;}
	X	| e ANDAND e
	X		{
	X			/*
	X			 * since this is a logical AND, its value
	X			 *  is known if either subexpression is false.
	X			 */
	X
	X			$$ = $1;
	X			if ($1->an_val == INT && $3->an_val == INT) {
	X				/* both subexpressions are known */
	X				$$->an_ifval = $1->an_ifval && $3->an_ifval;
	X			} else {
	X				if (($1->an_val == INT && !$1->an_ifval) ||
	X				    ($3->an_val == INT && !$3->an_ifval)) {
	X					$$->an_val = INT;
	X					$$->an_ifval = FALSE;
	X				} else {
	X					$$->an_val = OTHER;
	X				}
	X			}
	X			freenode($2); freenode($3);
	X		}
	X	| e OROR e
	X		{
	X			/*
	X			 * since this is a logical OR, its value
	X			 *  is known if either subexpression is true.
	X			 */
	X
	X			$$ = $1;
	X			if ($1->an_val == INT && $3->an_val == INT) {
	X				/* both subexpressions are known */
	X				$$->an_ifval = $1->an_ifval || $3->an_ifval;
	X			} else {
	X				if (($1->an_val == INT && $1->an_ifval) ||
	X				    ($3->an_val == INT && $3->an_ifval)) {
	X					$$->an_val = INT;
	X					$$->an_ifval = TRUE;
	X				} else {
	X					$$->an_val = OTHER;
	X				}
	X			}
	X			freenode($2); freenode($3);
	X		}
	X	| e QUEST e COLON e
	X		{
	X			/*
	X			 * since this is an IF-ELSE, its value is known
	X			 * in some cases even if one subexpression is unknown.
	X			 */
	X
	X			$$ = $1;
	X			if ($1->an_val == INT) {
	X				if ($1->an_ifval) {
	X					$$->an_val = $3->an_val;
	X					$$->an_ifval = $3->an_ifval;
	X				} else {
	X					$$->an_val = $5->an_val;
	X					$$->an_ifval = $5->an_ifval;
	X				}
	X			} else {
	X				$$->an_val = OTHER;
	X			}
	X			freenode($2); freenode($3); freenode($4);
	X			freenode($5);
	X		}
	X	| e CM e
	X		{
	X			/*
	X			 * since this is a comma operator, the value of
	X			 * the first expression is irrelevant.
	X			 */
	X
	X			$$ = $3;
	X			freenode($1);
	X			freenode($2);
	X		}
	X	| term
	X		{$$ = $1;}
	X	;
	Xterm:
	X	  MINUS term
	X		{
	X			$2->an_ifval = -($2->an_ifval);
	X		unop:
	X			$$ = $2;
	X			freenode($1);
	X		}
	X	| NOT term
	X		{$2->an_ifval = !($2->an_ifval); goto unop;}
	X	| COMPL term
	X		{$2->an_ifval = ~($2->an_ifval); goto unop;}
	X	| LP e RP
	X		{
	X			$$ = $2;
	X			freenode($1); freenode($3);
	X		}
	X	| INT
	X		{$$= $1;}
	X	| IDENT
	X		{/* an uninterpreted macro */ $$ = $1;}
	X	| DEFMAC
	X		{/* an uninterpreted 'defined(x)' invocation */ $$ = $1;}
	X	;
	X%%
	X
	Xyyerror(s)
	Xchar *s;
	X{
	X	struct anode *anp;
	X
	X	/* free all nodes */
	X
	X	for (anp = &nodepool[0]; anp < &nodepool[NODESIZ]; anp++) {
	X		anp->an_val = 0;
	X	}
	X	warnf("syntax error in #if");
	X}
	X
	X/*
	X * yylex() - the lexical analyzer for #if statements.
	X *  yylex() reads from the stream of interpreted macros, skipping
	X *  insignificant tokens, then sets yylval appropriately and returns
	X *  the token number of the token.
	X */
	X
	Xint
	Xyylex()
	X{
	X	int tok;
	X
	X
	X	/*
	X	 * Skip whitespace, quoted newlines, and interpreted preprocessor
	X	 * directives;
	X	 * End-of-file or an unquoted newline marks the end of the parse;
	X	 * calculate the value of integers and character constants.
	X	 */
	X
	X	if (!(yylval.lexval = mknode())) {
	X		return(0);
	X	}
	X
	X	while ((tok = gintok()) == WHITE || tok == COMMENT || tok == QNL)
	X		;
	X
	X	if (tok == 0 || tok == NL) {
	X		freenode(yylval.lexval);
	X		yylval.lexval = NIL;
	X		return(0);
	X	}
	X
	X	yylval.lexval->an_val = tok;
	X	if (tok == INT) {
	X		yylval.lexval->an_ifval = inttok(curtext, nxtout);
	X	} else if (tok == CHARS) {
	X		yylval.lexval->an_val = INT;
	X		yylval.lexval->an_ifval = chartok(curtext, nxtout);
	X	}
	X	return(yylval.lexval->an_val);
	X}
	X
	X/*
	X * inttok - convert integer token.
	X *  Given the bounds of a token of type INT, return the value of that integer.
	X */
	X
	Xint
	Xinttok(s, e)
	Xchar *s, *e;
	X{
	X	char *str;	/* points to a (dynamically alloc'ed) copy of the tok */
	X	char *cp;
	X	int base;	/* the radix of this integer			*/
	X	int value;	/* the value to return				*/
	X	int digit;	/* the value of the current digit		*/
	X
	X	/*
	X	 * get a copy of the token (to remove ATTN bytes and null-terminate
	X	 *  the string), and find out what the number base is.
	X	 */
	X
	X	str = savtok(s, e);
	X	cp = str;
	X	if (*cp != '0') {
	X		base = 10;
	X	} else {
	X		if (*cp && (*++cp == 'x' || *cp == 'X')) {
	X			++cp;
	X			base = 16;
	X		} else {
	X			base = 8;
	X		}
	X	}
	X
	X	/*
	X	 * convert the string
	X	 */
	X
	X	value = 0;
	X	for (;*cp; ++cp) {
	X		if (*cp >= '0' && *cp <= '7') {
	X			digit = (int)(*cp - '0');
	X		} else if (*cp >= '8' && *cp <= '9' && base >= 10) {
	X			digit = (int)(*cp - '0');
	X		} else if (*cp >= 'a' && *cp <= 'f' && base == 16) {
	X			digit = (int)(*cp - 'a') + 10;
	X		} else if (*cp >= 'A' && *cp <= 'F' && base == 16) {
	X			digit = (int)(*cp - 'A') + 10;
	X		} else {
	X			break;
	X		}
	X		value = value * base + digit;
	X	}
	X
	X	free(str);
	X	return(value);
	X}
	X
	X/*
	X * chartok() - convert a character constant token.
	X *  given the bounds of a character constant, return the integer value
	X *   of that character constant.
	X */
	X 
	Xint
	Xchartok(s, e)
	Xchar *s, *e;
	X{
	X	char *str;	/* (dynamically alloc'ed) copy of the token	*/
	X	char *cp;
	X	int value;	/* value to return				*/
	X	int cnt;
	X
	X
	X	str = savtok(s, e);
	X
	X	cp = str + 1;
	X	if (*cp != '\\') {
	X		value = (int) *cp;
	X	} else if (*++cp == 'n') {
	X		value = (int) '\n';
	X	} else if (*cp == 't') {
	X		value = (int) '\t';
	X	} else if (*cp == 'b') {
	X		value = (int) '\b';
	X/*--read the book to find out the other chars supported--*/
	X	} else if (*cp >= '0' && *cp <= '7') {
	X		for (value = 0, cnt = 3; cnt >= 1 && *cp >= '0' && *cp <= '7';
	X		  --cnt, ++cp) {
	X			value = value * 8 + (int)(*cp - '0');
	X		}
	X	} else {
	X		value = (int) *cp;
	X	}
	X
	X	free(str);
	X	return(value);
	X}
	X
	Xstruct anode *
	Xmknode()
	X{
	X	struct anode *anp;
	X
	X	for (anp = &nodepool[0];
	X	  anp < &nodepool[NODESIZ] && anp->an_val != 0; anp++)
	X		;
	X	if (anp >= &nodepool[NODESIZ]) {
	X		warnf("#if expression too complex");
	X		return(NIL);
	X	}
	X	anp->an_val = OTHER;
	X	return(anp);
	X}
	X
	Xfreenode(n)
	Xstruct anode *n;
	X{
	X	n->an_val = 0;
	X}
SHAR_EOF
if test 10067 -ne "`wc -c < 'parse.y'`"
then
	echo shar: error transmitting "'parse.y'" '(should have been 10067 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'scpp.c'" '(14405 characters)'
if test -f 'scpp.c'
then
	echo shar: will not over-write existing file "'scpp.c'"
else
sed 's/^	X//' << \SHAR_EOF > 'scpp.c'
	X/*
	X * scpp.c - main processing for the selective C preprocessor, scpp.
	X *
	X * Copyright (c) 1985 by
	X * Tektronix, Incorporated Beaverton, Oregon 97077
	X * All rights reserved.
	X *
	X * Permission is hereby granted for personal, non-commercial
	X * reproduction and use of this program, provided that this
	X * notice and all copyright notices are included in any copy.
	X */
	X
	X#define VARS
	X# include <stdio.h>
	X# include "scpp.h"
	X# include "y.tab.h"
	X
	X/*
	X * actual[] - the array of actual parameters of the macro currently being
	X *  interpreted.
	X */
	X
	Xstruct anactual {
	X	char *aa_val;	/*
	X			 * the value of this actual (a pointer to the null-
	X			 * terminator.  see amacro.am_val in scpp.h).
	X			 */
	X	char *aa_mem;	/*
	X			 * points to the beginning of the aa_val string.
	X			 * Used to later free the value's memory.
	X			 */
	X};
	X#define ACTSIZ MAXPARMS
	Xstruct anactual actual[ACTSIZ];
	Xstruct anactual *actp;	/* the next available slot in actual[] */
	X
	X
	X
	Xmain(argc, argv)
	Xint argc;
	Xchar **argv;
	X{
	X	int tok;	/* current token's value	*/
	X	char *cp;
	X	char *ep;
	X	char **dp;	/* where within dirlist to put the next directory */
	X	struct amacro *np;
	X	char *name;	/* name of the current macro	*/
	X	char *val;	/* value of the current macro	*/
	X	char *defmagic = "defined";	/* name of the 'defined()' macro */
	X	struct amacro *magmac;	/* (temp) slot for the magic macro */
	X
	X	/*
	X	 * init all the global structures
	X	 */
	X
	X	nxtout = &pend[0];
	X	curfile = &filestk[-1];
	X	nxtin = &istk[ISTKSIZ];
	X	curif = &ifstk[-1];
	X	
	X	nxtfile = &catlist[0];
	X	dp = &dirlist[0];
	X
	X	/*
	X	 * setup the keyword symbols and the special macro, 'defined()'.
	X	 */
	X
	X	ikeywords();
	X	magmac = findmac(defmagic, defmagic + strlen(defmagic));
	X	if (magmac->am_name) {
	X		bomb("INTERNAL: 'defined()' macro slot in use");
	X	}
	X	magmac->am_name = defmagic;
	X	magmac->am_npar = 1;
	X	magmac->am_val = &magicval;
	X
	X	while (++argv, --argc > 0) {
	X		cp = *argv;
	X		if (*cp == '-' && *(cp + 1) != '\0') {
	X			switch(*++cp) {
	X			case 'C':
	X				savcom = TRUE;
	X				break;
	X			case 'I':
	X				*dp++ = cp + 1;
	X				break;
	X			case 'M':
	X				/*
	X				 * for each name in the list of whitespace-
	X				 *  separated macro names,
	X				 * Setup a slot for that macro, but leave it
	X				 *  undefined.
	X				 */
	X
	X				while (*cp) {
	X					while (*++cp == ' ' || *cp == '\t' ||
	X					    *cp == '\n')
	X						;
	X					if (*cp == '\0') {
	X						break;
	X					}
	X					for (name = cp; *cp != '\0' &&
	X					  *cp != ' ' && *cp != '\t' &&
	X					  *cp != '\n'; ++cp)
	X						;
	X
	X					np = findmac(name, cp);
	X					if (np->am_name == (char *) 0) {
	X					    np->am_name = savtok(name, cp);
	X						np->am_npar = -1;
	X					}
	X					/* am_val is left as zero */
	X				}
	X				break;
	X			case 'D':
	X				for (name = ++cp; *cp != '\0' && *cp != '=';
	X				  ++cp)
	X					;
	X				if (name == cp) {
	X					warn("missing macro name in `%s'",
	X					  name - 2);
	X					break;
	X				}
	X
	X				if (*cp == '\0') {
	X					/*
	X					 * macro name with no definition.
	X					 * Define the name with no parameters
	X					 *  and with a value of "1".
	X					 */
	X
	X					defmac(name, cp, -1, "1");
	X				} else {
	X					/* macro + definition */
	X
	X					for (*cp++ = '\0', val = cp;
	X					  *cp != '\0'; ++cp)
	X						;
	X					defmac(name, name + strlen(name),
	X					  -1, val);
	X				}
	X				break;
	X			default:
	X				bomb("unknown switch `%c'", *cp);
	X			}
	X		} else {
	X			*nxtfile++ = cp;
	X		}
	X	}
	X
	X	if (nxtfile == &catlist[0]) {
	X		*nxtfile++ = "-";
	X	}
	X	*nxtfile = (char *) 0;
	X	nxtfile = &catlist[0];
	X
	X	*dp++ = "/usr/include";
	X	*dp = (char *) 0;
	X
	X	/*
	X	 * prime the input stack and go,
	X	 * interpreting preprocessor directives along the way.
	X	 */
	X
	X	pushfile(*nxtfile++, PF_NOLOOK, PF_NOHIDE);
	X	do {
	X		tok = gintok();
	X		if (tok == POUNDLINE) {
	X			tok = doctrl(curtext);
	X		}
	X		outpend();	/* even the 0 token needs to be flushed.
	X				 * Otherwise, incomplete comments at the end
	X				 * of the file would be destroyed.
	X				 */
	X	} while (tok != 0);
	X	writepend();		/* flush trailing output	*/
	X
	X	if (curif >= &ifstk[0]) {
	X		warnf("missing endif");
	X	}
	X
	X	exit(sawerror ? 1 : 0);
	X}
	X
	Xint
	Xgintok()	/* get a token, interpreting macro's	*/
	X{
	X	int tok;		/* the current token's value	*/
	X	struct amacro *mac;	/* the current macro		*/
	X	struct amacro *defsym;	/* the macro being checked for 'defined()' */
	X	char *mactext;		/*
	X				 * the start of the invocation of a macro
	X				 * which has parameters.
	X				 */
	X	char *start;		/* the start of the current parameter	*/
	X	int nest;		/*
	X				 * current nesting level of parentheses.
	X				 * used to avoid misinterpreting commas within
	X				 * nested parens as parameter separators.
	X				 */
	X	char *defident;		/*
	X				 * The IDENT parameter for the magic macro,
	X				 * 'defined()' (dynamically alloc'ed).
	X				 * If gintok() is interpreting the magic macro,
	X				 * this variable is marked so that, during the
	X				 * parameter parsing, the first IDENT is saved
	X				 * here.
	X				 */
	X	int parmgripe;		/*
	X				 * "an error message about parameters of
	X				 * this macro has already been printed."
	X				 */
	X	int i;			/* an actual-parameter index	*/
	X	char *cp;		/* a temp pointer		*/
	X
	X	/*
	X	 * special macro values (see scpp.h: struct amacro, field am_val):
	X	 *  noval == a null macro value;
	X	 *  oneval == a macro value of '1';
	X	 *  zeroval == a macro value of '0';
	X	 */
	X
	X	static char nv[2] = {'\0', '\0'};
	X	static char *noval = &nv[1];
	X	static char ov[3] = {'\0', '1', '\0'};
	X	static char *oneval = &ov[2];
	X	static char zv[3] = {'\0', '0', '\0'};
	X	static char *zeroval = &zv[2];
	X
	X
	X	tok = OTHER;
	X	while (tok != DEFMAC && (tok = gtok()) != 0) {
	X		if (tok == QUOTE || tok == DQUOTE) {
	X			tok = gstrtok(tok);
	X		}
	X		if (tok != IDENT) {
	X			return(tok);
	X		}
	X
	X		if ((mac = findmac(curtext, nxtout))->am_name == (char *) 0 ||
	X		    mac->am_val == (char *) 0) {
	X			/* there is no macro by this name currently defined */
	X
	X			return(tok);
	X		}
	X
	X		/*
	X		 * tally this interpretation
	X		 */
	X
	X		++ninterp;
	X
	X		if (mac->am_npar < 0) {
	X			/*
	X			 * the macro has no formal parameters.
	X			 * pushback the replacement text and continue.
	X			 */
	X
	X			(void) dispose(curtext);
	X			(void) pushmac(mac->am_val);
	X			continue;
	X		}
	X
	X		/* this is a macro with formals */
	X
	X		/*
	X		 * save the starting-point of the macro's text.
	X		 * Used for later disposal.  The text is not disposed
	X		 * here in case the macro is a 'defined()' of some non--M'ed
	X		 * macro.
	X		 */
	X
	X		mactext = curtext;
	X
	X		/*
	X		 * collect the comma-separated actual parameters of the macro,
	X		 * ignoring commas within pairs of parens or within strings.
	X		 */
	X
	X		parmgripe = FALSE;
	X		actp = &actual[0];
	X		nest = 0;
	X		if (mac->am_val == &magicval) {
	X			defident = &magicval;
	X		} else {
	X			defident = (char *) 0;
	X		}
	X
	X		if ((tok = nonwhite(gtok)) != LP) {
	X			warnf("missing parenthesis in macro");
	X			parmgripe = TRUE;
	X	
	X			/* pushback the erroneous token	*/
	X			untok();
	X		} else {
	X			do {
	X				/* collect one parameter */
	X
	X				start = nxtout;
	X				while ((tok = gtok())) {
	X					if (tok == CM && nest == 0) {
	X						break;
	X					} else if (tok == RP) {
	X						if (nest > 0) {
	X							--nest;
	X						} else if (nest == 0) {
	X							break;
	X						}
	X					} else if (tok == LP) {
	X						++nest;
	X					} else if (tok == QUOTE ||
	X					  tok == DQUOTE) {
	X						tok = gstrtok(tok);
	X					} else if (tok == IDENT &&
	X					  defident == &magicval) {
	X						defident =
	X						  savtok(curtext, nxtout);
	X					}
	X				}
	X
	X				/*
	X				 * Warn about too many parameters, otherwise,
	X				 * store the parameter in the format of
	X				 * a macro value.
	X				 */
	X
	X				if ((actp - &actual[0]) >= mac->am_npar) {
	X					if (!parmgripe) {
	X					  warnf("macro parameter mismatch");
	X					  parmgripe = TRUE;
	X					}
	X				} else {
	X					cp = savtok(start - 1, curtext);
	X					*cp = '\0';
	X					actp->aa_mem = cp;
	X					while (*++cp)
	X						;
	X					actp->aa_val = cp;
	X					++actp;
	X				}
	X			} while (tok == CM);
	X			if (tok != RP) {
	X				if (!parmgripe) {
	X				  warnf("missing parenthesis in macro");
	X				  parmgripe = TRUE;
	X				}
	X			}
	X		}
	X
	X		/*
	X		 * If there are too few actual parameters, fill out the
	X		 * list with null values.
	X		 */
	X
	X		while (actp - &actual[0] < mac->am_npar) {
	X			if (!parmgripe) {
	X				warnf("parameter mismatch");
	X				parmgripe = TRUE;
	X			}
	X			actp->aa_val = noval;
	X			actp->aa_mem = (char *) 0;
	X			++actp;
	X		}
	X
	X		/*
	X		 * replace the macro invocation with the value of the macro,
	X		 *  replacing formal arguments with the corresponding actual.
	X		 */
	X
	X		if ((cp = mac->am_val) == &magicval) {
	X			/*
	X			 * This is the magic macro, "defined(x)".
	X			 * Interpret only if the parameter is a -M'ed
	X			 *  macro and we are currently parsing a
	X			 *  #if expression.
	X			 * Lookup the parameter (if any);
	X			 * If the parameter is -M'ed, pushback a '1' or '0',
	X			 * depending on whether the macro is defined.
	X			 */
	X
	X			defsym = findmac(defident, defident + strlen(defident));
	X			if (!defsym->am_name || !expparse) {
	X				/*
	X				 * Leave the invocation of defined() untouched.
	X				 */
	X
	X				curtext = mactext;
	X				tok = DEFMAC;
	X			} else {
	X				(void) dispose(mactext);
	X				if (defsym->am_val) {
	X					(void) pushmac(oneval);
	X				} else {
	X					(void) pushmac(zeroval);
	X				}
	X			}
	X			free(defident);
	X		} else {
	X			(void) dispose(mactext);
	X			while (*(cp = pushmac(cp)) == ATTN) {
	X				i = (int) (*--cp) - 1;
	X				if (i < 0 || i >= mac->am_npar) {
	X					warnf(
	X"INTERNAL: parameter number %d out of bounds", i);
	X				} else {
	X					(void) pushmac(actual[i].aa_val);
	X				}
	X			}
	X		}
	X
	X		/*
	X		 * free the actual parameters.
	X		 */
	X
	X		while (--actp >= &actual[0]) {
	X			if (actp->aa_mem) {
	X				free(actp->aa_mem);
	X			}
	X		}
	X	}
	X	return(tok);
	X}
	X
	X/*
	X * gtok() - get a token without interpreting macros or preprocessor directives.
	X *  This is the low-level lexical analyzer.  It exists only because Lex's
	X *  analyzer chokes on long comments.
	X */
	X
	Xint
	Xgtok()
	X{
	X	int tok;
	X
	X
	X	curtext = nxtout;
	X	tok = xxlex();
	X	if (tok == OPENC) {
	X		while ((tok = xxlex()) != CLOSEC) {
	X			if (tok == 0) {
	X				warnf("unterminated comment");
	X				return(0);
	X			}
	X		}
	X		tok = COMMENT;
	X	}
	X	return(tok);
	X}
	X
	X/*
	X * gstrtok - get a string token.  Given the token which starts a string
	X *  or character constant (I.E. QUOTE or DQUOTE), collect the string token
	X *  as if it had been recognised by the lexical analyzer as a single token.
	X */
	X
	Xint
	Xgstrtok(tok)
	Xint tok;		/* token which started the quoted string	*/
	X{
	X	int tok2;		/* the next token's value	*/
	X	char *qstrt;		/* start of a string in pend[]	*/
	X
	X	/*
	X	 * collect the string without interpreting
	X	 * macros.  Allow \' and \" within strings.
	X	 * Newline or EOF terminate strings.
	X	 * Save and restore curtext so that on returning,
	X	 * curtext points to the beginning of the token.
	X	 */
	X
	X	qstrt = curtext;
	X	while ((tok2 = gtok()) != tok) {
	X		if (tok2 == 0) {
	X			/* unterminated quote	*/
	X			curtext = qstrt;
	X			return(0);
	X		}
	X		if (tok2 == NL) {
	X			/* unterminated quote. pushback the newline	*/
	X
	X			untok();
	X			break;
	X		}
	X		if (tok2 == BACKS) {
	X			if (gtok() == 0) {
	X				/* unterminated quote */
	X				curtext = qstrt;
	X				return(0);
	X			}
	X		}
	X	}
	X	curtext = qstrt;
	X	return(tok == DQUOTE ? STRING : CHARS);
	X}
	X
	X/*
	X * findmac - find a macro
	X *  given the bounds of what might be a macro name (possibly containing ATTN
	X *   bytes), return a pointer to the symbol table slot
	X *  corresponding to that name.
	X */
	X
	Xstruct amacro *
	Xfindmac(name, last)
	Xchar *name;	/* points to the beginning of the name.			*/
	Xchar *last;	/* points to the char beyond the end of the name	*/
	X{
	X	/*
	X	 * hash the first 8 chars of the name (less ATTN bytes) into an index;
	X	 * Use that index as a starting point for a linear search
	X	 *  for either the matching slot or an empty slot.
	X	 */
	X
	X	int idx;
	X	char *cp;
	X	char *tp;
	X	int cnt;
	X	struct amacro *np, *start;
	X
	X
	X	for (idx = 0, cp = name, cnt = 0; cp < last && cnt < 8; ++cp) {
	X		if (*cp == ATTN) {
	X			++cp;
	X		} else {
	X			idx += (int) *cp++ & 0xff;
	X			++cnt;
	X		}
	X	}
	X	start = np = &sym[idx % SYMSIZ];
	X
	X	while (np->am_name) {
	X		/*
	X		 * compare the token at 'name' with the macro's name,
	X		 * skipping ATTN bytes and their associated codes.
	X		 */
	X
	X		for (tp = name, cp = np->am_name; tp < last; ++tp) {
	X			if (*tp == ATTN) {
	X				++tp;
	X				continue;
	X			}
	X			if (*tp != *cp++) {
	X				break;
	X			}
	X		}
	X		if (tp == last) {
	X			/* the names match */
	X			break;
	X		}
	X
	X		if (++np >= &sym[SYMSIZ]) {
	X			np = &sym[0];
	X		}
	X		if (np == start) {
	X			bombf("symbol table overflow");
	X		}
	X	}
	X	return(np);
	X}
	X
	X/*
	X * defmac - define a macro
	X */
	X
	Xdefmac(name, end, npar, val)
	Xchar *name;		/* the start of the macro's name		*/
	Xchar *end;		/* points to one char beyond the end of the name */
	Xint npar;		/* # of parameters (-1 == none)			*/
	Xchar *val;		/* the beginning of the value string		*/
	X{
	X	char *cp;
	X	struct amacro *np;
	X	struct akeyword *kp;
	X	char *malloc();
	X
	X
	X	/*
	X	 * find the slot for the macro and give it a name if this is the
	X	 * first occurrence of this name.
	X	 */
	X
	X	np = findmac(name, end);
	X	if (!np->am_name) {
	X		np->am_name = savtok(name, end);
	X	} else {
	X		/*
	X		 * Don't allow preprocessor keywords to be defined.
	X		 */
	X
	X		if ((kp = findkey(np)) != (struct akeyword *) 0) {
	X			warnf("redeclaration of keyword \"%s\"", kp->ak_name);
	X			return;
	X		}
	X
	X		/*
	X		 * if the macro is currently defined (I.E. has a value),
	X		 *  reject redefinitions of magic macros.
	X		 * compare the new and old values.
	X		 * If the value or number of parameters differs,
	X		 *  print a warning and destroy the old value.
	X		 * If they are the same, do nothing (return).
	X		 */
	X
	X		if (np->am_val) {
	X			if (np->am_val == &magicval) {
	X				warnf("cannot redefine implicit macro");
	X				return;
	X			}
	X			cp = np->am_val;
	X			while (*--cp)
	X				;
	X			if (np->am_npar == npar && strcmp(cp + 1, val) == 0) {
	X				return;
	X			}
	X
	X			warnf("redeclaration of \"%s\"", np->am_name);
	X			free(cp);
	X		}
	X	}
	X
	X	/*
	X	 * Set the new value and number of parameters.
	X	 * Put a null introduction on the value;
	X	 * Remember that am_val points to the *end* of the value.
	X	 */
	X
	X	np->am_npar = npar;
	X
	X	if (!(cp = malloc((unsigned) strlen(val) + 2))) {
	X		bombf("out of memory");
	X	}
	X	*cp++ = '\0';
	X	strcpy(cp, val);
	X	np->am_val = cp + strlen(cp);
	X}
	X
	X/*
	X * savtok - given the limits of a token string,
	X *  copy that string (less ATTN bytes) into a dynamically allocated buffer
	X *  then return the buffer.
	X */
	X
	Xchar *
	Xsavtok(s, e)
	Xchar *s;	/* first char of token			*/
	Xchar *e;	/* points beyond the last char of token	*/
	X{
	X	char *name;	/* the text of the token -- the value to return	*/
	X	char *cp;
	X	char *malloc();
	X
	X	if (!(name = malloc(e - s + 1))) {
	X		bombf("out of memory");
	X	}
	X
	X	for (cp = name; s < e; ++s) {
	X		if (*s == ATTN) {
	X			++s;
	X		} else {
	X			*cp++ = *s;
	X		}
	X	}
	X	*cp = '\0';
	X
	X	return(name);
	X}
SHAR_EOF
if test 14405 -ne "`wc -c < 'scpp.c'`"
then
	echo shar: error transmitting "'scpp.c'" '(should have been 14405 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'scpp.h'" '(10614 characters)'
if test -f 'scpp.h'
then
	echo shar: will not over-write existing file "'scpp.h'"
else
sed 's/^	X//' << \SHAR_EOF > 'scpp.h'
	X
	X/*
	X * scpp.h - common declarations for the selective C preprocessor, scpp.
	X *
	X * Copyright (c) 1985 by
	X * Tektronix, Incorporated Beaverton, Oregon 97077
	X * All rights reserved.
	X *
	X * Permission is hereby granted for personal, non-commercial
	X * reproduction and use of this program, provided that this
	X * notice and all copyright notices are included in any copy.
	X */
	X
	X# define TRUE	1
	X# define FALSE	0
	X
	X/*
	X * sawerror - "some error was processed" If true, scpp exits non-zero.
	X * Set by the error printout routines, examined when exiting.
	X */
	X#ifdef VARS
	Xint sawerror;
	X#else
	Xextern int sawerror;
	X#endif
	X
	X# define BSIZE	512	/*
	X			 * # of bytes per read -- controls how quickly
	X			 * istk[] is consumed.
	X			 */
	X
	X/*
	X * PENDSIZ is a tunable parameter -- it is the largest number of characters
	X *  which can be waiting to be output.  This number sets a limit on:
	X *  1) the longest comment;
	X *  2) the largest invocation of a macro with parameters, i.e. the number
	X *    of characters between the '(' and the ')'.
	X *  3) the longest preprocessor control line, e.g. #define....
	X * PENDSIZ also controls the input stack size, ISTK.
	X *
	X * Pend[] is the pending output buffer.
	X *
	X * Nxtout points to where within pend[] to put the next token scanned.
	X * Nxtout is advanced by questr() and quec(),
	X *  the primitives for putting stuff in pend[],
	X * and is moved backward by dispose() and outpend(),
	X *  the primitives for getting stuff out of pend[].
	X *
	X * Curtext points to the start of the text within pend[] of
	X * the current token.  Set by gtok() and gintok().
	X * For anyone who uses gtok() or gintok() to get
	X *  a token, the limits of the text of the resultant
	X *  token are curtext and nxtout. (be aware that the
	X *  text of anything in pend[] may contain imbedded
	X *  ATTN bytes.)
	X */
	X
	X# define PENDSIZ 8000
	X# define PENDHIGH 512	/* highwater mark for flushing pend[]	*/
	X#ifdef VARS
	Xchar pend[PENDSIZ];
	Xchar *nxtout;
	Xchar *curtext;
	X#else
	Xextern char pend[];
	Xextern char *nxtout;
	Xchar *curtext;
	X#endif
	Xextern char *dispose();
	X#define outpend() (nxtout < &pend[PENDHIGH] ? 0 : writepend())
	X
	X/*
	X * filestk - the stack containing the state of the current file
	X */
	X
	Xstruct afile {
	X	int	af_fd;		/* the open file's file-descriptor	*/
	X	char	*af_name;	/* the name of the file (dynamic alloc)	*/
	X	int	af_line;	/* the current line in the file		*/
	X	int	af_raw;		/*
	X				 * "scanning unprocessed data rather than
	X				 *  pushed-back data".
	X				 * Used to count input lines.
	X				 * Also used to prevent
	X				 *  interpretation of "#if" expressions whose
	X				 *  truth or falsehood does not depend on
	X				 *  interpreting macros (e.g. #if '\377' > 0). 
	X				 */
	X	int	af_hide;	/*
	X				 * "do not output anything for this file."
	X				 * This file is the result of an uninterpreted
	X				 * "# include".
	X				 */
	X};
	X
	X#define FILESIZ 11	/* max # of include files + 1 (the original file) */
	X#ifdef VARS
	Xstruct afile filestk[FILESIZ];
	Xstruct afile *curfile;	/* the current file.  Initially = &filestk[-1]	*/
	X#else
	Xextern struct afile filestk[];
	Xextern struct afile *curfile;
	X#endif
	X
	X/*
	X * ISTKSIZ is the size of the input/pushback stack.
	X *  It contains up to one block of data for each pending file plus
	X *  one pending token.
	X * The input stack grows down from istk[ISTKSIZ - 1].
	X *
	X * Nxtin points to the next char to read from istk[].
	X * Characters are popped from the stack by nxtc()
	X * and are pushed back on the stack by unc() and
	X * pushmac().
	X */
	X
	X# define ISTKSIZ (FILESIZ * BSIZE + PENDSIZ)
	X#ifdef VARS
	Xchar istk[ISTKSIZ];
	Xchar *nxtin;
	X#else
	Xextern char istk[];
	Xextern char *nxtin;
	X#endif
	Xextern char nxtc();
	Xextern char *pushmac();
	X#define unc(c) (nxtin-- < &istk[0] ? over() : (*nxtin = c))
	X
	X/*
	X * ATTN appears in the input stack to notify nxtc() of some condition,
	X *  in the output queue to notify dispose() or outpend() of some condition,
	X *  or in the value of a macro to notify gintok() of some condition.
	X * ATTN means that the next byte contains a control code.
	X * Input control codes are:
	X *  AT_EPUSH	- end of pushed-back data.  what follows has not been
	X *		 scanned before.
	X *  AT_EBLK	- end of block.  read another block from the current file.
	X * Output control codes are:
	X *  AT_OUTOFF	- disable further output.
	X *  AT_OUTON	- enable output.
	X * Macro value control codes are formal parameter numbers and are not defined.
	X *
	X * note: to avoid breaking string operations and newline recognition,
	X *  do not add an ATTN control code which has a value of '\0', '\\', or '\n'.
	X */
	X
	X#define ATTN		'\376'	/* this char must not appear in any file */
	X#define AT_EPUSH	'\001'
	X#define AT_EBLK		'\002'
	X
	X#define AT_OUTOFF	'\006'
	X#define AT_OUTON	'\007'
	X
	X/*
	X * Ninterp - number of interpretations.  Incremented each time
	X *  gintok() interprets a macro.  Since there is no
	X *  overflow detection, ninterp can be used only to
	X *  see if some interpretation took place -- not to
	X *  count the interpretations (e.g. "oldnint != ninterp"
	X *  works, but "cnt = ninterp - oldnint" may fail).
	X * Used in conjunction with af_raw to prevent
	X *  interpretation of  #if's which are always true
	X *  or false without any macro interpretation (e.g.
	X *  "#if '\377' > 0").
	X */
	X
	X#ifdef VARS
	Xint ninterp;
	X#else
	Xextern int ninterp;
	X#endif
	X
	X/*
	X * Falsecnt - number of currently false #if's;
	X * Hidecnt  - current number of uninterpreted #include's.
	X * Collectively, these variables are used to determine when
	X *  to enable or disable output.
	X */
	X
	X#ifdef VARS
	Xint falsecnt;
	Xint hidecnt;
	X#else
	Xextern int falsecnt;
	Xextern int hidecnt;
	X#endif
	X
	X/*
	X * ifstk[] contains flags describing the state of all currently active #if's.
	X * curif points to the currently active #if within the stack.
	X * The stack grows upward, starting at ifstk[-1].
	X */
	X
	X#define IF_INIF		'\001'	/* "in the 'if' clause rather than 'else'" */
	X#define IF_TRUE		'\002'	/* "this if is currently true"		   */
	X#define IF_FALSE	'\004'	/* "this if is currently false"		   */
	X	/* uninterpreted #if statements are neither true nor false.	   */
	X#define IFSIZ	100		/* maximum number of nested #if's	   */
	X#ifdef VARS
	Xchar ifstk[IFSIZ];
	Xchar *curif;
	X#else
	Xextern char ifstk[];
	Xextern char *curif;
	X#endif
	X
	X/*
	X * expparse - "currently parsing a #if expression".
	X *  Used to prevent interpretation of the macro "defined()" outside
	X *  #if expressions.
	X */
	X
	X#ifdef VARS
	Xint expparse;
	X#else
	Xextern int expparse;
	X#endif
	X
	X/*
	X * the next set of definitions are values of parameters to pushfile().
	X *  PF_NOLOOK	- the filename was given on the command line.  Don't
	X *		 search any directories for it.
	X *  PF_NODOT	- the include filename was enclosed in '<' and '>'.
	X *		 Do not search the current directory (dot) for the it.
	X *  PF_DOT	- the include filename was enclosed in double-quotes.
	X *
	X *  PF_HIDE	- the file is not to be interpreted (I.e. is an include file).
	X *		 Do not output anything while processing this file.
	X *  PF_NOHIDE	- the file is to be interpreted.
	X */
	X
	X# define PF_NOLOOK	(-1)
	X# define PF_NODOT	0
	X# define PF_DOT		1
	X
	X# define PF_HIDE	TRUE
	X# define PF_NOHIDE	FALSE
	X
	X/*
	X * savcom - "save comments and whitespace"
	X *  If false, comments and leading and trailing whitespace are removed
	X *   from interpreted macro definitions.
	X */
	X
	X#ifdef VARS
	Xint savcom;
	X#else
	Xextern int savcom;
	X#endif
	X
	X/*
	X * catlist - the list of files to process; I.E. the filenames from
	X *  the command line.  A zero pointer marks the end of the list.
	X * nxtfile - points to the next element of catlist[] to be processed.
	X */
	X
	X# define CLSIZ		100
	X#ifdef VARS
	Xchar *catlist[CLSIZ];
	Xchar **nxtfile;
	X#else
	Xextern char *catlist[];
	Xextern char **nxtfile;
	X#endif
	X
	X/*
	X * dirlist - the list of directories to search for an include file.
	X *  I.E. all the -I directories from the command line + /usr/include.
	X *  (the search of the current directory of the file is handled separately.)
	X *  A zero pointer marks the end of the list.
	X */
	X
	X#define DLSIZ		100
	X#ifdef VARS
	Xchar *dirlist[DLSIZ];
	X#else
	Xextern char *dirlist[];
	X#endif
	X
	X/*
	X * The symbol table.  All macros are stored in this table.
	X */
	X
	Xstruct amacro {
	X	char *am_name;	/*
	X			 * the name of this macro (dynamically allocated).
	X			 * An am_name value of 0 means this slot is empty.
	X			 * All macros to be interpreted are allocated slots
	X			 * before any files are scanned.  #define and #undef
	X			 * do not allocate or free symbol-table slots.
	X			 */
	X	int am_npar;	/* number of parameters.  -1 == no parameters.	*/
	X	char *am_val;	/*
	X			 * the value (replacement text) of the macro.
	X			 * (dynamically allocated.)
	X			 * An am_val value of 0 means that this macro is not
	X			 * currently defined.
	X			 *
	X			 * am_val points to the null-terminator of the
	X			 * replacement text.  The replacement text is to be
	X			 * read backwards from (am_val - 1) until a null-
	X			 * terminator is found at the other end.
	X			 * An ATTN byte followed (well, preceeded if scanning
	X			 *  forward) by a one-byte integer parameter number
	X			 *  is replaced when expanding this macro by the
	X			 *  corresponding actual parameter.
	X			 * To avoid breaking string operations on val strings,
	X			 * parameter numbers begin at 1 rather than 0
	X			 *
	X			 * A visual example may help:
	X			 *   #define goop(name) hello there name people
	X			 *  results in a sym[] slot containing:
	X			 *
	X			 *  am_name:-------------|
	X			 *			 V
	X			 *			 goop\0
	X			 *  am_npar: 1
	X			 *  am_val:----------------------------|
	X			 *				       V
	X			 *	\0hello there <1><ATTN> people\0
	X			 */
	X};
	X
	X#define SYMSIZ	1001
	X#ifdef VARS
	Xstruct amacro sym[SYMSIZ];
	X#else
	Xextern struct amacro sym[];
	X#endif
	X
	Xextern struct amacro *findmac();
	Xextern char *savtok();
	Xextern int gintok();
	Xextern int gtok();
	X
	X/*
	X * magicval - This (uninitialized) character is used to
	X *  recognize special macro's (e.g. "defined()").
	X * An am_val field of &magicval marks a macro
	X *  as special -- it cannot be undef'ed or redefined,
	X *  and macro expansion in gintok() recognizes it.
	X */
	X
	X#ifdef VARS
	Xchar magicval;
	X#else
	Xextern char magicval;
	X#endif
	X
	X#define MAXPARMS 40	/* max number of formal parameters to a macro	*/
	X
	X/*
	X * the keyword structure - one of these describes each preprocessor keyword.
	X * see ctrl.c for the keyword array, key[].
	X */
	X
	Xstruct akeyword {
	X	char *ak_name;		/* name of this keyword (used to set ak_sym) */
	X	int (*ak_proc)();	/* procedure to interpret this directive     */
	X	struct amacro *ak_sym;	/*
	X				 * pointer to the symbol table slot for this
	X				 * keyword.  Used to recognise the keyword.
	X				 * All keywords in this list are effectively
	X				 * "-M"ed when scpp is invoked.  They are
	X				 * never defined.
	X				 *   This field is initialized at runtime.
	X				 */
	X};
	Xextern struct akeyword *findkey();
	Xextern char *strcpy();
SHAR_EOF
if test 10614 -ne "`wc -c < 'scpp.h'`"
then
	echo shar: error transmitting "'scpp.h'" '(should have been 10614 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0



More information about the Mod.sources mailing list