home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume15 / cshar / part03 / parser.c
Encoding:
C/C++ Source or Header  |  1988-05-26  |  23.6 KB  |  1,078 lines

  1. /*
  2. **  An interpreter that can unpack many /bin/sh shell archives.
  3. **  This program should really be split up into a couple of smaller
  4. **  files; it started with Argify and SynTable as a cute ten-minute
  5. **  hack and it just grew.
  6. **
  7. **  Also, note that (void) casts abound, and that every command goes
  8. **  to some trouble to return a value.  That's because I decided
  9. **  not to implement $? "properly."
  10. */
  11. #include "shar.h"
  12. #ifdef    RCSID
  13. static char RCS[] =
  14.     "$Header: parser.c,v 2.0 88/05/27 13:27:39 rsalz Exp $";
  15. #endif    /* RCSID */
  16.  
  17.  
  18. /*
  19. **  Manifest constants, handy shorthands.
  20. */
  21.  
  22. /* Character classes used in the syntax table. */
  23. #define C_LETR        1        /* A letter within a word    */
  24. #define C_WHIT        2        /* Whitespace to separate words    */
  25. #define C_WORD        3        /* A single-character word    */
  26. #define C_DUBL        4        /* Something like <<, e.g.    */
  27. #define C_QUOT        5        /* Quotes to group a word    */
  28. #define C_META        6        /* Heavy magic character    */
  29. #define C_TERM        7        /* Line terminator        */
  30.  
  31. /* Macros used to query character class. */
  32. #define ISletr(c)    (SynTable[(c)] == C_LETR)
  33. #define ISwhit(c)    (SynTable[(c)] == C_WHIT)
  34. #define ISword(c)    (SynTable[(c)] == C_WORD)
  35. #define ISdubl(c)    (SynTable[(c)] == C_DUBL)
  36. #define ISquot(c)    (SynTable[(c)] == C_QUOT)
  37. #define ISmeta(c)    (SynTable[(c)] == C_META)
  38. #define ISterm(c)    (SynTable[(c)] == C_TERM)
  39.  
  40.  
  41. /*
  42. **  Data types
  43. */
  44.  
  45. /* Command dispatch table. */
  46. typedef struct {
  47.     char      Name[10];        /* Text of command name        */
  48.     int        (*Func)();        /* Function that implements it    */
  49. } COMTAB;
  50.  
  51. /* A shell variable.  We only have a few of these. */
  52. typedef struct {
  53.     char     *Name;
  54.     char     *Value;
  55. } VAR;
  56.  
  57.  
  58. /*
  59. **  Global variables.
  60. */
  61.  
  62. FILE        *Input;            /* Current input stream        */
  63. char        *File;            /* Input filename        */
  64. int         Interactive;        /* isatty(fileno(stdin))?    */
  65. #ifdef    MSDOS
  66. jmp_buf         jEnv;            /* Pop out of main loop        */
  67. #endif    MSDOS
  68.  
  69. extern COMTAB     Dispatch[];        /* Defined below...        */
  70. static VAR     VarList[MAX_VARS];    /* Our list of variables    */
  71. static char     Text[BUFSIZ];        /* Current text line        */
  72. static int     LineNum = 1;        /* Current line number        */
  73. static int     Running = TRUE;    /* Working, or skipping?    */
  74. static short     SynTable[256] = {    /* Syntax table            */
  75.     /*    \0    001    002    003    004    005    006    007    */
  76.     C_TERM,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,
  77.     /*    \h    \t    \n    013    \f    \r    016    017    */
  78.     C_WHIT,    C_WHIT,    C_TERM,    C_WHIT,    C_TERM,    C_TERM,    C_WHIT,    C_WHIT,
  79.     /*    020    021    022    023    024    025    026    027    */
  80.     C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,
  81.     /*    can    em    sub    esc    fs    gs    rs    us    */
  82.     C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,    C_WHIT,
  83.  
  84.     /*    sp    !    "    #    $    %    &    '    */
  85.     C_WHIT,    C_LETR,    C_QUOT,    C_TERM,    C_LETR,    C_LETR,    C_DUBL,    C_QUOT,
  86.     /*    (    )    *    +    ,    -    .    /    */
  87.     C_WORD,    C_WORD,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,
  88.     /*    0    1    2    3    4    5    6    7    */
  89.     C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,
  90.     /*    8    9    :    ;    <    =    >    ?    */
  91.     C_LETR,    C_LETR,    C_LETR,    C_DUBL,    C_DUBL,    C_LETR,    C_DUBL,    C_LETR,
  92.  
  93.     /*    @    A    B    C    D    E    F    G    */
  94.     C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,
  95.     /*    H    I    J    K    L    M    N    O    */
  96.     C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,
  97.     /*    P    Q    R    S    T    U    V    W    */
  98.     C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,
  99.     /*    X    Y    Z    [    \    ]    ^    _    */
  100.     C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_META,    C_LETR,    C_LETR,    C_LETR,
  101.  
  102.     /*    `    a    b    c    d    e    f    g    */
  103.     C_WORD,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,
  104.     /*    h    i    j    k    l    m    n    o    */
  105.     C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,
  106.     /*    p    q    r    s    t    u    v    w    */
  107.     C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_LETR,
  108.     /*    x    y    z    {    |    }    ~    del    */
  109.     C_LETR,    C_LETR,    C_LETR,    C_LETR,    C_DUBL,    C_LETR,    C_LETR,    C_WHIT,
  110. };
  111.  
  112. /**
  113. ***        E R R O R   R O U T I N E S
  114. **/
  115.  
  116.  
  117. /*
  118. **  Print message with current line and line number.
  119. */
  120. static void
  121. Note(text, arg)
  122.     char    *text;
  123.     char    *arg;
  124. {
  125.     Fprintf(stderr, "\nIn line %d of %s:\n\t", LineNum, File);
  126.     Fprintf(stderr, text, arg);
  127.     Fprintf(stderr, "Current line:\n\t%s\n", Text);
  128.     (void)fflush(stderr);
  129. }
  130.  
  131.  
  132. /*
  133. **  Print syntax message and die.
  134. */
  135. void
  136. SynErr(text)
  137.     char    *text;
  138. {
  139.     Note("Fatal syntax error in %s statement.\n", text);
  140.     exit(1);
  141. }
  142.  
  143. /**
  144. ***        I N P U T   R O U T I N E S
  145. **/
  146.  
  147.  
  148. /*
  149. **  Miniscule regular-expression matcher; only groks the . meta-character.
  150. */
  151. static int
  152. Matches(p, text)
  153.     REGISTER char    *p;
  154.     REGISTER char    *text;
  155. {
  156.     for (; *p && *text; text++, p++)
  157.     if (*p != *text && *p != '.')
  158.         return(FALSE);
  159.     return(TRUE);
  160. }
  161.  
  162.  
  163.  
  164. /*
  165. **  Read input, possibly handling escaped returns.  Returns a value so
  166. **  we can do things like "while (GetLine(TRUE))", which is a hack.  This
  167. **  should also be split into two separate routines, and punt the Flag
  168. **  argument, but so it goes.
  169. */
  170. int
  171. GetLine(Flag)
  172.     REGISTER int     Flag;
  173. {
  174.     REGISTER char    *p;
  175.     REGISTER char    *q;
  176.     char         buf[LINE_SIZE];
  177.  
  178.     if (Interactive) {
  179.     Fprintf(stderr, "Line %d%s>  ", LineNum, Running ? "" : "(SKIP)");
  180.     (void)fflush(stderr);
  181.     }
  182.     Text[0] = '\0';
  183.     for (q = Text; fgets(buf, sizeof buf, Input); q += strlen(strcpy(q, buf))) {
  184.     LineNum++;
  185.     p = &buf[strlen(buf) - 1];
  186.     if (*p != '\n') {
  187.         Note("Input line too long.\n", (char *)NULL);
  188.         exit(1);
  189.     }
  190.     if (!Flag || p == buf || p[-1] != '\\') {
  191.         (void)strcpy(q, buf);
  192.         return(1);
  193.     }
  194.     p[-1] = '\0';
  195.     if (Interactive) {
  196.         Fprintf(stderr, "PS2>  ");
  197.         (void)fflush(stderr);
  198.     }
  199.     }
  200.     Note("RAN OUT OF INPUT.\n", (char *)NULL);
  201.     exit(1);
  202.     /* NOTREACHED */
  203. }
  204.  
  205.  
  206. /*
  207. **  Copy a sub-string of characters into dynamic space.
  208. */
  209. static char *
  210. CopyRange(Start, End)
  211.     char    *Start;
  212.     char    *End;
  213. {
  214.     char    *p;
  215.     int         i;
  216.  
  217.     i = End - Start + 1;
  218.     p = strncpy(NEW(char, i + 1), Start, i);
  219.     p[i] = '\0';
  220.     return(p);
  221. }
  222.  
  223.  
  224. /*
  225. **  Split a line up into shell-style "words."
  226. */
  227. int
  228. Argify(ArgV)
  229.     char        **ArgV;
  230. {
  231.     REGISTER char    **av;
  232.     REGISTER char     *p;
  233.     REGISTER char     *q;
  234.     
  235.     for (av = ArgV, p = Text; *p; p++) {
  236.     /* Skip whitespace, but treat "\ " as a letter. */
  237.     for (; ISwhit(*p); p++)
  238.         if (ISmeta(*p))
  239.         p++;
  240.     if (ISterm(*p))
  241.         break;
  242.     switch (SynTable[*p]) {
  243.     default:
  244.         Note("Bad case %x in Argify.\n", (char *)SynTable[*p]);
  245.         /* FALLTHROUGH */
  246.     case C_META:
  247.         p++;
  248.         /* FALLTHROUGH */
  249.     case C_WHIT:
  250.     case C_LETR:
  251.         for (q = p; ISletr(*++q) || ISmeta(q[-1]); )
  252.         ;
  253.         *av++ = CopyRange(p, --q);
  254.         p = q;
  255.         break;
  256.     case C_DUBL:
  257.         if (*p == p[1]) {
  258.         *av++ = CopyRange(p, p + 1);
  259.         p++;
  260.         break;
  261.         }
  262.         /* FALLTHROUGH */
  263.     case C_WORD:
  264.         *av++ = CopyRange(p, p);
  265.         break;
  266.     case C_QUOT:
  267.         for (q = p; *++q; )
  268.         if (*q == *p && !ISmeta(q[-1]))
  269.             break;
  270.         *av++ = CopyRange(p + 1, q - 1);
  271.         p = q;
  272.         break;
  273.     }
  274.     }
  275.     *av = NULL;
  276.     if (av > &ArgV[MAX_WORDS - 1])
  277.     SynErr("TOO MANY WORDS IN LINE");
  278.     return(av - ArgV);
  279. }
  280.  
  281. /**
  282. ***        V A R I A B L E   R O U T I N E S
  283. **/
  284.  
  285.  
  286. /*
  287. **  Return the value of a variable, or an empty string.
  288. */
  289. static char *
  290. GetVar(Name)
  291.     REGISTER char    *Name;
  292. {
  293.     REGISTER VAR    *Vptr;
  294.  
  295.     for (Vptr = VarList; Vptr < &VarList[MAX_VARS]; Vptr++)
  296.     if (EQ(Vptr->Name, Name))
  297.         return(Vptr->Value);
  298.  
  299.     /* Try the environment. */
  300.     return((Name = getenv(Name)) ? Name : "");
  301. }
  302.  
  303.  
  304. /*
  305. **  Insert a variable/value pair into the list of variables.
  306. */
  307. void
  308. SetVar(Name, Value)
  309.     REGISTER char    *Name;
  310.     REGISTER char    *Value;
  311. {
  312.     REGISTER VAR    *Vptr;
  313.     REGISTER VAR    *FreeVar;
  314.  
  315.     /* Skip leading whitespace in variable names, sorry... */
  316.     while (ISwhit(*Name))
  317.     Name++;
  318.  
  319.     /* Try to find the variable in the table. */
  320.     for (Vptr = VarList, FreeVar = NULL; Vptr < &VarList[MAX_VARS]; Vptr++)
  321.     if (Vptr->Name) {
  322.         if (EQ(Vptr->Name, Name)) {
  323.         free(Vptr->Value);
  324.         Vptr->Value = COPY(Value);
  325.         return;
  326.         }
  327.     }
  328.     else if (FreeVar == NULL)
  329.         FreeVar = Vptr;
  330.  
  331.     if (FreeVar == NULL) {
  332.     Fprintf(stderr, "Overflow, can't do '%s=%s'\n", Name, Value);
  333.     SynErr("ASSIGNMENT");
  334.     }
  335.     FreeVar->Name = COPY(Name);
  336.     FreeVar->Value = COPY(Value);
  337. }
  338.  
  339.  
  340. /*
  341. **  Expand variable references inside a word that are of the form:
  342. **    foo${var}bar
  343. **    foo$$bar
  344. **  Returns a pointer to a static area which is overwritten every
  345. **  other time it is called, so that we can do EQ(Expand(a), Expand(b)).
  346. */
  347. static char *
  348. Expand(p)
  349.     REGISTER char    *p;
  350. {
  351.     static char         buff[2][VAR_VALUE_SIZE];
  352.     static int         Flag;
  353.     REGISTER char    *q;
  354.     REGISTER char    *n;
  355.     REGISTER char     Closer;
  356.     char         name[VAR_NAME_SIZE];
  357.  
  358.     /* This is a hack, but it makes things easier in DoTEST, q.v. */
  359.     if (p == NULL)
  360.     return(p);
  361.  
  362.     /* Pick the "other" buffer then loop over the string to be expanded. */
  363.     for (Flag = 1 - Flag, q = buff[Flag]; *p; )
  364.     if (*p == '$')
  365.         if (*++p == '$') {
  366.         (void)sprintf(name, "%d", Pid());
  367.         q += strlen(strcpy(q, name));
  368.         p++;
  369.         }
  370.         else if (*p == '?') {
  371.         /* Fake it -- all commands always succeed, here. */
  372.         *q++ = '0';
  373.         *q = '\0';
  374.         p++;
  375.         }
  376.         else {
  377.         Closer =  (*p == '{') ? *p++ : '\0';
  378.         for (n = name; *p && *p != Closer; )
  379.             *n++ = *p++;
  380.         if (*p)
  381.             p++;
  382.         *n = '\0';
  383.         q += strlen(strcpy(q, GetVar(name)));
  384.         }
  385.     else
  386.         *q++ = *p++;
  387.     *q = '\0';
  388.     return(buff[Flag]);
  389. }
  390.  
  391.  
  392. /*
  393. **  Do a variable assignment of the form:
  394. **    var=value
  395. **    var="quoted value"
  396. **    var="...${var}..."
  397. **    etc.
  398. */
  399. static void
  400. DoASSIGN(Name)
  401.     REGISTER char    *Name;
  402. {
  403.     REGISTER char    *Value;
  404.     REGISTER char    *q;
  405.     REGISTER char     Quote;
  406.  
  407.     /* Split out into name:value strings, and deal with quoted values. */
  408.     Value = IDX(Name, '=');
  409.     *Value = '\0';
  410.     if (ISquot(*++Value))
  411.     for (Quote = *Value++, q = Value; *++q && *q != Quote; )
  412.         ;
  413.     else
  414.     for (q = Value; ISletr(*q); q++)
  415.         ;
  416.     *q = '\0';
  417.  
  418.     SetVar(Name, Expand(Value));
  419. }
  420.  
  421. /**
  422. ***        " O U T P U T "   C O M M A N D S
  423. **/
  424.  
  425.  
  426. /*
  427. **  Do a cat command.  Understands the following:
  428. **    cat >arg1 <<arg2
  429. **    cat >>arg1 <<arg2
  430. **    cat >>arg1 /dev/null
  431. **  Except that arg2 is assumed to be quoted -- i.e., no expansion of meta-chars
  432. **  inside the "here" document is done.  The IO redirection can be in any order.
  433. */
  434. /* ARGSUSED */
  435. static int
  436. DoCAT(ac, av)
  437.     int             ac;
  438.     REGISTER char    *av[];
  439. {
  440.     REGISTER FILE    *Out;
  441.     REGISTER char    *Ending;
  442.     REGISTER char    *Source;
  443.     REGISTER int     V;
  444.     REGISTER int     l;
  445.  
  446.     /* Parse the I/O redirecions. */
  447.     for (V = TRUE, Source = NULL, Out = NULL, Ending = NULL; *++av; )
  448.     if (EQ(*av, ">") && av[1]) {
  449.         av++;
  450.         /* This is a hack, but maybe MS-DOS doesn't have /dev/null? */
  451.         Out = Running ? fopen(Expand(*av), "w") : stderr;
  452.     }
  453.     else if (EQ(*av, ">>") && av[1]) {
  454.         av++;
  455.         /* And besides, things are actually faster this way. */
  456.         Out = Running ? fopen(Expand(*av), "a") : stderr;
  457.     }
  458.     else if (EQ(*av, "<<") && av[1]) {
  459.         for (Ending = *++av; *Ending == '\\'; Ending++)
  460.         ;
  461.         l = strlen(Ending);
  462.     }
  463.     else if (!EQ(Source = *av, "/dev/null"))
  464.         SynErr("CAT (bad input filename)");
  465.  
  466.     if (Out == NULL || (Ending == NULL && Source == NULL)) {
  467.     Note("Missing parameter in CAT command.\n", (char *)NULL);
  468.     V = FALSE;
  469.     }
  470.  
  471.     /* Read the input, spit it out. */
  472.     if (V && Running && Out != stderr) {
  473.     if (Source == NULL)
  474.         while (GetLine(FALSE) && !EQn(Text, Ending, l))
  475.         (void)fputs(Text, Out);
  476.     (void)fclose(Out);
  477.     }
  478.     else
  479.     while (GetLine(FALSE) && !EQn(Text, Ending, l))
  480.         ;
  481.  
  482.     return(V);
  483. }
  484.  
  485.  
  486. /*
  487. **  Do a SED command.  Understands the following:
  488. **    sed sX^yyyyXX >arg1 <<arg2
  489. **    sed -e sX^yyyyXX >arg1 <<arg2
  490. **  Where the yyyy is a miniscule regular expression; see Matches(), above.
  491. **  The "X" can be any single character and the ^ is optional (sigh).  No
  492. **  shell expansion is done inside the "here' document.  The IO redirection
  493. **  can be in any order.
  494. */
  495. /* ARGSUSED */
  496. static int
  497. DoSED(ac, av)
  498.     int             ac;
  499.     REGISTER char    *av[];
  500. {
  501.     REGISTER FILE    *Out;
  502.     REGISTER char    *Pattern;
  503.     REGISTER char    *Ending;
  504.     REGISTER char    *p;
  505.     REGISTER int     V;
  506.     REGISTER int     l;
  507.     REGISTER int     i;
  508.  
  509.     /* Parse IO redirection stuff. */
  510.     for (V = TRUE, Out = NULL, Pattern = NULL, Ending = NULL; *++av; )
  511.     if (EQ(*av, ">") && av[1]) {
  512.         av++;
  513.         Out = Running ? fopen(Expand(*av), "w") : stderr;
  514.     }
  515.     else if (EQ(*av, ">>") && av[1]) {
  516.         av++;
  517.         Out = Running ? fopen(Expand(*av), "a") : stderr;
  518.     }
  519.     else if (EQ(*av, "<<") && av[1]) {
  520.         for (Ending = *++av; *Ending == '\\'; Ending++)
  521.         ;
  522.         l = strlen(Ending);
  523.     }
  524.     else
  525.         Pattern = EQ(*av, "-e") && av[1] ? *++av : *av;
  526.  
  527.     /* All there? */
  528.     if (Out == NULL || Ending == NULL || Pattern == NULL) {
  529.     Note("Missing parameter in SED command.\n", (char *)NULL);
  530.     V = FALSE;
  531.     }
  532.  
  533.     /* Parse the substitute command and its pattern. */
  534.     if (*Pattern != 's') {
  535.     Note("Bad SED command -- not a substitute.\n", (char *)NULL);
  536.     V = FALSE;
  537.     }
  538.     else {
  539.     Pattern++;
  540.     p = Pattern + strlen(Pattern) - 1;
  541.     if (*p != *Pattern || *--p != *Pattern) {
  542.         Note("Bad substitute pattern in SED command.\n", (char *)NULL);
  543.         V = FALSE;
  544.     }
  545.     else {
  546.         /* Now check the pattern. */
  547.         if (*++Pattern == '^')
  548.         Pattern++;
  549.         for (*p = '\0', i = strlen(Pattern), p = Pattern; *p; p++)
  550.         if (*p == '[' || *p == '*' || *p == '$') {
  551.             Note("Bad meta-character in SED pattern.\n", (char *)NULL);
  552.             V = FALSE;
  553.         }
  554.     }
  555.     }
  556.  
  557.     /* Spit out the input. */
  558.     if (V && Running && Out != stderr) {
  559.     while (GetLine(FALSE) && !EQn(Text, Ending, l))
  560.         (void)fputs(Matches(Pattern, Text) ? &Text[i] : Text, Out);
  561.     (void)fclose(Out);
  562.     }
  563.     else
  564.     while (GetLine(FALSE) && !EQn(Text, Ending, l))
  565.         ;
  566.  
  567.     return(V);
  568. }
  569.  
  570. /**
  571. ***        " S I M P L E "   C O M M A N D S
  572. **/
  573.  
  574.  
  575. /*
  576. **  Parse a cp command of the form:
  577. **    cp /dev/null arg
  578. **  We should check if "arg" is a safe file to clobber, but...
  579. */
  580. static int
  581. DoCP(ac, av)
  582.     int         ac;
  583.     char    *av[];
  584. {
  585.     FILE    *F;
  586.  
  587.     if (Running) {
  588.     if (ac != 3 || !EQ(av[1], "/dev/null"))
  589.         SynErr("CP");
  590.     if (F = fopen(Expand(av[2]), "w")) {
  591.         (void)fclose(F);
  592.         return(TRUE);
  593.     }
  594.     Note("Can't create %s.\n", av[2]);
  595.     }
  596.     return(FALSE);
  597. }
  598.  
  599.  
  600. /*
  601. **  Do a mkdir command of the form:
  602. **    mkdir arg
  603. */
  604. static int
  605. DoMKDIR(ac, av)
  606.     int         ac;
  607.     char    *av[];
  608. {
  609.     if (Running) {
  610.     if (ac != 2)
  611.         SynErr("MKDIR");
  612.     if (mkdir(Expand(av[1]), 0777) >= 0)
  613.         return(TRUE);
  614.     Note("Can't make directory %s.\n", av[1]);
  615.     }
  616.     return(FALSE);
  617. }
  618.  
  619.  
  620. /*
  621. **  Do a cd command of the form:
  622. **    cd arg
  623. **    chdir arg
  624. */
  625. static int
  626. DoCD(ac, av)
  627.     int         ac;
  628.     char    *av[];
  629. {
  630.     if (Running) {
  631.     if (ac != 2)
  632.         SynErr("CD");
  633.     if (chdir(Expand(av[1])) >= 0)
  634.         return(TRUE);
  635.     Note("Can't cd to %s.\n", av[1]);
  636.     }
  637.     return(FALSE);
  638. }
  639.  
  640.  
  641. /*
  642. **  Do the echo command.  Understands the "-n" hack.
  643. */
  644. /* ARGSUSED */
  645. static int
  646. DoECHO(ac, av)
  647.     int         ac;
  648.     char    *av[];
  649. {
  650.     int         Flag;
  651.  
  652.     if (Running) {
  653.     if (Flag = av[1] != NULL && EQ(av[1], "-n"))
  654.         av++;
  655.     while (*++av)
  656.         Fprintf(stderr, "%s ", Expand(*av));
  657.     if (!Flag)
  658.         Fprintf(stderr, "\n");
  659.     (void)fflush(stderr);
  660.     }
  661.     return(TRUE);
  662. }
  663.  
  664.  
  665. /*
  666. **  Generic "handler" for commands we can't do.
  667. */
  668. static int
  669. DoIT(ac, av)
  670.     int         ac;
  671.     char    *av[];
  672. {
  673.     if (Running)
  674.     Fprintf(stderr, "You'll have to do this yourself:\n\t%s ", *av);
  675.     return(DoECHO(ac, av));
  676. }
  677.  
  678.  
  679. /*
  680. **  Do an EXIT command.
  681. */
  682. static int
  683. DoEXIT(ac, av)
  684.     int         ac;
  685.     char    *av[];
  686. {
  687.     ac = *++av ? atoi(Expand(*av)) : 0;
  688.     Fprintf(stderr, "Exiting, with status %d\n", ac);
  689. #ifdef    MSDOS
  690.     longjmp(jEnv, 1);
  691. #endif    /* MSDOS */
  692.     return(ac);
  693. }
  694.  
  695.  
  696. /*
  697. **  Do an EXPORT command.  Often used to make sure the archive is being
  698. **  unpacked with the Bourne (or Korn?) shell.  We look for:
  699. **    export PATH blah blah blah
  700. */
  701. static int
  702. DoEXPORT(ac, av)
  703.     int         ac;
  704.     char    *av[];
  705. {
  706.     if (ac < 2 || !EQ(av[1], "PATH"))
  707.     SynErr("EXPORT");
  708.     return(TRUE);
  709. }
  710.  
  711. /**
  712. ***        F L O W - O F - C O N T R O L   C O M M A N D S
  713. **/
  714.  
  715.  
  716. /*
  717. **  Parse a "test" statement.  Returns TRUE or FALSE.  Understands the
  718. **  following tests:
  719. **    test {!} -f arg        Is arg {not} a plain file?
  720. **    test {!} -d arg        Is arg {not} a directory?
  721. **    test {!} $var -eq $var    Is the variable {not} equal to the variable?
  722. **    test {!} $var != $var    Is the variable {not} equal to the variable?
  723. **    test {!} ddd -ne `wc -c {<} arg`
  724. **                Is size of arg {not} equal to ddd in bytes?
  725. **    test -f arg -a $var -eq val
  726. **                Used by my shar, check for file clobbering
  727. **  These last two tests are starting to really push the limits of what is
  728. **  reasonable to hard-code, but they are common cliches in shell archive
  729. **  "programming."  We also understand the [ .... ] way of writing test.
  730. **  If we can't parse the test, we show the command and ask the luser.
  731. */
  732. static int
  733. DoTEST(ac, av)
  734.     REGISTER int      ac;
  735.     REGISTER char     *av[];
  736. {
  737.     REGISTER char    **p;
  738.     REGISTER char     *Name;
  739.     REGISTER FILE     *DEVTTY;
  740.     REGISTER int      V;
  741.     REGISTER int      i;
  742.     char          buff[LINE_SIZE];
  743.  
  744.     /* Quick test. */
  745.     if (!Running)
  746.     return(FALSE);
  747.  
  748.     /* See if we're called as "[ ..... ]" */
  749.     if (EQ(*av, "[")) {
  750.     for (i = 1; av[i] && !EQ(av[i], "]"); i++)
  751.         ;
  752.     free(av[i]);
  753.     av[i] = NULL;
  754.     ac--;
  755.     }
  756.  
  757.     /* Ignore the "test" argument. */
  758.     av++;
  759.     ac--;
  760.  
  761.     /* Inverted test? */
  762.     if (EQ(*av, "!")) {
  763.     V = FALSE;
  764.     av++;
  765.     ac--;
  766.     }
  767.     else
  768.     V = TRUE;
  769.  
  770.     /* Testing for file-ness? */
  771.     if (ac == 2 && EQ(av[0], "-f") && (Name = Expand(av[1])))
  772.     return(GetStat(Name) && Ftype(Name) == F_FILE ? V : !V);
  773.  
  774.     /* Testing for directory-ness? */
  775.     if (ac == 2 && EQ(av[0], "-d") && (Name = Expand(av[1])))
  776.     return(GetStat(Name) && Ftype(Name) == F_DIR ? V : !V);
  777.  
  778.     /* Testing a variable's value? */
  779.     if (ac == 3 && (EQ(av[1], "-eq") || EQ(av[1], "=")))
  780.     return(EQ(Expand(av[0]), Expand(av[2])) ? V : !V);
  781.     if (ac == 3 && (EQ(av[1], "-ne") || EQ(av[1], "!=")))
  782.     return(!EQ(Expand(av[0]), Expand(av[2])) ? V : !V);
  783.  
  784.     /* Testing a file's size? */
  785.     if (ac == (av[5] && EQ(av[5], "<") ? 8 : 7) && isdigit(av[0][0])
  786.      && (EQ(av[1], "-ne") || EQ(av[1], "-eq"))
  787.      && EQ(av[2], "`") && EQ(av[3], "wc")
  788.      && EQ(av[4], "-c") && EQ(av[ac - 1], "`")) {
  789.     if (GetStat(av[ac - 2])) {
  790.         if (EQ(av[1], "-ne"))
  791.         return(Fsize(av[ac - 2]) != atol(av[0]) ? V : !V);
  792.         return(Fsize(av[ac - 2]) == atol(av[0]) ? V : !V);
  793.     }
  794.     Note("Can't get status of %s.\n", av[ac - 2]);
  795.     }
  796.  
  797.     /* Testing for existing, but can clobber? */
  798.     if (ac == 6 && EQ(av[0], "-f") && EQ(av[2], "-a")
  799.      && (EQ(av[4], "!=") || EQ(av[4], "-ne")))
  800.     return(GetStat(Name = Expand(av[1])) && Ftype(Name) == F_FILE
  801.            && EQ(Expand(av[3]), Expand(av[5])) ? !V : V);
  802.  
  803.     /* I give up -- print it out, and let's ask Mikey, he can do it... */
  804.     Fprintf(stderr, "Can't parse this test:\n\t");
  805.     for (i = FALSE, p = av; *p; p++) {
  806.     Fprintf(stderr, "%s ", *p);
  807.     if (p[0][0] == '$')
  808.         i = TRUE;
  809.     }
  810.     if (i) {
  811.     Fprintf(stderr, "\n(Here it is with shell variables expanded...)\n\t");
  812.     for (p = av; *p; p++)
  813.         Fprintf(stderr, "%s ", Expand(*p));
  814.     }
  815.     Fprintf(stderr, "\n");
  816.  
  817.     DEVTTY = fopen(THE_TTY, "r");
  818.     do {
  819.     Fprintf(stderr, "Is value true/false/quit [tfq] (q):  ");
  820.     (void)fflush(stderr);
  821.     clearerr(DEVTTY);
  822.     if (fgets(buff, sizeof buff, DEVTTY) == NULL
  823.      || buff[0] == 'q' || buff[0] == 'Q' || buff[0] == '\n')
  824.         SynErr("TEST");
  825.     if (buff[0] == 't' || buff[0] == 'T') {
  826.         (void)fclose(DEVTTY);
  827.         return(TRUE);
  828.     }
  829.     } while (buff[0] != 'f' && buff[0] != 'F');
  830.     (void)fclose(DEVTTY);
  831.     return(FALSE);
  832. }
  833.  
  834.  
  835. /*
  836. **  Do an IF statement.
  837. */
  838. static int
  839. DoIF(ac, av)
  840.     REGISTER int     ac;
  841.     REGISTER char    *av[];
  842. {
  843.     REGISTER char    **p;
  844.     REGISTER int      Flag;
  845.     char         *vec[MAX_WORDS];
  846.     char        **Pushed;
  847.  
  848.     /* Skip first argument. */
  849.     if (!EQ(*++av, "[") && !EQ(*av, "test"))
  850.     SynErr("IF");
  851.     ac--;
  852.  
  853.     /* Look for " ; then " on this line, or "then" on next line. */
  854.     for (Pushed = NULL, p = av; *p; p++)
  855.     if (Flag = EQ(*p, ";")) {
  856.         if (p[1] == NULL || !EQ(p[1], "then"))
  857.         SynErr("IF");
  858.         *p = NULL;
  859.         ac -= 2;
  860.         break;
  861.     }
  862.     if (!Flag) {
  863.     (void)GetLine(TRUE);
  864.     if (Argify(vec) > 1)
  865.         Pushed = &vec[1];
  866.     if (!EQ(vec[0], "then"))
  867.         SynErr("IF (missing THEN)");
  868.     }
  869.  
  870.     if (DoTEST(ac, av)) {
  871.     if (Pushed)
  872.         (void)Exec(Pushed);
  873.     while (GetLine(TRUE)) {
  874.         if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi"))
  875.         break;
  876.         if (EQ(vec[0], "else")) {
  877.         DoUntil("fi", FALSE);
  878.         break;
  879.         }
  880.         (void)Exec(vec);
  881.     }
  882.     }
  883.     else
  884.     while (GetLine(TRUE)) {
  885.         if ((ac = Argify(vec)) == 1 && EQ(vec[0], "fi"))
  886.         break;
  887.         if (EQ(vec[0], "else")) {
  888.         if (ac > 1)
  889.             (void)Exec(&vec[1]);
  890.         DoUntil("fi", Running);
  891.         break;
  892.         }
  893.     }
  894.     return(TRUE);
  895. }
  896.  
  897.  
  898. /*
  899. **  Do a FOR statement.
  900. */
  901. static int
  902. DoFOR(ac, av)
  903.     REGISTER int      ac;
  904.     REGISTER char     *av[];
  905. {
  906.     REGISTER char     *Var;
  907.     REGISTER char    **Values;
  908.     REGISTER int      Found;
  909.     long          Here;
  910.     char         *vec[MAX_WORDS];
  911.  
  912.     /* Check usage, get variable name and eat noise words. */
  913.     if (ac < 4 || !EQ(av[2], "in"))
  914.     SynErr("FOR");
  915.     Var = av[1];
  916.     ac -= 3;
  917.     av += 3;
  918.  
  919.     /* Look for "; do" on this line, or just "do" on next line. */
  920.     for (Values = av; *++av; )
  921.     if (Found = EQ(*av, ";")) {
  922.         if (av[1] == NULL || !EQ(av[1], "do"))
  923.         SynErr("FOR");
  924.         *av = NULL;
  925.         break;
  926.     }
  927.     if (!Found) {
  928.     (void)GetLine(TRUE);
  929.     if (Argify(vec) != 1 || !EQ(vec[0], "do"))
  930.         SynErr("FOR (missing DO)");
  931.     }
  932.  
  933.     for (Here = ftell(Input); *Values; ) {
  934.     SetVar(Var, *Values);
  935.     DoUntil("done", Running);
  936.         ;
  937.     /* If we're not Running, only go through the loop once. */
  938.     if (!Running)
  939.         break;
  940.     if (*++Values && (fseek(Input, Here, 0) < 0 || ftell(Input) != Here))
  941.         SynErr("FOR (can't seek back)");
  942.     }
  943.  
  944.     return(TRUE);
  945. }
  946.  
  947.  
  948. /*
  949. **  Do a CASE statement of the form:
  950. **    case $var in
  951. **        text1)
  952. **        ...
  953. **        ;;
  954. **    esac
  955. **  Where text1 is a simple word or an asterisk.
  956. */
  957. static int
  958. DoCASE(ac, av)
  959.     REGISTER int     ac;
  960.     REGISTER char    *av[];
  961. {
  962.     REGISTER int     FoundIt;
  963.     char        *vec[MAX_WORDS];
  964.     char         Value[VAR_VALUE_SIZE];
  965.  
  966.     if (ac != 3 || !EQ(av[2], "in"))
  967.     SynErr("CASE");
  968.     (void)strcpy(Value, Expand(av[1]));
  969.  
  970.     for (FoundIt = FALSE; GetLine(TRUE); ) {
  971.     ac = Argify(vec);
  972.     if (EQ(vec[0], "esac"))
  973.         break;
  974.     /* This is for vi: (-; sigh. */
  975.     if (ac != 2 || !EQ(vec[1], ")"))
  976.         SynErr("CASE");
  977.     if (!FoundIt && (EQ(vec[0], Value) || EQ(vec[0], "*"))) {
  978.         FoundIt = TRUE;
  979.         if (Running && ac > 2)
  980.         (void)Exec(&vec[2]);
  981.         DoUntil(";;", Running);
  982.     }
  983.     else
  984.         DoUntil(";;", FALSE);
  985.     }
  986.     return(TRUE);
  987. }
  988.  
  989.  
  990.  
  991. /*
  992. **  Dispatch table of known commands.
  993. */
  994. static COMTAB     Dispatch[] = {
  995.     {    "cat",        DoCAT        },
  996.     {    "case",        DoCASE        },
  997.     {    "cd",        DoCD        },
  998.     {    "chdir",    DoCD        },
  999.     {    "chmod",    DoIT        },
  1000.     {    "cp",        DoCP        },
  1001.     {    "echo",        DoECHO        },
  1002.     {    "exit",        DoEXIT        },
  1003.     {    "export",    DoEXPORT    },
  1004.     {    "for",        DoFOR        },
  1005.     {    "if",        DoIF        },
  1006.     {    "mkdir",    DoMKDIR        },
  1007.     {    "rm",        DoIT        },
  1008.     {    "sed",        DoSED        },
  1009.     {    "test",        DoTEST        },
  1010.     {    "[",        DoTEST        },
  1011.     {    ":",        DoIT        },
  1012.     {    "",        NULL        }
  1013. };
  1014.  
  1015.  
  1016. /*
  1017. **  Dispatch on a parsed line.
  1018. */
  1019. int
  1020. Exec(av)
  1021.     REGISTER char    *av[];
  1022. {
  1023.     REGISTER int     i;
  1024.     REGISTER COMTAB    *p;
  1025.  
  1026.     /* We have to re-calculate this because our callers can't always
  1027.        pass the count down to us easily. */
  1028.     for (i = 0; av[i]; i++)
  1029.     ;
  1030.     if (i) {
  1031.     /* Is this a command we know? */
  1032.     for (p = Dispatch; p->Func; p++)
  1033.         if (EQ(av[0], p->Name)) {
  1034.         i = (*p->Func)(i, av);
  1035.         if (p->Func == DoEXIT)
  1036.             /* Sigh; this is a hack. */
  1037.             return(-FALSE);
  1038.         break;
  1039.         }
  1040.  
  1041.     /* If not a command, try it as a variable assignment. */
  1042.     if (p->Func == NULL)
  1043.         /* Yes, we look for "=" in the first word, but pass down
  1044.            the whole line. */
  1045.         if (IDX(av[0], '='))
  1046.         DoASSIGN(Text);
  1047.         else
  1048.         Note("Command %s unknown.\n", av[0]);
  1049.  
  1050.     /* Free the line. */
  1051.     for (i = 0; av[i]; i++)
  1052.         free(av[i]);
  1053.     }
  1054.     return(TRUE);
  1055. }
  1056.  
  1057.  
  1058. /*
  1059. **  Do until we reach a specific terminator.
  1060. */
  1061. static
  1062. DoUntil(Terminator, NewVal)
  1063.     char    *Terminator;
  1064.     int         NewVal;
  1065. {
  1066.     char    *av[MAX_WORDS];
  1067.     int         OldVal;
  1068.  
  1069.     for (OldVal = Running, Running = NewVal; GetLine(TRUE); )
  1070.     if (Argify(av)) {
  1071.         if (EQ(av[0], Terminator))
  1072.         break;
  1073.         (void)Exec(av);
  1074.     }
  1075.  
  1076.     Running = OldVal;
  1077. }
  1078.