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.