home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / ccmd / cmswi.c < prev    next >
Encoding:
C/C++ Source or Header  |  1988-08-19  |  14.6 KB  |  401 lines

  1. /*
  2.  Author: Andrew Lowry
  3.  
  4.  Columbia University Center for Computing Activities, July 1986.
  5.  Copyright (C) 1986, 1987, Trustees of Columbia University in the City
  6.  of New York.  Permission is granted to any individual or institution
  7.  to use, copy, or redistribute this software so long as it is not sold
  8.  for profit, provided this copyright notice is retained.
  9. */
  10. /* cmswi
  11. **
  12. ** Code to parse switches.  Parsing succeeds if current input
  13. ** uniquely matches a non-ignored switch in the supplied switch
  14. ** table.  Global variable 'cmswbeg' contains the character that
  15. ** must precede the switch text, and global variable 'cmswend'
  16. ** contains a character that may be used to terminate a switch.
  17. ** The initial values for these variables are "/" for cmswbeg, and
  18. ** ":" for cmswend.
  19. **
  20. ** A switch must begin with the character in cmswbeg.  Subsequent
  21. ** text up to but not including the cmswend character or a break
  22. ** character (according to the break table in effect) is then matched
  23. ** against the supplied switch table.  If a unique match results,
  24. ** the parse value is the _swval field of the matching switch.
  25. ** If the switch was terminated by the cmswend character, that
  26. ** character is consumed by the parse, and the CM_SWT flag is set
  27. ** in the CSB.  Otherwise, the flag is cleared, and the delimiter
  28. ** character is not consumed by the parse.  The cmswend character is
  29. ** allowed, however, only on switches for which the character actually 
  30. ** appears in the switch table.
  31. **
  32. ** For completion, if the current input uniquely matches a single switch,
  33. ** that switch is completed, with the cmswend character if it is present
  34. ** on the switch in the switch table.  Partial completion completes
  35. ** only up to punctuation.  Full completion adds a space and wakeup
  36. ** as well.
  37. **
  38. ** Standard help prints a list of all visible switches that might match
  39. ** the current input.  The cmswbeg character is prepended to each switch
  40. ** in the listing, and the cmswend character will appear on those switches
  41. ** in which it appears in the supplied switch table.
  42. **
  43. ** The break table specifies what characters are allowed in the interior
  44. ** of switches (that is, everything except the cmswbeg character at the
  45. ** beginning and the cmswend character, if present, at the end).  Note
  46. ** that any white space following the cmswbeg character is considered
  47. ** part of the switch text, so whitespace will generally kill the parse.
  48. **/
  49.  
  50. #define    SWIERR                /* switch error tbl allocated here */
  51.  
  52. #include "ccmdlib.h"            /* get standard symbols */
  53. #include "cmfncs.h"        /* and internal symbols */
  54.  
  55. /* Forward declaration of handler routines */
  56.  
  57. int swiprs(), swihlp(), swicplt();
  58.         
  59. static brktab swibrk = {                /* standard break table */
  60.   {                    /* 1st char break array */
  61.                     /* all but letters, digits, hyphen */
  62.     0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f, 
  63.     0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
  64.   },
  65.   {                    /* subsequent char break array */
  66.                     /* same as above */
  67.     0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f, 
  68.     0x80, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x1f
  69.   }
  70. };
  71.  
  72. ftspec ft_swi = { swiprs, swihlp, swicplt, 0, &swibrk }; /* handler structure */
  73.  
  74. char cmswbeg = '/';        /* standard punctuation to start switch */
  75. char cmswend = ':';        /* standard switch terminator */
  76.  
  77.  
  78.  
  79.  
  80. /* swiprs - Check that the first character in the input matches the cmswbeg
  81. ** character.  Then find matching switches, and succeed if there is only
  82. ** one.
  83. **/
  84.  
  85. PASSEDSTATIC int
  86. swiprs(text,textlen,fdbp,parselen,value)
  87. char *text;
  88. int textlen,*parselen;
  89. fdb *fdbp;
  90. pval *value;
  91. {
  92.   int mcount;                /* number of matches */
  93.   swtch *mat;                /* the matching switch */
  94.   char *term;                /* char terminating the switch */
  95.   switab *st;                /* the switch table */
  96.   
  97.   if (textlen == 0)
  98.     return(CMxINC);            /* nothing to parse */
  99.   if ((*text & CC_CHR) != cmswbeg) 
  100.     return(SWIxBEG);            /* beginning char doesn't match */
  101.   text++;                /* skip past opening char */
  102.   textlen--;
  103.  
  104.   mcount = match(text,textlen,fdbp,&mat,&term);    /* find matching switches */
  105.   if (mcount == 0)
  106.     return(SWIxNM);            /* no match */
  107.   else if (term == NULL)        /* not terminated? */
  108.     return(CMxINC);            /* ambiguous parse */
  109.   else if (mcount > 1)
  110.     return(SWIxAMB);            /* ambiguous */
  111.  
  112.   st = (switab *) fdbp->_cmdat;        /* unique - get table address */
  113.   while ((mat)->_swflg & SWI_ABR)    /* track down abbreviations */
  114.     if (((mat)->_swval >= st->_stcnt) || /* abbrev ptr out of bounds? */
  115.     ((mat)->_swval < 0)
  116.        )
  117.       return(SWIxABR);            /* bad abbrev chain */
  118.     else
  119.       mat = &(st->_stwds[mat->_swval]);    /* move down chain */
  120.  
  121.   if ((*term & CC_CHR) == cmswend) {    /* terminated with cmswend? */
  122.     if (mat->_swswi[strlen(mat->_swswi)-1] != cmswend) /* but not in tbl */
  123.       return(SWIxEND);            /* not allowed */
  124.     term++;                /* consume special terminator */
  125.     cmcsb._cmflg |= CM_SWT;        /* and flag that it was present */
  126.   }
  127.   else
  128.     cmcsb._cmflg &= ~CM_SWT;        /* else clear the flag */
  129.  
  130.   *parselen = (term - text) + 1;    /* set # of consumed chars */
  131.                     /*  (including opening punctuation) */
  132.   value->_pvkey = mat->_swval;        /* set return value */
  133.   return(CMxOK);            /* and return success */
  134. }
  135.  
  136.  
  137.  
  138. /* swicplt - If the current input is ambiguous, we beep.  Otherwise
  139. ** we complete with the remainder of the identified switch.  In the
  140. ** latter case, full completion adds a space and a wakeup, and partial
  141. ** completion stops at punctuation.  If the cmswend character is present
  142. ** in the identified switch, it is included in the completion text.
  143. **/
  144.  
  145. PASSEDSTATIC int
  146. swicplt(text,textlen,fdbp,full,cplt,cpltlen)
  147. char *text,**cplt;
  148. int textlen,full,*cpltlen;
  149. fdb *fdbp;
  150. {
  151.   int mcount;                /* number of matches */
  152.   swtch *mat;                /* the matching switch */
  153.   char *term;                /* char terminating the switch */
  154.   switab *st;                /* the switch table */
  155.   char lastchar;            /* last char in completion text */
  156.  
  157.   *cplt = NULL;                /* assume empty completion */
  158.  
  159.   if (textlen <= 1)            /* no input (besides opening char) */
  160.     return(CMP_BEL);            /* so just beep */
  161.  
  162.   text++; textlen--;            /* skip past opening char */
  163.   mcount = match(text,textlen,fdbp,&mat,&term); /* find matches */
  164.   if (mcount > 1)            /* ambiguous */
  165.     return(CMP_BEL);            /* so just beep */
  166.   else {
  167.     st = (switab *) fdbp->_cmdat;    /* unique - get table address */
  168.     while ((mat)->_swflg & SWI_ABR)    /* track down abbreviations */
  169.       mat = &(st->_stwds[mat->_swval]);    /* move down chain */
  170.  
  171.     *cplt = mat->_swswi+textlen;    /* point to completion text */
  172.     *cpltlen = strlen(mat->_swswi)-textlen; /* get length of remainder */
  173.     if (*cpltlen < 0) {            /* can happen on weird abbrev */
  174.       *cplt = NULL;            /* no completion in that case */
  175.       lastchar = NULCHAR;
  176.     }
  177.     else
  178.       lastchar = (*cplt)[(*cpltlen)-1];    /* else get last completion char */
  179.     if (full)                /* full completion? */
  180.       if (lastchar == cmswend)        /* cplt contains  special char? */
  181.     return(CMP_GO);            /* then no space but wakeup */
  182.       else
  183.         return(CMP_SPC | CMP_GO);    /* otherwise add space and wakeup */
  184.     else
  185.       return(CMP_PNC);            /* partial stops after punctuation */
  186.   }
  187. }
  188.  
  189.  
  190.  
  191. /* swihlp - Construct a set of matching switches for the current input,
  192. ** then print them in tabular form.  Do not print invisible switches.
  193. ** If no switches match, print a special indication.  The help message
  194. ** starts with "switch, one of the following:".  If custom help has
  195. ** been given, "switch, " is left off of this string.
  196. **/
  197.  
  198. PASSEDSTATIC int
  199. swihlp(text,textlen,fdbp,cust)
  200. char *text;
  201. int textlen,cust;
  202. fdb *fdbp;
  203. {
  204.   int mcount;                /* number of matches */
  205.   swtch *mat;                /* a matching switch */
  206.   char *term;                /* ptr to input terminator */
  207.   switab *st;                /* switch table */
  208.   int i,j;
  209.   int swtlen;                /* lengths of individual switchs */
  210.   int cols;                /* # of switches printed per line */
  211.   int curcol;                /* current table column */
  212.   int maxlen = 0;            /* maximum switch length */
  213.  
  214.   if (!cust)
  215.     cmxputs("switch, ");        /* start of msg with no custom help */
  216.   cmxputs("one of the following:");    /* remainder of the first line */
  217.       
  218.   if (textlen == 0)            /* no input? */
  219.     mcount = match(text,0,fdbp,&mat,&term); /* then all will match */
  220.   else
  221.     mcount = match(text+1,textlen-1,fdbp,&mat,&term); /* find matches */
  222.   if (mcount == 0) {            /* no match */
  223.     cmxputs(" (No switches match current input)"); /* indicate void */
  224.     return(CMxOK);
  225.   }
  226.   st = (switab *) fdbp->_cmdat;        /* get switch table */
  227.   mat = st->_stwds;            /* point to first switch */
  228.   for (i = 0; i < st->_stcnt; i++) {    /* first pass to find longest swt */
  229.     if ((mat->_swflg & SWI_MAT) &&     /* matching switch... */
  230.         ((mat->_swflg & SWI_INV) == 0)    /* and not invisible */
  231.        ) {
  232.       swtlen = strlen(mat->_swswi);    /* get switch length  */
  233.       if (swtlen > maxlen)
  234.     maxlen = swtlen;        /* update longest size */
  235.     }
  236.     mat++;                /* and move to next switch */
  237.   }
  238.   maxlen += 4;                /* adjust for prefix and */
  239.                     /*  column separation */
  240.  
  241.   /* Following calculation goes as follows:
  242.   ** Let w be the screen width.  Naively, we would calculate that
  243.   ** we could list floor(w/maxlen) switches per line, each taking up
  244.   ** maxlen columns on the screen.  But since each line is indented
  245.   ** two spaces, we should subtract 2 from the effective screen width.
  246.   ** Then we add 3 to the effective width because we will not need to
  247.   ** print three spaces after the last switch in a line.  So the
  248.   ** effective screen width is w-2+3 = w+1 = cmcsb._cmcmx+2, since
  249.   ** cmcsb._cmcmx is one less than the screen width.  So we want
  250.   ** floor((cmcsb._cmcmx+2)/maxlen) switches per line.
  251.   **/
  252.  
  253.   /* XXX see my comments in cmkey.c about this -- chris */
  254.  
  255.   cols = (cmcsb._cmcmx+2) / maxlen;    /* number of columns per line */
  256.  
  257.   mat = st->_stwds;            /* point to first switch */
  258.   curcol = 0;                /* currently printing first column */
  259.   for (i = 0; i < st->_stcnt; i++) {    /* second pass to print matches */
  260.     if ((mat->_swflg & SWI_MAT) &&    /* matching switch? */
  261.     ((mat->_swflg & SWI_INV) == 0)    /* and visible? */
  262.        ) {
  263.       if (curcol == 0) {
  264.     cmxnl();            /* new line for first column */
  265.     cmxputs(" ");            /* and offset a bit */
  266.       }
  267.       cmxputc(cmswbeg);            /* print opening punctuation */
  268.       cmxputs(mat->_swswi);        /* print the switch */
  269.       if (curcol < (cols-1))        /* space out if not last column */
  270.     for (j = strlen(mat->_swswi); j < maxlen; j++)
  271.        cmxputc(SPACE);
  272.       curcol = (curcol+1) % cols;    /* and move to next column */
  273.     }
  274.     mat++;                /* move to next switch */
  275.   }
  276.   return(CMxOK);            /* all done */
  277. }
  278.  
  279.  
  280.  
  281. /* match - Auxiliary routine used by switch handlers.
  282. **
  283. ** Purpose:
  284. **   Step through all the switches in a table, and set their SWI_MAT
  285. **   flags according to whether or not they match a given input string.
  286. **   Returns the number of matching switches, and if only one switch
  287. **   matches, a pointer to that switch.  If more than one switch
  288. **   matches, but one switch matches exactly, it is returned as if
  289. **   it were the only matching switch.  SWI_INV, SWI_NOR and SWI_ABR
  290. **   flags do not affect this operation, except that an exact match
  291. **   to a SWI_NOR flagged switch is not considered an exact match.
  292. **   If an individual switch is terminated by the cmswend character,
  293. **   that character is considered not present for matching purposes.
  294. **
  295. ** Input arguments:
  296. **   text - A pointer to the first input character.
  297. **   textlen - The number of input characters.
  298. **   fdbp - A pointer to the FDB pointing to the switch table
  299. **     and break table to be used.
  300. ** 
  301. ** Output arguments:
  302. **   mat - A pointer to the matching switch, if exactly one switch
  303. **     matched or if one of the matching switches was an exact match.
  304. **   term - A pointer to the first character following the input that
  305. **     was used to match switches, if any characters were left after
  306. **     that input.  Otherwise, NULL is returned here.  (If the cmswend
  307. **     character follows the switch, term will be left pointing at it.)
  308. **
  309. ** Returns: The number of matching switches (or 1 for an exact match).
  310. **/
  311.  
  312. static int
  313. match(text,textlen,fdbp,mat,term)
  314. char *text,**term;
  315. int textlen;
  316. fdb *fdbp;
  317. swtch **mat;
  318. {
  319.   int mcount = 0;            /* number of matches seen */
  320.   int inlen;                /* # of chars to match in input */
  321.   int len;                /* size of switch without cmswend */
  322.   int i;
  323.   int exact = FALSE;            /* true if exact match occurs */
  324.   swtch *swis;                /* for stepping through table */
  325.   brktab *btab;                /* break table to use */
  326.   switab *st;                /* switch table to search */
  327.   
  328.   if ((btab = fdbp->_cmbrk) == NULL)    /* get supplied break table */
  329.     btab = &swibrk;            /* or use default */
  330.  
  331.   for (inlen = 0; inlen < textlen; inlen++) /* find # of usable chars */
  332.     if (((text[inlen] & CC_CHR) == cmswend) || /* stop at special end char */
  333.         BREAK(btab,text[inlen],inlen)    /* or first break char */
  334.        )
  335.       break;
  336.   if (inlen == textlen)            /* no break char? */
  337.     *term = NULL;            /* then set no terminator */
  338.   else
  339.     *term = text+inlen;            /* else point to it for caller */
  340.   
  341.   st = (switab *) fdbp->_cmdat;        /* point to switch table */
  342.   swis = st->_stwds;            /* point to first switch */
  343.   for (i = 0; i < st->_stcnt; i++) {    /* step through table */
  344.     if (match1(swis->_swswi,text,inlen)) {
  345.       swis->_swflg |= SWI_MAT;        /* this switch matches */
  346.       if (!exact) {            /* if no prior exact match */
  347.     *mat = swis;            /* then save pointer to return */
  348.         mcount++;            /* and count it */
  349.       }
  350.       len = strlen(swis->_swswi);    /* get length of matching switch */
  351.       if (swis->_swswi[len-1] == cmswend) /* but don't count ending punct */
  352.     len--;
  353.       if (len == inlen)
  354.         if ((swis->_swflg & SWI_NOR) == 0) { /* and not ignored? */
  355.       exact = TRUE;            /* flag this exact match */
  356.       mcount = 1;            /* only one match now */
  357.         }
  358.     }
  359.     else
  360.       swis->_swflg &= ~SWI_MAT;        /* no match -- turn off flag */
  361.  
  362.     swis++;                /* move on to next switch */
  363.   }
  364.   return(mcount);            /* give back # of matches */
  365. }
  366.  
  367.  
  368.  
  369. /* match1 - Auxiliary routine for match 
  370. **
  371. ** Purpose:
  372. **   Decides whether or not two strings match up to a given number of
  373. **   characters.  Case of letters is ignored in the comparison.
  374. **
  375. ** Input arguments:
  376. **   s1, s2 - Pointers to the strings to be compared.
  377. **   slen - Number of characters to compare.
  378. **
  379. ** Output arguments: None.
  380. ** Returns: TRUE for a match, FALSE otherwise.
  381. **/
  382.  
  383. static int
  384. match1(s1,s2,slen)
  385. char *s1,*s2;
  386. int slen;
  387. {
  388.   char c1,c2;            /* individual chars to compare */
  389.   while (slen-- > 0) {        /* step through strings */
  390.     c1 = (*s1++) & CC_CHR;        /* pick up next pair of chars */
  391.     c2 = (*s2++) & CC_CHR;
  392.     if ((c1 >= 'a') && (c1 <= 'z'))
  393.       c1 -= 'a'-'A';        /* upper case first char */
  394.     if ((c2 >= 'a') && (c2 <= 'z'))
  395.       c2 -= 'a'-'A';        /* upper case second char */
  396.     if (c1 != c2)
  397.       return(FALSE);        /* mismatch */
  398.   }
  399.   return(TRUE);            /* all chars matched */
  400. }
  401.