home *** CD-ROM | disk | FTP | other *** search
/ Columbia Kermit / kermit.zip / old / ckermit4f / ckucmd.c < prev    next >
C/C++ Source or Header  |  2020-01-01  |  42KB  |  1,295 lines

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