rsalz@uunet.uu.net (Rich Salz) (05/28/88)
Submitted-by: Rich Salz <rsalz@uunet.uu.net> Posting-number: Volume 15, Issue 20 Archive-name: cshar/part03 #! /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 3 (of 3)." # Contents: parser.c # Wrapped by rsalz@fig.bbn.com on Fri May 27 14:15:09 1988 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'\" \(24206 characters\) sed "s/^X//" >'parser.c' <<'END_OF_FILE' X/* X** An interpreter that can unpack many /bin/sh shell archives. X** This program should really be split up into a couple of smaller X** files; it started with Argify and SynTable as a cute ten-minute X** hack and it just grew. X** X** Also, note that (void) casts abound, and that every command goes X** to some trouble to return a value. That's because I decided X** not to implement $? "properly." X*/ X#include "shar.h" X#ifdef RCSID static char RCS[] = X "$Header: parser.c,v 2.0 88/05/27 13:27:39 rsalz Exp $"; X#endif /* RCSID */ X X X/* X** Manifest constants, handy shorthands. X*/ X X/* Character classes used in the syntax table. */ X#define C_LETR 1 /* A letter within a word */ X#define C_WHIT 2 /* Whitespace to separate words */ X#define C_WORD 3 /* A single-character word */ X#define C_DUBL 4 /* Something like <<, e.g. */ X#define C_QUOT 5 /* Quotes to group a word */ X#define C_META 6 /* Heavy magic character */ X#define C_TERM 7 /* Line terminator */ X X/* Macros used to query character class. */ X#define ISletr(c) (SynTable[(c)] == C_LETR) X#define ISwhit(c) (SynTable[(c)] == C_WHIT) X#define ISword(c) (SynTable[(c)] == C_WORD) X#define ISdubl(c) (SynTable[(c)] == C_DUBL) X#define ISquot(c) (SynTable[(c)] == C_QUOT) X#define ISmeta(c) (SynTable[(c)] == C_META) X#define ISterm(c) (SynTable[(c)] == C_TERM) X X X/* X** Data types X*/ X X/* Command dispatch table. */ typedef struct { X char Name[10]; /* Text of command name */ X int (*Func)(); /* Function that implements it */ X} COMTAB; X X/* A shell variable. We only have a few of these. */ typedef struct { X char *Name; X char *Value; X} VAR; X X X/* X** Global variables. X*/ X XFILE *Input; /* Current input stream */ char *File; /* Input filename */ int Interactive; /* isatty(fileno(stdin))? */ X#ifdef MSDOS jmp_buf jEnv; /* Pop out of main loop */ X#endif MSDOS X extern COMTAB Dispatch[]; /* Defined below... */ static VAR VarList[MAX_VARS]; /* Our list of variables */ static char Text[BUFSIZ]; /* Current text line */ static int LineNum = 1; /* Current line number */ static int Running = TRUE; /* Working, or skipping? */ static 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*/ static void Note(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*/ void SynErr(text) X char *text; X{ X Note("Fatal syntax error in %s statement.\n", text); X exit(1); X} X X/** X*** I N P U T R O U T I N E S X**/ X X X/* X** Miniscule regular-expression matcher; only groks the . meta-character. X*/ static int Matches(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*/ int GetLine(Flag) X REGISTER int Flag; X{ X REGISTER char *p; X REGISTER char *q; 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 p = &buf[strlen(buf) - 1]; X if (*p != '\n') { X Note("Input line too long.\n", (char *)NULL); X exit(1); 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} X X X/* X** Copy a sub-string of characters into dynamic space. X*/ static char * CopyRange(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*/ int Argify(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 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*/ static char * GetVar(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*/ void SetVar(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*/ static 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 Closer = (*p == '{') ? *p++ : '\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*/ static void DoASSIGN(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; ) 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 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 -- i.e., no expansion of meta-chars X** inside the "here" document is done. The IO redirection can be in any order. X*/ X/* ARGSUSED */ static int DoCAT(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 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 ? fopen(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 ? fopen(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 (void)fputs(Text, Out); 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 */ static int DoSED(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 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 ? fopen(Expand(*av), "w") : stderr; X } X else if (EQ(*av, ">>") && av[1]) { X av++; X Out = Running ? fopen(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 (void)fputs(Matches(Pattern, Text) ? &Text[i] : Text, Out); 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*/ static int DoCP(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 = fopen(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*/ static int DoMKDIR(ac, av) X int ac; X char *av[]; X{ X if (Running) { X if (ac != 2) X SynErr("MKDIR"); 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*/ static int DoCD(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 */ static int DoECHO(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*/ static int DoIT(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** Do an EXIT command. X*/ static int DoEXIT(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 MSDOS X longjmp(jEnv, 1); X#endif /* MSDOS */ 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*/ static int DoEXPORT(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*/ static int DoTEST(ac, av) X REGISTER int ac; X REGISTER char *av[]; X{ X REGISTER char **p; X REGISTER char *Name; X REGISTER FILE *DEVTTY; X REGISTER int V; X REGISTER int i; X char buff[LINE_SIZE]; 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 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) && 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 DEVTTY = fopen(THE_TTY, "r"); X do { X Fprintf(stderr, "Is value true/false/quit [tfq] (q): "); X (void)fflush(stderr); X clearerr(DEVTTY); X if (fgets(buff, sizeof buff, DEVTTY) == NULL X || buff[0] == 'q' || buff[0] == 'Q' || buff[0] == '\n') X SynErr("TEST"); X if (buff[0] == 't' || buff[0] == 'T') { X (void)fclose(DEVTTY); X return(TRUE); X } X } while (buff[0] != 'f' && buff[0] != 'F'); X (void)fclose(DEVTTY); X return(FALSE); X} X X X/* X** Do an IF statement. X*/ static int DoIF(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")) 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*/ static int DoFOR(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*/ static int DoCASE(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*/ static 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 { ":", DoIT }, X { "", NULL } X}; X X X/* X** Dispatch on a parsed line. X*/ int 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(-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*/ static DoUntil(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 24206 -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 3 \(of 3\). cp /dev/null ark3isdone MISSING="" for I in 1 2 3 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 3 archives. echo "See the README" 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.