home *** CD-ROM | disk | FTP | other *** search
/ Columbia Kermit / kermit.zip / convergent / ctcmd.c < prev    next >
C/C++ Source or Header  |  2020-01-01  |  32KB  |  1,196 lines

  1. char *cmdv = "Interactive command parser CTOS/BTOS Version-2.00, Sep 1991";
  2.  
  3. /*  C K C M D  --  Interactive command package for Unix  */
  4. /*
  5.  Modelled after the DECSYSTEM-20 command parser (the COMND JSYS)
  6.  
  7.  Features:
  8.  . parses and verifies keywords, text strings, numbers, and other data
  9.  . displays appropriate menu or help message when user types "?"
  10.  . does keyword and filename completion when user types ESC
  11.  . accepts any unique abbreviation for a keyword
  12.  . allows keywords to have attributes, like "invisible"
  13.  . can supply defaults for fields omitted by user
  14.  . provides command line editing (character, word, and line deletion)
  15.  . accepts input from keyboard, command files, or redirected stdin
  16.  . allows for full or half duplex operation, character or line input
  17.  . settable prompt, protected from deletion
  18.  
  19.  Functions:
  20.   cmsetp - Set prompt
  21.   cmsavp - Save current prompt
  22.   prompt - Issue prompt
  23.   cmini  - Clear the command buffer (before parsing a new command)
  24.   cmres  - Reset command buffer pointers (before reparsing)
  25.   cmkey  - Parse a keyword
  26.   cmnum  - Parse a number
  27.   cmifi  - Parse an input file name
  28.   cmofi  - Parse an output file name
  29.   cmfld  - Parse an arbitrary field
  30.   cmtxt  - Parse a text string
  31.   cmcfm  - Parse command confirmation (end of line)
  32.   stripq - Strip out backslash quotes from a string.
  33.   ckvar  - Check if a name is valid for a variable
  34.   expvar - Expand a variable into its string
  35.   savvar - Save a value to a specified variable buffer
  36.   clrkbd - Clears the keyboard buffer
  37.  
  38.  Return codes:
  39.   -3: no input provided when required
  40.   -2: input was invalid
  41.   -1: reparse required (user deleted into a preceding field)
  42.    0 or greater: success
  43.   See individual functions for greater detail.
  44.  
  45.  Before using these routines, the caller should #include ckcmd.h, and
  46.  set the program's prompt by calling cmsetp().  If the file parsing
  47.  functions cmifi and cmofi are to be used, this module must be linked
  48.  with a ckz??? file system support module for the appropriate system,
  49.  e.g. ckzbsd for Berkeley Unix.  If the caller puts the terminal in
  50.  character wakeup ("cbreak") mode with no echo, then these functions will
  51.  provide line editing -- character, word, and line deletion, as well as
  52.  keyword and filename completion upon ESC and help strings, keyword, or
  53.  file menus upon '?'.  If the caller puts the terminal into character
  54.  wakeup/noecho mode, care should be taken to restore it before exit from
  55.  or interruption of the program.  If the character wakeup mode is not
  56.  set, the system's own line editor may be used.
  57.  
  58.  Author: Frank da Cruz (SY.FDC@CU20B),
  59.  Columbia University Center for Computing Activities, Jan 1985.
  60.  Copyright (C) 1985, Trustees of Columbia University in the City of New York.
  61.  Permission is granted to any individual or institution to copy or use this
  62.  software except for explicitly commercial purposes, provided this copyright
  63.  notice is retained.
  64. */
  65. /* Modifications:
  66.            PUSHCMD function added to support IF command
  67.                    SAVVAR function added to support variables
  68.                    CKVAR function added to support variables
  69.                    EXPVAR function added to support variables
  70.                    CLRKBD function added to support interactive IF command
  71.  
  72.            D. Drury, E. Arnerich 1991
  73.  
  74. */
  75.  
  76. /* Includes */
  77.  
  78. #include <stdio.h>            /* Standard C I/O package */
  79. #include <ctype.h>            /* Character types */
  80. #include "ctcmd.h"            /* Command parsing definitions */
  81. #include "ctdebu.h"            /* Formats for debug() */
  82.  
  83. #ifndef MAXVARS
  84. #define MAXVARS 26
  85. #endif
  86. #ifndef MAXVARL
  87. #define MAXVARL 21
  88. #endif
  89. extern char savbuf[CMDBL+4];
  90. extern char g_vars[MAXVARS][MAXVARL];  /* global variable buffers */
  91. extern int expand_var;
  92. extern int dsplon; /* error msg display flag 1 = display defined in CTUSR3*/
  93.  
  94. /* Local variables */
  95.  
  96. int psetf = 0,                /* Flag that prompt has been set */
  97.     cc = 0,                /* Character count */
  98.     dpx = 0;                /* Duplex (0 = full) */
  99.  
  100. int hw = HLPLW,                /* Help line width */
  101.     hc = HLPCW,                /* Help line column width */
  102.     hh,                    /* Current help column number */
  103.     hx;                    /* Current help line position */
  104.  
  105. #define PROML 60            /* Maximum length for prompt */
  106.  
  107. char cmprom[PROML+1];            /* Program's prompt */
  108. char *dfprom = "Command? ";        /* Default prompt */
  109.  
  110. int cmflgs;                /* Command flags */
  111.  
  112. char cmdbuf[CMDBL+4];            /* Command buffer */
  113. char inicmd[MAXINI][CMDBL];        /* Initial commands from -O option */
  114. int  inicmd_count = 0;
  115. int  inicmd_index = 0;
  116. char hlpbuf[HLPBL+4];            /* Help string buffer */
  117. char atmbuf[ATMBL+4];            /* Atom buffer */
  118. char filbuf[ATMBL+4];            /* File name buffer */
  119.  
  120. /* Command buffer pointers */
  121.  
  122. static char *bp,            /* Current command buffer position */
  123.     *pp,                /* Start of current field */
  124.     *np;                /* Start of next field */
  125. /*  C M S E T P  --  Set the program prompt.  */
  126.  
  127. cmsetp(s) char *s; {
  128.     char *strncpy();
  129.     psetf = 1;                /* Flag that prompt has been set. */
  130.     strncpy(cmprom,s,PROML - 1);    /* Copy the string. */
  131.     cmprom[PROML] = NUL;
  132. }
  133.  
  134.  
  135. /*  C M S A V P  --  Save a copy of the current prompt.  */
  136.  
  137. cmsavp(s,n) int n; char s[]; {
  138.     strncpy(s,cmprom,n-1);
  139.     s[n] = NUL;
  140. }
  141.  
  142.  
  143. /*  P R O M P T  --  Issue the program prompt.  */
  144.  
  145. prompt() {
  146.     if (psetf == 0) cmsetp(dfprom);    /* If no prompt set, set default. */
  147.     printf("\r%s",cmprom);        /* Print the prompt. */
  148. }
  149.  
  150.  
  151. /*  C M R E S  --  Reset pointers to beginning of command buffer.  */
  152.  
  153. cmres() {
  154.  
  155.     cc = 0;                /* Reset character counter. */
  156.     pp = cmdbuf;
  157.     pp = np = bp = cmdbuf;        /* Point to command buffer. */
  158.     cmflgs = -5;            /* Parse not yet started. */
  159. }
  160.  
  161.  
  162. /*  C M I N I  --  Clear the command and atom buffers, reset pointers.  */
  163.  
  164. /*
  165. The argument specifies who is to echo the user's typein --
  166.   1 means the cmd package echoes
  167.   0 somebody else (system, front end, terminal) echoes
  168. */
  169. cmini(d) int d; {
  170.     for (bp = cmdbuf; bp < cmdbuf+CMDBL; bp++) *bp = NUL;
  171.     *atmbuf = NUL;
  172.     dpx = d;
  173.     cmres();
  174. }
  175.  
  176. stripq(s) char *s; {            /* Function to strip '\' quotes */
  177.     char *t;
  178.     while (*s) {
  179.     if (*s == '\\') {
  180.         for (t = s; *t != '\0'; t++) *t = *(t+1);
  181.     }
  182.     s++;
  183.     }
  184. }
  185.  
  186. /*  C M N U M  --  Parse a number in the indicated radix  */
  187.  
  188. /*  For now, only works for positive numbers in base 10.  */
  189.  
  190. /*
  191.  Returns
  192.    -3 if no input present when required,
  193.    -2 if user typed an illegal number,
  194.    -1 if reparse needed,
  195.     0 otherwise, with n set to number that was parsed
  196. */
  197. cmnum(xhlp,xdef,radix,n) char *xhlp, *xdef; int radix, *n; {
  198.     int x; char *s;
  199.  
  200.     if (radix != 10) {            /* Just do base 10 for now */
  201.     printf("cmnum: illegal radix - %d\n",radix);
  202.     return(-1);
  203.     }
  204.  
  205.     x = cmfld(xhlp,xdef,&s);
  206.     debug(F101,"cmnum: cmfld","",x);
  207.     if (x < 0) return(x);    /* Parse a field */
  208.  
  209.     if (digits(atmbuf)) {        /* Convert to number */
  210.     *n = atoi(atmbuf);
  211.     return(x);
  212.     } else {
  213.     printf("\n?not a number - %s\n",s);
  214.     return(-2);
  215.     }
  216. }
  217.  
  218. /*  C M O F I  --  Parse the name of an output file  */
  219.  
  220. /*
  221.  Depends on the external function zchko(); if zchko() not available, use
  222.  cmfld() to parse output file names.
  223.  
  224.  Returns
  225.    -3 if no input present when required,
  226.    -2 if permission would be denied to create the file,
  227.    -1 if reparse needed,
  228.     0 or 1 otherwise, with xp pointing to name.
  229. */
  230. cmofi(xhlp,xdef,xp) char *xhlp, *xdef, **xp; {
  231.     int x; char *s;
  232.  
  233.     if (*xhlp == NUL) xhlp = "Output file";
  234.     *xp = "";
  235.  
  236.     if ((x = cmfld(xhlp,xdef,&s)) < 0) return(x);
  237.  
  238.     if (chkwld(s)) {
  239.     printf("\n?Wildcards not allowed - %s\n",s);
  240.     return(-2);
  241.     }
  242.     if (zchko(s) < 0) {
  243.     printf("\n?Write permission denied - %s\n",s);
  244.     return(-2);
  245.     } else {
  246.     *xp = s;
  247.     return(x);
  248.     }
  249. }
  250.  
  251. /*  C M I F I  --  Parse the name of an existing file  */
  252.  
  253. /*
  254.  This function depends on the external functions:
  255.    zchki()  - Check if input file exists and is readable.
  256.    zxpand() - Expand a wild file specification into a list.
  257.    znext()  - Return next file name from list.
  258.  If these functions aren't available, then use cmfld() to parse filenames.
  259. */
  260. /*
  261.  Returns
  262.    -4 EOF
  263.    -3 if no input present when required,
  264.    -2 if file does not exist or is not readable,
  265.    -1 if reparse needed,
  266.     0 or 1 otherwise, with:
  267.     xp pointing to name,
  268.         wild = 1 if name contains '*' or '?', 0 otherwise.
  269. */
  270. cmifi(xhlp,xdef,xp,wild) char *xhlp, *xdef, **xp; int *wild; {
  271.     int i, x, xc, y; char *sp;
  272.  
  273.     cc = xc = 0;            /* Initialize counts & pointers */
  274.     *xp = "";
  275.     if ((x = cmflgs) != 1) {        /* Already confirmed? */
  276.     x = getwd();            /* No, get a word */
  277.     } else {
  278.     cc = setatm(xdef);        /* If so, use default, if any. */
  279.     }
  280.     *xp = atmbuf;            /* Point to result. */
  281.     *wild = chkwld(*xp);
  282.  
  283.     while (1) {
  284.     xc += cc;            /* Count the characters. */
  285.     debug(F111,"cmifi: getwd",atmbuf,xc);
  286.         switch (x) {
  287.         case -4:            /* EOF */
  288.         case -2:            /* Out of space. */
  289.         case -1:            /* Reparse needed */
  290.             return(x);
  291.  
  292. /* cont'd... */
  293.  
  294. /* ...cmifi(), cont'd */
  295.  
  296.  
  297.         case 0:            /* SP or NL */
  298.         case 1:
  299.             if (xc == 0) *xp = xdef;    /* If no input, return default. */
  300.         else *xp = atmbuf;
  301.         if (**xp == NUL) return(-3); /* If field empty, return -3. */
  302.  
  303.         /* If filespec is wild, see if there are any matches */
  304.  
  305.         *wild = chkwld(*xp);
  306.         debug(F101," *wild","",*wild);
  307.         if (*wild != 0) {
  308.             y = zxpand(*xp);
  309.             if (y == 0) {
  310.             if (dsplon)
  311.             printf("\n?No files match - %s\n",*xp);
  312.             return(-2);
  313.             } else if (y < 0) {
  314.             if (dsplon)
  315.             printf("\n?Too many files match - %s\n",*xp);
  316.             return(-2);
  317.             } else return(x);
  318.         }
  319.  
  320.         /* If not wild, see if it exists and is readable. */
  321.  
  322.         y = zchki(*xp);
  323.         if (y == -3) {
  324.             if (dsplon)
  325.             printf("\n?Read permission denied - %s\n",*xp);
  326.             return(-2);
  327.         } else if (y == -2) {
  328.             if (dsplon)
  329.             printf("\n?File not readable - %s\n",*xp);
  330.             return(-2);
  331.         } else if (y < 0) {
  332.             if (dsplon)
  333.             printf("\n?File not found - %s\n",*xp);
  334.             return(-2);
  335.         }
  336.         return(x);
  337. /* cont'd... */
  338.  
  339. /* ...cmifi(), cont'd */
  340.  
  341.  
  342.         case 2:            /* ESC */
  343.             if (xc == 0) {
  344.             if (*xdef != '\0') {
  345.             printf("%s ",xdef); /* If at beginning of field, */
  346.             addbuf(xdef);    /* supply default. */
  347.             cc = setatm(xdef);
  348.             } else {        /* No default */
  349.             putchar(BEL);
  350.             }
  351.             break;
  352.         }
  353.         if (*wild = chkwld(*xp)) {  /* No completion if wild */
  354.             putchar(BEL);
  355.             break;
  356.         }
  357.         sp = atmbuf + cc;
  358.         *sp++ = '*';
  359.         *sp-- = '\0';
  360.         y = zxpand(atmbuf);    /* Add * and expand list. */
  361.         *sp = '\0';        /* Remove *. */
  362.  
  363.         if (y == 0) {
  364.             if (dsplon)
  365.             printf("\n?No files match - %s\n",atmbuf);
  366.             return(-2);
  367.         } else if (y < 0) {
  368.             if (dsplon)
  369.             printf("\n?Too many files match - %s\n",atmbuf);
  370.             return(-2);
  371.         } else if (y > 1) {    /* Not unique, just beep. */
  372.             putchar(BEL);
  373.         } else {        /* Unique, complete it.  */
  374.             znext(filbuf);    /* Get whole name of file. */
  375.             sp = filbuf + cc;    /* Point past what user typed. */
  376.             printf("%s ",sp);    /* Complete the name. */
  377.             addbuf(sp);        /* Add the characters to cmdbuf. */
  378.             setatm(pp);        /* And to atmbuf. */
  379.             *xp = atmbuf;    /* Return pointer to atmbuf. */
  380.             return(cmflgs = 0);
  381.         }
  382.         break;
  383.  
  384. /* cont'd... */
  385.  
  386. /* ...cmifi(), cont'd */
  387.  
  388.  
  389.         case 3:            /* Question mark */
  390.             if (*xhlp == NUL)
  391.                 printf(" Input file specification");
  392.         else
  393.             printf(" %s",xhlp);
  394.         if (xc > 0) {
  395.             sp = atmbuf + cc;    /* Insert * at end */
  396.             *sp++ = '*';
  397.             *sp-- = '\0';
  398.             y = zxpand(atmbuf);
  399.             *sp = '\0';
  400.             if (y == 0) {
  401.             if (dsplon)
  402.             printf("\n?No files match - %s\n",atmbuf);
  403.             return(-2);
  404.             } else if (y < 0) {
  405.             if (dsplon)
  406.             printf("\n?Too many file match - %s\n",atmbuf);
  407.             return(-2);
  408.             } else {
  409.             printf(", one of the following:\n");
  410.             clrhlp();
  411.             for (i = 0; i < y; i++) {
  412.                 znext(filbuf);
  413.                 addhlp(filbuf);
  414.             }
  415.             dmphlp();
  416.             }
  417.         } else printf("\n");
  418.         printf("%s%s",cmprom,cmdbuf);
  419.         break;
  420.     }
  421.     x = getwd();
  422.     }
  423. }
  424.  
  425.  
  426.  
  427. /*  C H K W L D  --  Check for wildcard characters '*' or '?'  */
  428.  
  429. chkwld(s) char *s; {
  430.  
  431.     for ( ; *s != '\0'; s++) {
  432.         if ((*s == '*') || (*s == '?'))
  433.         return(1);
  434.     }
  435.     return(0);
  436. }
  437.  
  438. /*  C M F L D  --  Parse an arbitrary field  */
  439. /*
  440.  Returns
  441.    -3 if no input present when required,
  442.    -2 if field too big for buffer,
  443.    -1 if reparse needed,
  444.     0 otherwise, xp pointing to string result.
  445. */
  446. cmfld(xhlp,xdef,xp) char *xhlp, *xdef, **xp; {
  447.     int x, xc;
  448.  
  449.     cc = xc = 0;            /* Initialize counts & pointers */
  450.     *xp = "";
  451.     if ((x = cmflgs) != 1) {        /* Already confirmed? */
  452.     x = getwd();            /* No, get a word */
  453.     } else {
  454.     cc = setatm(xdef);        /* If so, use default, if any. */
  455.     }
  456.     *xp = atmbuf;            /* Point to result. */
  457.  
  458.     while (1) {
  459.     xc += cc;            /* Count the characters. */
  460.     debug(F111,"cmfld: getwd",atmbuf,xc);
  461.     debug(F101," x","",x);
  462.         switch (x) {
  463.         case -4:            /* EOF */
  464.         case -2:            /* Out of space. */
  465.         case -1:            /* Reparse needed */
  466.             return(x);
  467.         case 0:            /* SP or NL */
  468.         case 1:
  469.             if (xc == 0) *xp = xdef;    /* If no input, return default. */
  470.         else *xp = atmbuf;
  471.         if (**xp == NUL) x = -3;    /* If field empty, return -3. */
  472.         return(x);
  473.         case 2:            /* ESC *** (maybe treat as SP) */
  474.             if (xc == 0) {
  475.             printf("%s ",xdef);    /* If at beginning of field, */
  476.             addbuf(xdef);    /* supply default. */
  477.             cc = setatm(xdef);
  478.         } else {
  479.             putchar(BEL);    /* Beep if already into field. */
  480.                 }
  481.         break;
  482.         case 3:            /* Question mark */
  483.             if (*xhlp == NUL)
  484.             printf(" Please complete this field");
  485.         else
  486.                 printf(" %s",xhlp);
  487.         printf("\n%s%s",cmprom,cmdbuf);
  488.         break;
  489.     }
  490.     x = getwd();
  491.     }
  492. }
  493.  
  494. /*  C M T X T  --  Get a text string, including confirmation  */
  495.  
  496. /*
  497.   Print help message 'xhlp' if ? typed, supply default 'xdef' if null
  498.   string typed.  Returns
  499.  
  500.    -1 if reparse needed or buffer overflows.
  501.     1 otherwise.
  502.  
  503.   with cmflgs set to return code, and xp pointing to result string.
  504. */
  505.  
  506. cmtxt(xhlp,xdef,xp) char *xhlp; char *xdef; char **xp; {
  507.  
  508.     int x, xc;
  509.  
  510.     cc = xc = 0;            /* Start counters off at 0 */
  511.     *xp = "";                /* And pointer to null string. */
  512.     *atmbuf = NUL;            /* And empty atom buffer. */
  513.     if ((x = cmflgs) != 1) {
  514.     x = getwd();            /* Get first word. */
  515.     *xp = pp;            /* Save pointer to it. */ 
  516.  }
  517.     while (1) {
  518.     xc += cc;            /* Accumulate count. */
  519.     debug(F111,"cmtxt: getwd",atmbuf,xc);
  520.     switch (x) {
  521.         case -4:            /* EOF */
  522.         case -2:            /* Overflow */
  523.         case -1:            /* Deletion */
  524.             return(x);
  525.         case 0:            /* Space */
  526.         break;
  527.         case 1:            /* CR or LF */
  528.             if (xc == 0) *xp = xdef;
  529.         return(x);
  530.         case 2:            /* ESC */
  531.             if (xc == 0) {
  532.             printf("%s ",xdef);
  533.             cc = addbuf(xdef);
  534.         } else {
  535.             putchar(BEL);
  536.         }
  537.         break;
  538.         case 3:            /* Question Mark */
  539.             if (*xhlp == NUL)
  540.             printf(" Text string");
  541.         else
  542.             printf(" %s",xhlp);
  543.         printf("\n%s%s",cmprom,cmdbuf);
  544.         break;
  545.             default:
  546.             printf("\n?Unexpected return code from getwd() - %d\n",x);
  547.         return(-2);
  548.         }
  549.     x = getwd();
  550.     }
  551. }
  552.  
  553. /*  C M K E Y  --  Parse a keyword  */
  554.  
  555. /*
  556.  Call with:
  557.    table    --  keyword table, in 'struct keytab' format;
  558.    n        --  number of entries in table;
  559.    xhlp     --  pointer to help string;
  560.    xdef     --  pointer to default keyword;
  561.  
  562.  Returns:
  563.    -3       --  no input supplied and no default available
  564.    -2       --  input doesn't uniquely match a keyword in the table
  565.    -1       --  user deleted too much, command reparse required
  566.     n >= 0  --  value associated with keyword
  567. */
  568.  
  569. cmkey(table,n,xhlp,xdef) struct keytab table[]; int n; char *xhlp, *xdef; {
  570.     int i, y, z, zz, xc;
  571.     char *xp;
  572.  
  573.     xc = cc = 0;            /* Clear character counters. */
  574.  
  575.     if ((zz = cmflgs) == 1)         /* Command already entered? */
  576.     setatm(xdef);
  577.     else zz = getwd();
  578. debug(F101,"cmkey: table length","",n);
  579. debug(F101," cmflgs","",cmflgs);
  580. debug(F101," zz","",zz);
  581. while (1) {
  582.     xc += cc;
  583.     debug(F111,"cmkey: getwd",atmbuf,xc);
  584.  
  585.     switch(zz) {
  586.     case -4:            /* EOF */
  587.     case -2:            /* Buffer overflow */
  588.         case -1:            /* Or user did some deleting. */
  589.         return(zz);
  590.  
  591.     case 0:                /* User terminated word with space */
  592.     case 1:                /* or newline */
  593.         if (cc == 0) setatm(xdef);
  594.         y = lookup(table,atmbuf,n,&z);
  595.         switch (y) {
  596.         case -2:
  597.             printf("\n?Ambiguous - %s\n",atmbuf);
  598.             return(cmflgs = -2);
  599.         case -1:
  600.             printf("\n?Invalid - %s\n",atmbuf);
  601.             return(cmflgs = -2);
  602.         default:
  603.             break;
  604.         }
  605.         return(y);
  606.  
  607. /* cont'd... */
  608.  
  609. /* ...cmkey(), cont'd */
  610.  
  611.     case 2:                /* User terminated word with ESC */
  612.         if (cc == 0) {
  613.             if (*xdef != NUL) {    /* Nothing in atmbuf */
  614.             printf("%s ",xdef);    /* Supply default if any */
  615.             addbuf(xdef);
  616.             cc = setatm(xdef);
  617.             debug(F111,"cmkey: default",atmbuf,cc);
  618.         } else {
  619.             putchar(BEL);    /* No default, just beep */
  620.             break;
  621.         }
  622.         }
  623.         y = lookup(table,atmbuf,n,&z); /* Something in atmbuf */
  624.         debug(F111,"cmkey: esc",atmbuf,y);
  625.         if (y == -2) {
  626.         putchar(BEL);
  627.         break;
  628.             }
  629.         if (y == -1) {
  630.         printf("\n?Invalid - %s\n",atmbuf);
  631.         return(cmflgs = -2);
  632.         }
  633.         xp = table[z].kwd + cc;
  634.             printf("%s ",xp);
  635.         addbuf(xp);
  636.         debug(F110,"cmkey: addbuf",cmdbuf,0);
  637.         return(y);
  638.  
  639. /* cont'd... */
  640.  
  641. /* ...cmkey(), cont'd */
  642.  
  643.     case 3:                /* User terminated word with "?" */
  644.         y = lookup(table,atmbuf,n,&z);
  645.         if (y > -1) {
  646.         printf(" %s\n%s%s",table[z].kwd,cmprom,cmdbuf);
  647.         break;
  648.         } else if (y == -1) {
  649.         printf("\n?Invalid\n");
  650.         return(cmflgs = -2);
  651.         }
  652.  
  653.         if (*xhlp == NUL)
  654.             printf(" One of the following:\n");
  655.         else
  656.             printf(" %s, one of the following:\n",xhlp);
  657.  
  658.         clrhlp();
  659.         for (i = 0; i < n; i++) {
  660.         if (!strncmp(table[i].kwd,atmbuf,cc)
  661.                 && !test(table[i].flgs,CM_INV))
  662.             addhlp(table[i].kwd);
  663.         }
  664.         dmphlp();
  665.         printf("%s%s", cmprom, cmdbuf);
  666.         break;
  667.  
  668.         default:
  669.         printf("\n%d - Unexpected return code from getwd\n",zz);
  670.         return(cmflgs = -2);
  671.         }
  672.     zz = getwd();
  673.     }
  674. }
  675.  
  676. /*  C M C F M  --  Parse command confirmation (end of line)  */
  677.  
  678. /*
  679.  Returns
  680.    -2: User typed anything but whitespace or newline
  681.    -1: Reparse needed
  682.     0: Confirmation was received
  683. */
  684.  
  685. cmcfm() {
  686.     int x, xc;
  687.  
  688.     debug(F101,"cmcfm: cmflgs","",cmflgs);
  689.  
  690.     xc = cc = 0;
  691.     if (cmflgs == 1) return(0);
  692.  
  693.     while (1) {
  694.     x = getwd();
  695.     xc += cc;
  696.     debug(F111,"cmcfm: getwd",atmbuf,xc);
  697.         switch (x) {
  698.         case -4:            /* EOF */
  699.         case -2:
  700.         case -1:
  701.         return(x);
  702.  
  703.         case 0:            /* Space */
  704.             continue;
  705.         case 1:            /* End of line */
  706.             if (xc > 0) {
  707.             printf("?Not confirmed - %s\n",atmbuf);
  708.             return(-2);
  709.                 } else return(0);
  710.         case 2:
  711.             putchar(BEL);
  712.         continue;
  713.  
  714.             case 3:
  715.             if (xc > 0) {
  716.             printf("\n?Not confirmed - %s\n",atmbuf);
  717.             return(-2);
  718.         }
  719.             printf("\n Type a carriage return to confirm the command\n");
  720.         printf("%s%s",cmprom,cmdbuf);
  721.         continue;
  722.     }
  723.     }
  724. }
  725.  
  726. /* Keyword help routines */
  727.  
  728.  
  729. /*  C L R H L P -- Initialize/Clear the help line buffer  */
  730.  
  731. clrhlp() {                /* Clear the help buffer */
  732.     hlpbuf[0] = NUL;
  733.     hh = hx = 0;
  734. }
  735.  
  736.  
  737. /*  A D D H L P  --  Add a string to the help line buffer  */
  738.  
  739. addhlp(s) char *s; {            /* Add a word to the help buffer */
  740.     int j;
  741.  
  742.     hh++;                /* Count this column */
  743.  
  744.     for (j = 0; j < hc; j++) {        /* Fill the column */
  745.     if (*s != NUL)            /* First with chars from the string */
  746.         hlpbuf[hx++] = *s++;
  747.     else {
  748.         if (hh < (hw / hc))        /* Then with spaces */
  749.             hlpbuf[hx++] = SP;
  750.         else {
  751.         hlpbuf[hx++] = NUL;    /* If last column, no spaces. */
  752.         dmphlp();        /* Print it. */
  753.         return;
  754.         }
  755.         }
  756.     }
  757.     if (*s != NUL)            /* Still some chars left in string? */
  758.     hlpbuf[hx-1] = '+';        /* Mark as too long for column. */
  759. }
  760.  
  761.  
  762. /*  D M P H L P  --  Dump the help line buffer  */
  763.  
  764. dmphlp() {                /* Print the help buffer */
  765.     hlpbuf[hx++] = NUL;
  766.     printf(" %s\n",hlpbuf);
  767.     clrhlp();
  768. }
  769.  
  770. /*  L O O K U P  --  Lookup the string in the given array of strings  */
  771.  
  772. /*
  773.  Call this way:  v = lookup(table,word,n,&x);
  774.  
  775.    table - a 'struct keytab' table.
  776.    cmd   - the target string to look up in the table.
  777.    n     - the number of elements in the table.
  778.    x     - address of an integer for returning the table array index.
  779.  
  780.  The keyword table must be arranged in ascending alphabetical order, and
  781.  all letters must be lowercase.
  782.  
  783.  Returns the keyword's associated value ( zero or greater ) if found,
  784.  with the variable x set to the array index, or:
  785.  
  786.   -3 if nothing to look up (target was null),
  787.   -2 if ambiguous,
  788.   -1 if not found.
  789.  
  790.  A match is successful if the target matches a keyword exactly, or if
  791.  the target is a prefix of exactly one keyword.  It is ambiguous if the
  792.  target matches two or more keywords from the table.
  793. */
  794.  
  795. lookup(table,cmd,n,x) char *cmd; struct keytab table[]; int n, *x; {
  796.  
  797.     int i, v, cmdlen;
  798.  
  799. /* Lowercase & get length of target, if it's null return code -3. */
  800.  
  801.     if ((((cmdlen = lower(cmd))) == 0) || (n < 1)) return(-3);
  802.  
  803. /* Not null, look it up */
  804.  
  805.     for (i = 0; i < n-1; i++) {
  806.     if (!strcmp(table[i].kwd,cmd) ||
  807.            ((v = !strncmp(table[i].kwd,cmd,cmdlen)) &&
  808.              strncmp(table[i+1].kwd,cmd,cmdlen))) {
  809.         *x = i;
  810.         return(table[i].val);
  811.          }
  812.     if (v) return(-2);
  813.     }
  814.  
  815. /* Last (or only) element */
  816.  
  817.     if (!strncmp(table[n-1].kwd,cmd,cmdlen)) {
  818.     *x = n-1;
  819.     return(table[n-1].val);
  820.     } else return(-1);
  821. }
  822.  
  823. /*  G E T W D  --  Gets a "word" from the command input stream  */
  824.  
  825. /*
  826. Usage: retcode = getwd();
  827.  
  828. Returns:
  829.  -4 if end of file (e.g. pipe broken)
  830.  -2 if command buffer overflows
  831.  -1 if user did some deleting
  832.   0 if word terminates with SP or tab
  833.   1 if ... CR
  834.   2 if ... ESC
  835.   3 if ... ?
  836.  
  837. With:
  838.   pp pointing to beginning of word in buffer
  839.   bp pointing to after current position
  840.   atmbuf containing a copy of the word
  841.   cc containing the number of characters in the word copied to atmbuf
  842. */
  843. getwd() {
  844.  
  845.     int y = -1;                /* Used for variable expansion    */
  846.     int c;                /* Current char */
  847.     static int inword = 0;        /* Flag for start of word found */
  848.     int quote = 0;            /* Flag for quote character */
  849.     int echof = 0;            /* Flag for whether to echo */
  850.     int ignore = 0;        
  851.  
  852.     pp = np;                /* Start of current field */
  853.     debug(F101,"getwd: cmdbuf","",(int) cmdbuf);
  854.     debug(F101," bp","",(int) bp);
  855.     debug(F101," pp","",(int) pp);
  856.     debug(F110," cmdbuf",cmdbuf,0);
  857.  
  858.  
  859.     while (bp < cmdbuf+CMDBL) {        /* Loop */
  860.  
  861.     ignore = echof = 0;        /* Flag for whether to echo */
  862.  
  863.     if ((c = *bp) == NUL) {        /* Get next character */
  864.         if (dpx) echof = 1;        /* from reparse buffer */
  865.         c = getchar();        /* or from tty. */
  866.             debug(F101,"getwd: c","",(int) c);
  867.         if (c == EOF) return(-4);
  868.     } else ignore = 1;
  869.  
  870.     if (quote == 0) {
  871.  
  872.         if (!ignore && (c == '\\')) { /* Quote character */
  873.            quote = 1;
  874.            continue;
  875.             }
  876.         if (c == FF) {        /* Formfeed. */
  877.             c = NL;            /* Replace with newline */
  878.             printf("\377C\001\001\377EF");    /* and clear the screen. */
  879.         }
  880.  
  881.         if (c == HT) c = SP;     /* Substitute space for tab. */
  882.  
  883. /* cont'd... */
  884.  
  885. /* ...getwd(), cont'd */
  886.  
  887.             if (c == SP) {        /* If space */
  888.         *bp++ = c;        /* deposit it in buffer. */
  889.         if (echof) putchar(c);    /* echo it. */
  890.         if (inword == 0) {    /* If leading, gobble it. */
  891.             pp++;
  892.             continue;
  893.         } else {        /* If terminating, return. */
  894.             np = bp;
  895.             setatm(pp);
  896.             inword = 0; 
  897.                     debug(F110," getwd np",np,0);
  898.                     return(cmflgs = 0);
  899.         }
  900.         }
  901.         if (c == NL) {         /* CR, LF */
  902.         if (echof) putchar(c);    /* Echo the typein */
  903.         np = bp;        /* Where to start next field. */
  904.  
  905. /* code added to implement variables */
  906. /* this code is only used if the variable is the last item on a command */
  907. /* line.  in that case, we expand the variable and fool the parser into */
  908. /* thinking its not done with the line.  we have to manually add the CR */
  909. /* back into the cmdbuf so that the interactive parser won't wait for   */
  910. /* it to be entered from the keyboard.  np and bp are then adjusted by  */
  911. /* the additional characters generated by the variable expansion - thats*/
  912. /* how the parser is fooled.                            */
  913.  
  914.                if (expand_var) {
  915.         y = ckvar(pp);
  916.                 if (y >= 0) {               /* if var(s) present,    */
  917. /*         y = expvar(cmdbuf, CMDBL);    expand var(s)    */
  918.          y = expvar(pp, CMDBL);
  919.                  debug(F101," y","",y); 
  920.          y = ((int) pp - (int) cmdbuf) + y;
  921.                  cmdbuf[y] = '\n';    /* add the new line character */
  922.                  debug(F110," Variable expanded to",cmdbuf,0);
  923.                  expand_var = 0; 
  924.          echof = 0;
  925.                  debug(F101," pp","",(int) pp);
  926.                 }               
  927.           }
  928. /* to here */
  929.  
  930.         setatm(pp);    /* Copy this field to atom buffer.*/
  931.         inword = 0;
  932.                 if (y <= 0) {   /* no variable was found or it was null */
  933.             *bp = NUL;        /* End the string */
  934.             return(cmflgs = 1); /*end of line if no var found */
  935.         }
  936. /* new code for variables */
  937.         else {      
  938.                         np = pp + cc;    /* reset the buffer pointers */
  939.             bp = np;    
  940.             return(cmflgs = 0);  /* to continue parsing the line */
  941.         }           
  942. /* to here */
  943.  
  944.         }
  945.         if (!ignore && (c == '?')) { /* Question mark */
  946.         putchar(c);
  947.         *bp = NUL;
  948.         setatm(pp);
  949.         return(cmflgs = 3);
  950.             }
  951.         if (c == ESC) {         /* ESC */
  952.         *bp = NUL;
  953.         setatm(pp);
  954.         return(cmflgs = 2);
  955.         }
  956.         if (c == BS || c == RUB) {     /* Character deletion */
  957.         if (bp > cmdbuf) {    /* If still in buffer... */
  958.             printf("\b \b");    /* erase character from screen, */
  959.             bp--;        /* point behind it, */
  960.             if (*bp == SP) inword = 0; /* Flag if current field gone */
  961.             *bp = NUL;        /* Erase character from buffer. */
  962.         } else {        /* Otherwise, */
  963.             putchar(BEL);    /* beep, */
  964.             cmres();        /* and start parsing a new command. */
  965.         }
  966.         if (pp < bp) continue;
  967.         else return(cmflgs = -1);
  968.         }
  969.         if (c == LDEL) {         /* ^U, line deletion */
  970.             while ((bp--) > cmdbuf) {
  971.                 printf("\b \b");
  972.             *bp = NUL;
  973.         }
  974.         cmres();        /* Restart the command. */
  975.         inword = 0;
  976.         return(cmflgs = -2);
  977.         }
  978.  
  979. /* cont'd... */
  980.  
  981. /* ...getwd(), cont'd */
  982.  
  983.             if (c == WDEL) {         /* ^W, word deletion */
  984.             if (bp <= cmdbuf) {    /* Beep if nothing to delete */
  985.             putchar(BEL);
  986.             cmres();
  987.             return(cmflgs = -1);
  988.         }
  989.         bp--;
  990.         for ( ; (bp >= cmdbuf) && (*bp == SP) ; bp--) {
  991.             printf("\b \b");
  992.             *bp = NUL;
  993.         }
  994.         for ( ; (bp >= cmdbuf) && (*bp != SP) ; bp--) {
  995.             printf("\b \b");
  996.             *bp = NUL;
  997.         }
  998.         *bp++ = NUL;
  999.         inword = 0;
  1000.         return(cmflgs = -1);
  1001.         }
  1002.         if (c == RDIS) {         /* ^R, redisplay */
  1003.         *bp = NUL;
  1004.         printf("\n%s%s",cmprom,cmdbuf);
  1005.         continue;
  1006.         }
  1007.         }
  1008.     if (echof) putchar(c);        /* If tty input, echo. */
  1009.     inword = 1;            /* Flag we're in a word. */
  1010.     quote = 0;            /* Turn off quote. */
  1011.     *bp++ = c;            /* And deposit it. */
  1012.     }                    /* end of big while */
  1013.     putchar(BEL);            /* Get here if... */
  1014.     printf("\n?Buffer full\n");
  1015.     return(cmflgs = -2);
  1016. }
  1017.  
  1018. /* Utility functions */
  1019.  
  1020. /* A D D B U F  -- Add the string pointed to by cp to the command buffer  */
  1021.  
  1022. addbuf(cp) char *cp; {
  1023.     int len = 0;
  1024.     while ((*cp != NUL) && (bp < cmdbuf+CMDBL)) {
  1025.         *bp++ = *cp++;            /* Copy and */
  1026.         len++;                /* count the characters. */
  1027.     }
  1028.     *bp++ = SP;                /* Put a space at the end */
  1029.     *bp = NUL;                /* Terminate with a null */
  1030.     np = bp;                /* Update the next-field pointer */
  1031.     return(len);            /* Return the length */
  1032. }
  1033.  
  1034. /*  S E T A T M  --  Deposit a string in the atom buffer  */
  1035.  
  1036. setatm(cp) char *cp; {
  1037.     int y;
  1038.     char *ap;
  1039.     cc = 0;
  1040.     ap = atmbuf;
  1041.     *ap = NUL;
  1042.  
  1043. /* new code to handle variables */
  1044.     if (expand_var) {
  1045.     y = ckvar(cp);   
  1046.     if (y >= 0) {
  1047.             y = expvar(cmdbuf, CMDBL); /* expand w/o mod to cmdbuf pointer */
  1048.             debug(F111,"setatm: expvar",cmdbuf,y);
  1049.         }
  1050.     }
  1051. /* to here */
  1052.  
  1053.     while (*cp == SP) cp++;
  1054.     while ((*cp != SP) && (*cp != NL) && (*cp != NUL)) {
  1055.     *ap++ = *cp++;
  1056.     cc++;
  1057.     } 
  1058.   *ap++ = NUL;
  1059.   if (y >= 0) {   /* if variable expanded, adjust cmdbuf pointers */
  1060.     np = pp + cc;
  1061.     bp = np;                                                        
  1062.   }
  1063.  return(cc);                /* Return length */
  1064. }
  1065. /*  D I G I T S  -- Verify that all the characters in line are digits  */
  1066.  
  1067. digits(s) char *s; {
  1068.     while (*s) {
  1069.         if (!isdigit(*s)) return(0);
  1070.         s++;
  1071.     }
  1072.     return(1);
  1073. }
  1074.  
  1075. /*  L O W E R  --  Lowercase a string  */
  1076.  
  1077. lower(s) char *s; {
  1078.     int n = 0;
  1079.     while (*s) {
  1080.     if (isupper(*s)) *s = tolower(*s);
  1081.     s++, n++;
  1082.     }
  1083.     return(n);
  1084. }
  1085.  
  1086. /*  T E S T  --  Bit test  */
  1087.  
  1088. test(x,m) int x, m; { /*  Returns 1 if any bits from m are on in x, else 0  */
  1089.     return((x & m) ? 1 : 0);
  1090. }
  1091.  
  1092. /* P U S H C M D -- Put remainder of command line into SAVBUF */
  1093.  
  1094. pushcmd(s) char *s; {               
  1095.   int x = 0;
  1096.  
  1097.     strcpy(savbuf,s);   /*  push the command     */ 
  1098.     while (savbuf[x] != '\0') x++;
  1099.     savbuf[x++] = '\n';  /* replace null with CR to make valid cmd */       
  1100.     debug(F110," pushcmd savbuf",savbuf,0);
  1101.     cmres();              /* and clear the buffer */
  1102. }
  1103.  
  1104. /* E X P V A R -- Expand variable(s) in pointed string */
  1105.  
  1106. expvar(string, max_size) char *string; int max_size; {
  1107.     char work_str[CMDBL+4];
  1108.     int g_index;
  1109.     char *g_vars_ptr;
  1110.     int work_index = 0;
  1111.     int str_index = 0;
  1112.  
  1113.     while (string[str_index] && work_index < (max_size-1)) {
  1114.  
  1115.     if (!strncmp(&string[str_index], "\\%", 2) &&
  1116.         isalpha(string[str_index+2])) {
  1117.         g_index = ckvar(&string[str_index]);
  1118.  
  1119.         if (g_index >= 0 && expand_var) {
  1120.         str_index += 3;
  1121.         g_vars_ptr = g_vars[g_index];
  1122.         while (*g_vars_ptr && work_index < (max_size-1))
  1123.             work_str[work_index++] = *g_vars_ptr++;   
  1124.          }
  1125.         else if (g_index >= 0) {
  1126.         work_str[work_index++] = string[str_index++];
  1127.         expand_var = 1;
  1128.         }
  1129.     }
  1130.     else
  1131.         work_str[work_index++] = string[str_index++];
  1132.     }
  1133.     if (strlen(string) > 0 && work_index > 0) {
  1134.     if (work_index > max_size)
  1135.         work_index = max_size;
  1136.     strncpy(string, work_str, work_index);
  1137.     string[work_index] = NUL;
  1138.     }
  1139.     return(work_index);
  1140. }
  1141.  
  1142. /* C K V A R -- Determine if pointed to string is a variable name */
  1143.  
  1144. ckvar(string) char *string; {
  1145.     int i;
  1146.     char c, *buf;
  1147.                        
  1148.     buf = string;
  1149. /*    while (*buf != '\\' && *buf) buf++; skip to var, if there */
  1150.     if (*buf == '\\') buf++;
  1151.     else return(-3);
  1152.     if (*buf == '%') buf++;
  1153.     else return(-2);
  1154.     c = *buf;
  1155.     if (islower(c)) c = toupper(c);
  1156.     if (!isalpha(c)) return(-1);
  1157.     i = (int) (c - 'A'); 
  1158.     return(i);                   /* return the global var index */
  1159. }
  1160.  
  1161. /* S A V V A R -- Save variable value to a global variable buffer from
  1162.           command string passed */
  1163.  
  1164. savvar(index, string) int index; char *string; {
  1165.     int i;
  1166.     char *str_tmp;
  1167.  
  1168.     str_tmp = string;
  1169.                             /* SKIP TO:    */
  1170.     while (*str_tmp &&  isspace(*str_tmp)) str_tmp++;    /* ldng. space    */
  1171.     while (*str_tmp && !isspace(*str_tmp)) str_tmp++;    /* first space    */
  1172.     while (*str_tmp &&  isspace(*str_tmp)) str_tmp++;    /* second atom    */
  1173.     while (*str_tmp && !isspace(*str_tmp)) str_tmp++;    /* second space    */
  1174.     while (*str_tmp &&  isspace(*str_tmp)) str_tmp++;    /* third atom    */
  1175.  
  1176.     if ((i = strlen(str_tmp)) >= MAXVARL)
  1177.     i = MAXVARL - 1;
  1178.     if (i > 0) strncpy(g_vars[index], str_tmp, i);
  1179.     g_vars[index][i] = '\0';
  1180.     return(0);
  1181. }
  1182.  
  1183. /* C L R K B D -- Clear the keyboard buffer of any residual characters */
  1184.  
  1185. clrkbd() {
  1186.   int c;
  1187.   int erc;
  1188.  
  1189.      c = getchar();        /* get 1st char    */
  1190.      debug(F101,"clrkbd: character","",(int) c);
  1191.      while (c != 10 && c != 0) {                    
  1192.            c = getchar();
  1193.      }
  1194.      return(0);
  1195. }
  1196.