[alt.sources] Rich $alz's cshar package, new alpha release, Part05/05

rsalz@bbn.com (Rich Salz) (02/21/89)

This is what I use to create shell archives of small-to-humongous source
distributions.  Hope you find it useful.  Hope you find bugs, and send
them to me.  This will appear in comp.sources.unix after the bug reports
die down.  It runs on all sort of Unix machines, and a raft of other OS's,
too.

For more details, see the README file in the first shar.

Please don't pass this version around, wait for the "REAL" one.
	/rich $alz

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 5 (of 5)."
# Contents:  parser.c
# Wrapped by rsalz@fig.bbn.com on Mon Feb 20 18:15:55 1989
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'parser.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'parser.c'\"
else
echo shar: Extracting \"'parser.c'\" \(26147 characters\)
sed "s/^X//" >'parser.c' <<'END_OF_FILE'
X/*
X**  Parser for /bin/sh language commonly used in writing shars.
X**
X**  Note that (void) casts abound, and that every command goes to some
X**  trouble to return a value.  That's because I decided not to implement
X**  $? "properly."
X**
X**  This code is bug and ugly, and should probably be rewritten from the
X**  ground up.  It wasn't designed; it started with Argify and SynTable
X**  as a cute ten-minute hack and it just grew.
X*/
X#include "parser.h"
X#ifdef	RCSID
Xstatic char RCS[] =
X	"$Header: parser.c,v 2.1 88/06/03 11:39:11 rsalz Locked $";
X#endif	/* RCSID */
X
X
X/*
X**  Global variables.
X*/
XFILE		*Input;			/* Current input stream		*/
Xchar		 File[TEMPSIZE];	/* Input filename		*/
Xint		 Interactive;		/* isatty(fileno(stdin))?	*/
X#ifdef	USE_LONGJMP
Xjmp_buf		 jEnv;			/* Pop out of main loop		*/
X#endif	/* USE_LONGJMP */
X#ifdef	MAX_MKDIRS
Xstatic int	 DirCount;
X#endif	/* MAX_MKDIRS */
X#ifdef	MAX_FOPENS
Xstatic int	 CreatedCount;
X#endif	/* MAX_FOPENS */
X
Xstatic VAR	 VarList[MAX_VARS];	/* Our list of variables	*/
Xstatic char	 Text[BUFSIZ];		/* Current text line		*/
Xstatic int	 LineNum = 1;		/* Current line number		*/
Xstatic int	 Running = TRUE;	/* Working, or skipping?	*/
Xstatic short	 SynTable[256] = {	/* Syntax table			*/
X    /*	\0	001	002	003	004	005	006	007	*/
X	C_TERM,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,
X    /*	\h	\t	\n	013	\f	\r	016	017	*/
X	C_WHIT,	C_WHIT,	C_TERM,	C_WHIT,	C_TERM,	C_TERM,	C_WHIT,	C_WHIT,
X    /*	020	021	022	023	024	025	026	027	*/
X	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,
X    /*	can	em	sub	esc	fs	gs	rs	us	*/
X	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,	C_WHIT,
X
X    /*	sp	!	"	#	$	%	&	'	*/
X	C_WHIT,	C_LETR,	C_QUOT,	C_TERM,	C_LETR,	C_LETR,	C_DUBL,	C_QUOT,
X    /*	(	)	*	+	,	-	.	/	*/
X	C_WORD,	C_WORD,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	0	1	2	3	4	5	6	7	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	8	9	:	;	<	=	>	?	*/
X	C_LETR,	C_LETR,	C_LETR,	C_DUBL,	C_DUBL,	C_LETR,	C_DUBL,	C_LETR,
X
X    /*	@	A	B	C	D	E	F	G	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	H	I	J	K	L	M	N	O	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	P	Q	R	S	T	U	V	W	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	X	Y	Z	[	\	]	^	_	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_META,	C_LETR,	C_LETR,	C_LETR,
X
X    /*	`	a	b	c	d	e	f	g	*/
X	C_WORD,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	h	i	j	k	l	m	n	o	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	p	q	r	s	t	u	v	w	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_LETR,
X    /*	x	y	z	{	|	}	~	del	*/
X	C_LETR,	C_LETR,	C_LETR,	C_LETR,	C_DUBL,	C_LETR,	C_LETR,	C_WHIT,
X};
X
X/**
X***		E R R O R   R O U T I N E S
X**/
X
X
X/*
X**  Print message with current line and line number.
X*/
Xstatic void
XNote(text, arg)
X    char	*text;
X    char	*arg;
X{
X    Fprintf(stderr, "\nIn line %d of %s:\n\t", LineNum, File);
X    Fprintf(stderr, text, arg);
X    Fprintf(stderr, "Current line:\n\t%s\n", Text);
X    (void)fflush(stderr);
X}
X
X
X/*
X**  Print syntax message and die.
X*/
Xvoid
XSynErr(text)
X    char	*text;
X{
X    Note("Fatal syntax error in %s statement.\n", text);
X    exit(1);
X    /* NOTREACHED */
X}
X
X/**
X***		I N P U T   R O U T I N E S
X**/
X
X
X/*
X**  Get a yes/no/quit reply.
X*/
Xstatic int
XYNQreply(prompt)
X    char		*prompt;
X{
X    REGISTER FILE	*DEVTTY;
X    register int	 value;
X    char		 buff[LINE_SIZE];
X
X    DEVTTY = fopen(ctermid((char *)NULL), "r");
X    for (value = '\0'; !value; ) {
X	Fprintf(stderr, "%s:  ", prompt);
X	(void)fflush(stderr);
X	clearerr(DEVTTY);
X	if (fgets(buff, sizeof buff, DEVTTY) == NULL)
X	    value = 'Q';
X	else
X	    switch (value) {
X	    case 'q': case 'Q': case '\n':
X		value = 'Q';
X		break;
X	    case 't': case 'T':
X	    case 'y': case 'Y':
X		value = 'T';
X		break;
X	    case 'f': case 'F':
X	    case 'n': case 'N':
X		value = 'F';
X		break;
X	    }
X    }
X    (void)fclose(DEVTTY);
X    return value;
X}
X
X
X/*
X**  Miniscule regular-expression matcher; only groks the . meta-character.
X*/
Xstatic int
XMatches(p, text)
X    REGISTER char	*p;
X    REGISTER char	*text;
X{
X    for (; *p && *text; text++, p++)
X	if (*p != *text && *p != '.')
X	    return FALSE;
X    return TRUE;
X}
X
X
X
X/*
X**  Read input, possibly handling escaped returns.  Returns a value so
X**  we can do things like "while (GetLine(TRUE))", which is a hack.  This
X**  should also be split into two separate routines, and punt the Flag
X**  argument, but so it goes.
X*/
Xint
XGetLine(Flag)
X    REGISTER int	 Flag;
X{
X    REGISTER char	*p;
X    REGISTER char	*q;
X    REGISTER int	 i;
X    char		 buf[LINE_SIZE];
X
X    if (Interactive) {
X	Fprintf(stderr, "Line %d%s>  ", LineNum, Running ? "" : "(SKIP)");
X	(void)fflush(stderr);
X    }
X    Text[0] = '\0';
X    for (q=Text; fgets(buf, sizeof buf, Input); q += strlen(strcpy(q, buf))) {
X	LineNum++;
X	i = strlen(buf);
X	p = &buf[i - 1];
X	if (*p != '\n') {
X	    /* If we hit EOF without a terminating \n, that's okay. */
X	    if (feof(Input))
X		p++;
X	    else {
X		Note("Input line too long.\n", (char *)NULL);
X		exit(1);
X		/* NOTREACHED */
X	    }
X	}
X
X	/* Make sure line isn't too long. */
X	if (q + i >= &Text[sizeof Text - 1]) {
X	    Note("Input line too big.\n", (char *)NULL);
X	    exit(1);
X	    /* NOTREACHED */
X	}
X
X	if (!Flag || p == buf || p[-1] != '\\') {
X	    (void)strcpy(q, buf);
X	    return 1;
X	}
X	p[-1] = '\0';
X	if (Interactive) {
X	    Fprintf(stderr, "PS2>  ");
X	    (void)fflush(stderr);
X	}
X    }
X    Note("RAN OUT OF INPUT.\n", (char *)NULL);
X    exit(1);
X    /* NOTREACHED */
X    /* NOTREACHED */
X}
X
X
X/*
X**  Copy a sub-string of characters into dynamic space.
X*/
Xstatic char *
XCopyRange(Start, End)
X    char	*Start;
X    char	*End;
X{
X    char	*p;
X    int		 i;
X
X    i = End - Start + 1;
X    p = strncpy(NEW(char, i + 1), Start, i);
X    p[i] = '\0';
X    return p;
X}
X
X
X/*
X**  Split a line up into shell-style "words."
X*/
Xint
XArgify(ArgV)
X    char		**ArgV;
X{
X    REGISTER char	**av;
X    REGISTER char	 *p;
X    REGISTER char	 *q;
X    
X    for (av = ArgV, p = Text; *p; p++) {
X	/* Skip whitespace, but treat "\ " as a letter. */
X	for (; ISwhit(*p); p++)
X	    if (ISmeta(*p))
X		p++;
X	if (ISterm(*p))
X	    break;
X	switch (SynTable[*p]) {
X	default:
X	    /* Gross type-casting for lint, oh well, shouldn't happen. */
X	    Note("Bad case %x in Argify.\n", (char *)SynTable[*p]);
X	    /* FALLTHROUGH */
X	case C_META:
X	    p++;
X	    /* FALLTHROUGH */
X	case C_WHIT:
X	case C_LETR:
X	    for (q = p; ISletr(*++q) || ISmeta(q[-1]); )
X		;
X	    *av++ = CopyRange(p, --q);
X	    p = q;
X	    break;
X	case C_DUBL:
X	    if (*p == p[1]) {
X		*av++ = CopyRange(p, p + 1);
X		p++;
X		break;
X	    }
X	    /* FALLTHROUGH */
X	case C_WORD:
X	    *av++ = CopyRange(p, p);
X	    break;
X	case C_QUOT:
X	    for (q = p; *++q; )
X		if (*q == *p && !ISmeta(q[-1]))
X		    break;
X	    *av++ = CopyRange(p + 1, q - 1);
X	    p = q;
X	    break;
X	}
X    }
X    *av = NULL;
X    if (av > &ArgV[MAX_WORDS - 1])
X	SynErr("TOO MANY WORDS IN LINE");
X    return av - ArgV;
X}
X
X/**
X***		V A R I A B L E   R O U T I N E S
X**/
X
X
X/*
X**  Return the value of a variable, or an empty string.
X*/
Xstatic char *
XGetVar(Name)
X    REGISTER char	*Name;
X{
X    REGISTER VAR	*Vptr;
X
X    for (Vptr = VarList; Vptr < &VarList[MAX_VARS]; Vptr++)
X	if (EQ(Vptr->Name, Name))
X	    return Vptr->Value;
X
X    /* Try the environment. */
X    return (Name = getenv(Name)) ? Name : "";
X}
X
X
X/*
X**  Insert a variable/value pair into the list of variables.
X*/
Xvoid
XSetVar(Name, Value)
X    REGISTER char	*Name;
X    REGISTER char	*Value;
X{
X    REGISTER VAR	*Vptr;
X    REGISTER VAR	*FreeVar;
X
X    /* Skip leading whitespace in variable names, sorry... */
X    while (ISwhit(*Name))
X	Name++;
X
X    /* Try to find the variable in the table. */
X    for (Vptr = VarList, FreeVar = NULL; Vptr < &VarList[MAX_VARS]; Vptr++)
X	if (Vptr->Name) {
X	    if (EQ(Vptr->Name, Name)) {
X		free(Vptr->Value);
X		Vptr->Value = COPY(Value);
X		return;
X	    }
X	}
X	else if (FreeVar == NULL)
X	    FreeVar = Vptr;
X
X    if (FreeVar == NULL) {
X	Fprintf(stderr, "Overflow, can't do '%s=%s'\n", Name, Value);
X	SynErr("ASSIGNMENT");
X    }
X    FreeVar->Name = COPY(Name);
X    FreeVar->Value = COPY(Value);
X}
X
X
X/*
X**  Expand variable references inside a word that are of the form:
X**	foo${var}bar
X**	foo$$bar
X**  Returns a pointer to a static area which is overwritten every
X**  other time it is called, so that we can do EQ(Expand(a), Expand(b)).
X*/
Xstatic char *
XExpand(p)
X    REGISTER char	*p;
X{
X    static char		 buff[2][VAR_VALUE_SIZE];
X    static int		 Flag;
X    REGISTER char	*q;
X    REGISTER char	*n;
X    REGISTER char	 Closer;
X    char		 name[VAR_NAME_SIZE];
X
X    /* This is a hack, but it makes things easier in DoTEST, q.v. */
X    if (p == NULL)
X	return p;
X
X    /* Pick the "other" buffer then loop over the string to be expanded. */
X    for (Flag = 1 - Flag, q = buff[Flag]; *p; )
X	if (*p == '$')
X	    if (*++p == '$') {
X		(void)sprintf(name, "%d", Pid());
X		q += strlen(strcpy(q, name));
X		p++;
X	    }
X	    else if (*p == '?') {
X		/* Fake it -- all commands always succeed, here. */
X		*q++ = '0';
X		*q = '\0';
X		p++;
X	    }
X	    else {
X		if (*p == '{') {
X		    Closer = '}';
X		    p++;
X		}
X		else
X		    Closer = '\0';
X		for (n = name; *p && *p != Closer; )
X		    *n++ = *p++;
X		if (*p)
X		    p++;
X		*n = '\0';
X		q += strlen(strcpy(q, GetVar(name)));
X	    }
X	else
X	    *q++ = *p++;
X    *q = '\0';
X    return buff[Flag];
X}
X
X
X/*
X**  Do a variable assignment of the form:
X**	var=value
X**	var="quoted value"
X**	var="...${var}..."
X**	etc.
X*/
Xstatic void
XDoASSIGN(Name)
X    REGISTER char	*Name;
X{
X    REGISTER char	*Value;
X    REGISTER char	*q;
X    REGISTER char	 Quote;
X
X    /* Split out into name=value strings, and deal with quoted values. */
X    Value = IDX(Name, '=');
X    *Value = '\0';
X    if (ISquot(*++Value))
X	for (Quote = *Value++, q = Value; *q && *q != Quote; q++)
X	    ;
X    else
X	for (q = Value; ISletr(*q); q++)
X	    ;
X    *q = '\0';
X
X    SetVar(Name, Expand(Value));
X}
X
X/**
X***		" O U T P U T "   C O M M A N D S
X**/
X
X#ifndef	OPEN_OVERHEAD
X
X#define CanOpenForWriting(name, mode)	fopen(name, mode)
X
X#else
X
X/*
X**  Open a file for writing, "safely."
X*/
Xstatic FILE *
XCanOpenForWriting(Name, mode)
X    char		*Name;
X    char		*mode;
X{
X    register char	 *p;
X
X    Fprintf(stderr, "Want to open \"%s\" for %s\n",
X	    Name, *mode == 'w' ? "writing" : "appending");
X#ifdef	MAX_FOPENS
X    if (++CreatedCount >= MAX_FOPENS) {
X	Fprintf(stderr, " -- would exceed limit of %d.\n", MAX_FOPENS);
X	SynErr("FILE CREATION");
X    }
X#endif	/* MAX_FOPENS */
X
X#ifdef	PATH_CHECK
X    /* Absolute pathname? */
X    if (Name[0] == '/'
X     && YNQreply("!!!Absolute pathname, confirm [ynq] (q)") != 'T')
X	SynErr("FILE CREATION");
X
X    /* Check for going up... */
X    for (bad = FALSE, p = Name; *p; p++)
X	if (p[0] == '.' && p[1] == '.') {
X	    bad++;
X	    break;
X	}
X    if (bad && YNQreply("!!!Going upwards, confirm [ynq] (q)") != 'T')
X	SynErr("FILE CREATION");
X
X    /* See if it exists. */
X    if (Fexists(Name) && YNQreply("!!!File exists, confirm [ynq] (q)") != 'T')
X	SynErr("FILE CREATION");
X#endif	/* PATH_CHECK */
X}
X
X#endif	/* OPEN_OVERHEAD */
X
X
X/*
X**  Do a cat command.  Understands the following:
X**	cat >arg1 <<arg2
X**	cat >>arg1 <<arg2
X**	cat >>arg1 /dev/null
X**  Except that arg2 is assumed to be quoted (no meta-char expansion
X**  inside the "here" document).  The IO redirection can be in any order.
X*/
X/* ARGSUSED */
Xstatic int
XDoCAT(ac, av)
X    int			 ac;
X    REGISTER char	*av[];
X{
X    REGISTER FILE	*Out;
X    REGISTER char	*Ending;
X    REGISTER char	*Source;
X    REGISTER int	 V;
X    REGISTER int	 l;
X#ifdef	MAX_LINES
X    REGISTER long	 c = 0;
X#endif	/* MAX_LINES */
X
X    /* Parse the I/O redirecions. */
X    for (V = TRUE, Source = NULL, Out = NULL, Ending = NULL; *++av; )
X	if (EQ(*av, ">") && av[1]) {
X	    av++;
X	    /* This is a hack, but maybe MS-DOS doesn't have /dev/null? */
X	    Out = Running ? CanOpenForWriting(Expand(*av), "w") : stderr;
X	}
X	else if (EQ(*av, ">>") && av[1]) {
X	    av++;
X	    /* And besides, things are actually faster this way. */
X	    Out = Running ? CanOpenForWriting(Expand(*av), "a") : stderr;
X	}
X	else if (EQ(*av, "<<") && av[1]) {
X	    for (Ending = *++av; *Ending == '\\'; Ending++)
X		;
X	    l = strlen(Ending);
X	}
X	else if (!EQ(Source = *av, "/dev/null"))
X	    SynErr("CAT (bad input filename)");
X
X    if (Out == NULL || (Ending == NULL && Source == NULL)) {
X	Note("Missing parameter in CAT command.\n", (char *)NULL);
X	V = FALSE;
X    }
X
X    /* Read the input, spit it out. */
X    if (V && Running && Out != stderr) {
X	if (Source == NULL)
X	    while (GetLine(FALSE) && !EQn(Text, Ending, l)) {
X#ifdef	MAX_LINES
X		if (++c == MAX_LINES) {
X		    Fprintf(stderr, "Line limit of %ld exceeded.\n",
X			    (long)MAX_LINES);
X		    SynErr("CAT");
X		}
X#endif	/* MAX_LINES */
X		(void)fputs(Text, Out);
X	    }
X	(void)fclose(Out);
X    }
X    else
X	while (GetLine(FALSE) && !EQn(Text, Ending, l))
X	    ;
X
X    return V;
X}
X
X
X/*
X**  Do a SED command.  Understands the following:
X**	sed sX^yyyyXX >arg1 <<arg2
X**	sed -e sX^yyyyXX >arg1 <<arg2
X**  Where the yyyy is a miniscule regular expression; see Matches(), above.
X**  The "X" can be any single character and the ^ is optional (sigh).  No
X**  shell expansion is done inside the "here" document.  The IO redirection
X**  can be in any order.
X*/
X/* ARGSUSED */
Xstatic int
XDoSED(ac, av)
X    int			 ac;
X    REGISTER char	*av[];
X{
X    REGISTER FILE	*Out;
X    REGISTER char	*Pattern;
X    REGISTER char	*Ending;
X    REGISTER char	*p;
X    REGISTER int	 V;
X    REGISTER int	 l;
X    REGISTER int	 i;
X#ifdef	MAX_LINES
X    REGISTER long	 c = 0;
X#endif	/* MAX_LINES */
X
X    /* Parse IO redirection stuff. */
X    for (V = TRUE, Out = NULL, Pattern = NULL, Ending = NULL; *++av; )
X	if (EQ(*av, ">") && av[1]) {
X	    av++;
X	    Out = Running ? CanOpenForWriting(Expand(*av), "w") : stderr;
X	}
X	else if (EQ(*av, ">>") && av[1]) {
X	    av++;
X	    Out = Running ? CanOpenForWriting(Expand(*av), "a") : stderr;
X	}
X	else if (EQ(*av, "<<") && av[1]) {
X	    for (Ending = *++av; *Ending == '\\'; Ending++)
X		;
X	    l = strlen(Ending);
X	}
X	else
X	    Pattern = EQ(*av, "-e") && av[1] ? *++av : *av;
X
X    /* All there? */
X    if (Out == NULL || Ending == NULL || Pattern == NULL) {
X	Note("Missing parameter in SED command.\n", (char *)NULL);
X	V = FALSE;
X    }
X
X    /* Parse the substitute command and its pattern. */
X    if (*Pattern != 's') {
X	Note("Bad SED command -- not a substitute.\n", (char *)NULL);
X	V = FALSE;
X    }
X    else {
X	Pattern++;
X	p = Pattern + strlen(Pattern) - 1;
X	if (*p != *Pattern || *--p != *Pattern) {
X	    Note("Bad substitute pattern in SED command.\n", (char *)NULL);
X	    V = FALSE;
X	}
X	else {
X	    /* Now check the pattern. */
X	    if (*++Pattern == '^')
X		Pattern++;
X	    for (*p = '\0', i = strlen(Pattern), p = Pattern; *p; p++)
X		if (*p == '[' || *p == '*' || *p == '$') {
X		    Note("Bad meta-character in SED pattern.\n", (char *)NULL);
X		    V = FALSE;
X		}
X	}
X    }
X
X    /* Spit out the input. */
X    if (V && Running && Out != stderr) {
X	while (GetLine(FALSE) && !EQn(Text, Ending, l)) {
X#ifdef	MAX_LINES
X	    if (++c == MAX_LINES) {
X		Fprintf(stderr, "Line limit of %ld exceeded.\n",
X			(long)MAX_LINES);
X		SynErr("SED");
X	    }
X#endif	/* MAX_LINES */
X	    (void)fputs(Matches(Pattern, Text) ? &Text[i] : Text, Out);
X	}
X	(void)fclose(Out);
X    }
X    else
X	while (GetLine(FALSE) && !EQn(Text, Ending, l))
X	    ;
X
X    return V;
X}
X
X/**
X***		" S I M P L E "   C O M M A N D S
X**/
X
X
X/*
X**  Parse a cp command of the form:
X**	cp /dev/null arg
X**  We should check if "arg" is a safe file to clobber, but...
X*/
Xstatic int
XDoCP(ac, av)
X    int		 ac;
X    char	*av[];
X{
X    FILE	*F;
X
X    if (Running) {
X	if (ac != 3 || !EQ(av[1], "/dev/null"))
X	    SynErr("CP");
X	if (F = CanOpenForWriting(Expand(av[2]), "w")) {
X	    (void)fclose(F);
X	    return TRUE;
X	}
X	Note("Can't create %s.\n", av[2]);
X    }
X    return FALSE;
X}
X
X
X/*
X**  Do a mkdir command of the form:
X**	mkdir arg
X*/
Xstatic int
XDoMKDIR(ac, av)
X    int		 ac;
X    char	*av[];
X{
X    if (Running) {
X	if (ac != 2)
X	    SynErr("MKDIR");
X#ifdef	MAX_MKDIRS
X	if (++DirCount >= MAX_MKDIRS) {
X	    Fprintf(stderr, "Directory limit of %ld exceeded for \"%s\".\n",
X		    MAX_MKDIRS, Expand(av[1]));
X	    SynErr("MKDIR");
X	}
X#endif	/* MAX_MKDIRS */
X	if (mkdir(Expand(av[1]), 0777) >= 0)
X	    return TRUE;
X	Note("Can't make directory %s.\n", av[1]);
X    }
X    return FALSE;
X}
X
X
X/*
X**  Do a cd command of the form:
X**	cd arg
X**	chdir arg
X*/
Xstatic int
XDoCD(ac, av)
X    int		 ac;
X    char	*av[];
X{
X    if (Running) {
X	if (ac != 2)
X	    SynErr("CD");
X	if (chdir(Expand(av[1])) >= 0)
X	    return TRUE;
X	Note("Can't cd to %s.\n", av[1]);
X    }
X    return FALSE;
X}
X
X
X/*
X**  Do the echo command.  Understands the "-n" hack.
X*/
X/* ARGSUSED */
Xstatic int
XDoECHO(ac, av)
X    int		 ac;
X    char	*av[];
X{
X    int		 Flag;
X
X    if (Running) {
X	if (Flag = av[1] != NULL && EQ(av[1], "-n"))
X	    av++;
X	while (*++av)
X	    Fprintf(stderr, "%s ", Expand(*av));
X	if (!Flag)
X	    Fprintf(stderr, "\n");
X	(void)fflush(stderr);
X    }
X    return TRUE;
X}
X
X
X/*
X**  Generic "handler" for commands we can't do.
X*/
Xstatic int
XDoIT(ac, av)
X    int		 ac;
X    char	*av[];
X{
X    if (Running)
X	Fprintf(stderr, "You'll have to do this yourself:\n\t%s ", *av);
X    return DoECHO(ac, av);
X}
X
X
X/*
X**  Handler for the comment command, :
X*/
Xstatic int
XDoCOMMENT(ac, av)
X    int		 ac;
X    char	*av[];
X{
X    if (Running)
X	Fprintf(stderr, "Shell comment:\n");
X    return DoECHO(ac, av);
X}
X
X
X/*
X**  Do an EXIT command.
X*/
Xstatic int
XDoEXIT(ac, av)
X    int		 ac;
X    char	*av[];
X{
X    ac = *++av ? atoi(Expand(*av)) : 0;
X    Fprintf(stderr, "Exiting, with status %d\n", ac);
X#ifdef	USE_LONGJMP
X    longjmp(jEnv, 1);
X#endif	/* USE_LONGJMP */
X    return ac;
X}
X
X
X/*
X**  Do an EXPORT command.  Often used to make sure the archive is being
X**  unpacked with the Bourne (or Korn?) shell.  We look for:
X**	export PATH blah blah blah
X*/
Xstatic int
XDoEXPORT(ac, av)
X    int		 ac;
X    char	*av[];
X{
X    if (ac < 2 || !EQ(av[1], "PATH"))
X	SynErr("EXPORT");
X    return TRUE;
X}
X
X/**
X***		F L O W - O F - C O N T R O L   C O M M A N D S
X**/
X
X
X/*
X**  Parse a "test" statement.  Returns TRUE or FALSE.  Understands the
X**  following tests:
X**	test {!} -f arg		Is arg {not} a plain file?
X**	test {!} -d arg		Is arg {not} a directory?
X**	test {!} $var -eq $var	Is the variable {not} equal to the variable?
X**	test {!} $var != $var	Is the variable {not} equal to the variable?
X**	test {!} ddd -ne `wc -c {<} arg`
X**				Is size of arg {not} equal to ddd in bytes?
X**	test -f arg -a $var -eq val
X**				Used by my shar, check for file clobbering
X**  These last two tests are starting to really push the limits of what is
X**  reasonable to hard-code, but they are common cliches in shell archive
X**  "programming."  We also understand the [ .... ] way of writing test.
X**  If we can't parse the test, we show the command and ask the luser.
X*/
Xstatic int
XDoTEST(ac, av)
X    REGISTER int	  ac;
X    REGISTER char	 *av[];
X{
X    REGISTER char	**p;
X    REGISTER char	 *Name;
X    REGISTER int	  V;
X    REGISTER int	  i;
X
X    /* Quick test. */
X    if (!Running)
X	return FALSE;
X
X    /* See if we're called as "[ ..... ]" */
X    if (EQ(*av, "[")) {
X	for (i = 1; av[i] && !EQ(av[i], "]"); i++)
X	    ;
X	if (av[i])
X	    free(av[i]);
X	av[i] = NULL;
X	ac--;
X    }
X
X    /* Ignore the "test" argument. */
X    av++;
X    ac--;
X
X    /* Inverted test? */
X    if (EQ(*av, "!")) {
X	V = FALSE;
X	av++;
X	ac--;
X    }
X    else
X	V = TRUE;
X
X    /* Testing for file-ness? */
X    if (ac == 2 && EQ(av[0], "-f") && (Name = Expand(av[1])))
X	return GetStat(Name) && Ftype(Name) == F_FILE ? V : !V;
X
X    /* Testing for directory-ness? */
X    if (ac == 2 && EQ(av[0], "-d") && (Name = Expand(av[1])))
X	return GetStat(Name) && Ftype(Name) == F_DIR ? V : !V;
X
X    /* Testing a variable's value? */
X    if (ac == 3 && (EQ(av[1], "-eq") || EQ(av[1], "=")))
X	return EQ(Expand(av[0]), Expand(av[2])) ? V : !V;
X    if (ac == 3 && (EQ(av[1], "-ne") || EQ(av[1], "!=")))
X	return !EQ(Expand(av[0]), Expand(av[2])) ? V : !V;
X
X    /* Testing a file's size? */
X    if (ac == (av[5] && EQ(av[5], "<") ? 8 : 7)
X     && CTYPE(av[0][0]) && isdigit(av[0][0])
X     && (EQ(av[1], "-ne") || EQ(av[1], "-eq"))
X     && EQ(av[2], "`") && EQ(av[3], "wc")
X     && EQ(av[4], "-c") && EQ(av[ac - 1], "`")) {
X	if (GetStat(av[ac - 2])) {
X	    if (EQ(av[1], "-ne"))
X		return Fsize(av[ac - 2]) != atol(av[0]) ? V : !V;
X	    return Fsize(av[ac - 2]) == atol(av[0]) ? V : !V;
X	}
X	Note("Can't get status of %s.\n", av[ac - 2]);
X    }
X
X    /* Testing for existing, but can clobber? */
X    if (ac == 6 && EQ(av[0], "-f") && EQ(av[2], "-a")
X     && (EQ(av[4], "!=") || EQ(av[4], "-ne")))
X	return GetStat(Name = Expand(av[1])) && Ftype(Name) == F_FILE
X	       && !EQ(Expand(av[3]), Expand(av[5])) ? V : !V);
X
X    /* I give up -- print it out, and let's ask Mikey, he can do it... */
X    Fprintf(stderr, "Can't parse this test:\n\t");
X    for (i = FALSE, p = av; *p; p++) {
X	Fprintf(stderr, "%s ", *p);
X	if (p[0][0] == '$')
X	    i = TRUE;
X    }
X    if (i) {
X	Fprintf(stderr, "\n(Here it is with shell variables expanded...)\n\t");
X	for (p = av; *p; p++)
X	    Fprintf(stderr, "%s ", Expand(*p));
X    }
X    Fprintf(stderr, "\n");
X
X    switch (YNQreply("Is value true/false/quit [tfq] (q)")) {
X    case 'T':
X	return TRUE;
X    case 'F':
X	return FALSE;
X    }
X    SynErr("TEST");
X    /* NOTREACHED */
X}
X
X
X/*
X**  Do an IF statement.
X*/
Xstatic int
XDoIF(ac, av)
X    REGISTER int	 ac;
X    REGISTER char	*av[];
X{
X    REGISTER char	**p;
X    REGISTER int	  Flag;
X    char		 *vec[MAX_WORDS];
X    char		**Pushed;
X
X    /* Skip first argument. */
X    if (!EQ(*++av, "[") && !EQ(*av, "test") && !EQ(*av, "/bin/test"))
X	SynErr("IF");
X    ac--;
X
X    /* Look for " ; then " on this line, or "then" on next line. */
X    for (Pushed = NULL, p = av; *p; p++)
X	if (Flag = EQ(*p, ";")) {
X	    if (p[1] == NULL || !EQ(p[1], "then"))
X		SynErr("IF");
X	    *p = NULL;
X	    ac -= 2;
X	    break;
X	}
X    if (!Flag) {
X	(void)GetLine(TRUE);
X	if (Argify(vec) > 1)
X	    Pushed = &vec[1];
X	if (!EQ(vec[0], "then"))
X	    SynErr("IF (missing THEN)");
X    }
X
X    if (DoTEST(ac, av)) {
X	if (Pushed)
X	    (void)Exec(Pushed);
X	while (GetLine(TRUE)) {
X	    if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi"))
X		break;
X	    if (EQ(vec[0], "else")) {
X		DoUntil("fi", FALSE);
X		break;
X	    }
X	    (void)Exec(vec);
X	}
X    }
X    else
X	while (GetLine(TRUE)) {
X	    if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi"))
X		break;
X	    if (EQ(vec[0], "else")) {
X		if (ac > 1)
X		    (void)Exec(&vec[1]);
X		DoUntil("fi", Running);
X		break;
X	    }
X	}
X    return TRUE;
X}
X
X
X/*
X**  Do a FOR statement.
X*/
Xstatic int
XDoFOR(ac, av)
X    REGISTER int	  ac;
X    REGISTER char	 *av[];
X{
X    REGISTER char	 *Var;
X    REGISTER char	**Values;
X    REGISTER int	  Found;
X    long		  Here;
X    char		 *vec[MAX_WORDS];
X
X    /* Check usage, get variable name and eat noise words. */
X    if (ac < 4 || !EQ(av[2], "in"))
X	SynErr("FOR");
X    Var = av[1];
X    ac -= 3;
X    av += 3;
X
X    /* Look for "; do" on this line, or just "do" on next line. */
X    for (Values = av; *++av; )
X	if (Found = EQ(*av, ";")) {
X	    if (av[1] == NULL || !EQ(av[1], "do"))
X		SynErr("FOR");
X	    *av = NULL;
X	    break;
X	}
X    if (!Found) {
X	(void)GetLine(TRUE);
X	if (Argify(vec) != 1 || !EQ(vec[0], "do"))
X	    SynErr("FOR (missing DO)");
X    }
X
X    for (Here = ftell(Input); *Values; ) {
X	SetVar(Var, *Values);
X	DoUntil("done", Running);
X	    ;
X	/* If we're not Running, only go through the loop once. */
X	if (!Running)
X	    break;
X	if (*++Values && (fseek(Input, Here, 0) < 0 || ftell(Input) != Here))
X	    SynErr("FOR (can't seek back)");
X    }
X
X    return TRUE;
X}
X
X
X/*
X**  Do a CASE statement of the form:
X**	case $var in
X**	    text1)
X**		...
X**		;;
X**	esac
X**  Where text1 is a simple word or an asterisk.
X*/
Xstatic int
XDoCASE(ac, av)
X    REGISTER int	 ac;
X    REGISTER char	*av[];
X{
X    REGISTER int	 FoundIt;
X    char		*vec[MAX_WORDS];
X    char		 Value[VAR_VALUE_SIZE];
X
X    if (ac != 3 || !EQ(av[2], "in"))
X	SynErr("CASE");
X    (void)strcpy(Value, Expand(av[1]));
X
X    for (FoundIt = FALSE; GetLine(TRUE); ) {
X	ac = Argify(vec);
X	if (EQ(vec[0], "esac"))
X	    break;
X	/* This is for vi: (-; sigh. */
X	if (ac != 2 || !EQ(vec[1], ")"))
X	    SynErr("CASE");
X	if (!FoundIt && (EQ(vec[0], Value) || EQ(vec[0], "*"))) {
X	    FoundIt = TRUE;
X	    if (Running && ac > 2)
X		(void)Exec(&vec[2]);
X	    DoUntil(";;", Running);
X	}
X	else
X	    DoUntil(";;", FALSE);
X    }
X    return TRUE;
X}
X
X
X
X/*
X**  Dispatch table of known commands.
X*/
Xstatic COMTAB	 Dispatch[] = {
X    {	"cat",		DoCAT		},
X    {	"case",		DoCASE		},
X    {	"cd",		DoCD		},
X    {	"chdir",	DoCD		},
X    {	"chmod",	DoIT		},
X    {	"cp",		DoCP		},
X    {	"echo",		DoECHO		},
X    {	"exit",		DoEXIT		},
X    {	"export",	DoEXPORT	},
X    {	"for",		DoFOR		},
X    {	"if",		DoIF		},
X    {	"mkdir",	DoMKDIR		},
X    {	"rm",		DoIT		},
X    {	"sed",		DoSED		},
X    {	"test",		DoTEST		},
X    {	"[",		DoTEST		},
X    {	":",		DoCOMMENT	},
X    {	"",		NULL		}
X};
X
X
X/*
X**  Dispatch on a parsed line.
X*/
Xint
XExec(av)
X    REGISTER char	*av[];
X{
X    REGISTER int	 i;
X    REGISTER COMTAB	*p;
X
X    /* We have to re-calculate this because our callers can't always
X       pass the count down to us easily. */
X    for (i = 0; av[i]; i++)
X	;
X    if (i) {
X	/* Is this a command we know? */
X	for (p = Dispatch; p->Func; p++)
X	    if (EQ(av[0], p->Name)) {
X		i = (*p->Func)(i, av);
X		if (p->Func == DoEXIT)
X		    /* Sigh; this is a hack. */
X		    return OOB_FALSE;
X		break;
X	    }
X
X	/* If not a command, try it as a variable assignment. */
X	if (p->Func == NULL)
X	    /* Yes, we look for "=" in the first word, but pass down
X	       the whole line. */
X	    if (IDX(av[0], '='))
X		DoASSIGN(Text);
X	    else
X		Note("Command %s unknown.\n", av[0]);
X
X	/* Free the line. */
X	for (i = 0; av[i]; i++)
X	    free(av[i]);
X    }
X    return TRUE;
X}
X
X
X/*
X**  Do until we reach a specific terminator.
X*/
Xstatic
XDoUntil(Terminator, NewVal)
X    char	*Terminator;
X    int		 NewVal;
X{
X    char	*av[MAX_WORDS];
X    int		 OldVal;
X
X    for (OldVal = Running, Running = NewVal; GetLine(TRUE); )
X	if (Argify(av)) {
X	    if (EQ(av[0], Terminator))
X		break;
X	    (void)Exec(av);
X	}
X
X    Running = OldVal;
X}
END_OF_FILE
if test 26147 -ne `wc -c <'parser.c'`; then
    echo shar: \"'parser.c'\" unpacked with wrong size!
fi
# end of 'parser.c'
fi
echo shar: End of archive 5 \(of 5\).
cp /dev/null ark5isdone
MISSING=""
for I in 1 2 3 4 5 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 5 archives.
    echo "Now go find those bugs, and report them to rsalz@uunet.uu.net"
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.