home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / mm / ccmd / ccmd.c < prev    next >
Encoding:
C/C++ Source or Header  |  1990-12-18  |  40.3 KB  |  1,315 lines

  1. /*
  2.  Copyright (c) 1986, 1990 by The Trustees of Columbia University in
  3.  the City of New York.  Permission is granted to any individual or
  4.  institution to use, copy, or redistribute this software so long as it
  5.  is not sold for profit, provided this copyright notice is retained.
  6.  
  7.  Author: Andrew Lowry
  8. */
  9. /* ccmd.c
  10. **
  11. ** Primary module of the ccmd package.  Following routines are accessible
  12. ** to user programs:
  13. **
  14. **/
  15.  
  16. #include "ccmdlib.h"        /* get standard package symbols */
  17. #include "cmfncs.h"        /* and internal symbols */
  18.  
  19. extern int (*(stdact[]))();    /* standard action table */
  20. extern int (*(preact[]))();    /* standard action table */
  21.  
  22. csb cmcsb = {            /* CSB for all parsing */
  23.     0,            /* all flags initially off */
  24.     0,            /* second flag word too. */
  25.     stdin,            /* input from console */
  26.     stdout,            /* output to console */
  27.     stderr,            /* error output */
  28.     NULL,            /* no prompt set yet */
  29.     NULL,            /* no command line buffer yet */
  30.     NULL,                /* no characters to parse */
  31.     0,            /* size of nonexistent command buffer */
  32.     0,            /* no unparsed characters */
  33.     NULL,            /* no history point */
  34.     NULL,            /* no atom buffer yet */
  35.     0,            /* size of nonexistent atom buffer */
  36.     NULL,            /* no work buffer yet */
  37.     0,            /* size of nonexistent work buffer */
  38.     stdact,            /* set up standard action table */
  39.     NULCHAR,        /* no saved break character */
  40.     -1,            /* maximum column position unknown */
  41.     0,            /* current column position */
  42.     CMxOK,            /* last parse error */
  43.     NULL,            /* no FDB giving incomplete parse */
  44.     NULL,            /* no reparse handler */
  45.     NULL,            /* no error handler */
  46.     ";",            /* comment to eol */
  47.     "!",            /* start delimited comment */
  48.     "!",            /* end delimited comment */
  49.     1000,            /* max help tokens */
  50.     NULL,            /* nonblocking I/O handler */
  51.     -1,            /* wrap column */
  52.     NULL,            /* command history */
  53.     -1,            /* max rows */
  54.     NULL,            /* no current position yet */
  55.     preact,            /* prefix actions */
  56. };
  57.  
  58.  
  59.  
  60. /* cmbufs
  61. **
  62. ** Purpose:
  63. **   Declare buffers for use in command parsing.  This routine must
  64. **   be invoked once before any parsing may be performed.  Thereafter,
  65. **   only if one or more buffers need to be changed.  
  66. **
  67. **   Three buffers are declared.  The "command buffer" is used to
  68. **   collect user input during the parse.  The "atom buffer" is used
  69. **   to hold the text from a single parsed field.  Each time a parse
  70. **   succeeds, the characters consumed by the parse are copied into
  71. **   the atom buffer.  The "work buffer" is used internally by the
  72. **   ccmd package to hold text to be passed to the individual field
  73. **   parsers.  The user program should generally never touch the work
  74. **   buffer for any reason.
  75. **
  76. **   If any of the buffers overflows during parsing, an error is
  77. **   signaled to the caller.  To prevent atom buffer and work buffer
  78. **   overflow, they should be made as large as the command buffer.
  79. **   Generally, however, these two auxiliary buffers can be smaller,
  80. **   especially the atom buffer, unless individual fields are likely
  81. **   to be large.
  82. **
  83. ** Input arguments:
  84. **   cmdbuf - A pointer to an int buffer to be used to hold
  85. **     the command line during parsing.  Each character is stored
  86. **     with a flag byte in the left half of its int entry.
  87. **   cmdlen - The total number of characters available in the command
  88. **     line buffer.
  89. **   atombuf - A pointer to an auxiliary "atom buffer" to hold the text
  90. **     comprising individual fields during the parse.  Some parsing
  91. **     functions return string results via the atom buffer.
  92. **   atomlen - The total number of characters available in the atom buffer.
  93. **   workbuf - A pointer to an auxiliary "work buffer" used internally by
  94. **     the parsing routines.
  95. **   worklen - The total number of characters available in the work
  96. **     buffer.
  97. **
  98. ** Output arguments: None.
  99. **
  100. ** Returns: CMxOK always
  101. **
  102. ** As a side effect, the first time this is called, the CCMD environment
  103. ** variable is read and interpreted.  This doesn't really belong here, but
  104. ** there is no other init routine which must get called first...
  105. **/
  106.  
  107. int
  108. cmbufs(cmdbuf,cmdlen,atombuf,atomlen,workbuf,worklen)
  109. int *cmdbuf;
  110. char *atombuf,*workbuf;
  111. int cmdlen,atomlen,worklen;
  112. {
  113.   static int firsttime = TRUE;
  114.  
  115.   if (firsttime) {
  116.       firsttime = FALSE;
  117.       cmgetenv();
  118.   }
  119.  
  120.   cmcsb._cmbfp = cmdbuf;    /* set up buffer attributes in CSB */
  121.   cmcsb._cmcnt = cmdlen;
  122.   cmcsb._cmabp = atombuf;
  123.   cmcsb._cmabc = atomlen;
  124.   cmcsb._cmwbp = workbuf;
  125.   cmcsb._cmwbc = worklen;
  126.   
  127.   cmcsb._cmcur = cmcsb._cmptr = cmdbuf;    /* no parsed characters in buffer */
  128.   cmcsb._cminc = 0;        /* and no unparsed characters either */
  129.   cmcsb._cmhst = cmdbuf;    /* prevent history attempts */
  130.   return(CMxOK);
  131. }
  132.  
  133.  
  134. /*
  135. ** cmraise, cmwake, cmecho
  136. **
  137. ** Purpose:
  138. **   Set or clear flags in the CSB.  Cmraise controls the CM_RAI flag,
  139. **   which determines whether or not characters are automatically converted
  140. **   to upper case on input.  Cmwake controls the CM_WKF flag, which controls
  141. **   whether or not a parsing "wakeup" occurs for non-action break characters
  142. **   that are encountered in the input.  Cmecho controls the CM_NEC flag,
  143. **   which determines whether or not input characters are echoed back to
  144. **   the user.
  145. **
  146. ** Input arguments:
  147. **   flag - TRUE to turn on an action, FALSE to turn it off.
  148. **
  149. ** Output arguments: None
  150. ** Returns: CMxOK always
  151. **/
  152.  
  153. int
  154. cmraise(flag)
  155. int flag;
  156. {
  157.   if (flag)
  158.     cmcsb._cmflg |= CM_RAI;    /* set the flag */
  159.   else
  160.     cmcsb._cmflg &= ~CM_RAI;    /* or clear it */
  161.   return(CMxOK);
  162. }
  163.  
  164. int
  165. cmwake(flag)
  166. int flag;
  167. {
  168.   if (flag)
  169.     cmcsb._cmflg |= CM_WKF;    /* set the flag */
  170.   else
  171.     cmcsb._cmflg &= ~CM_WKF;    /* or clear it */
  172.   return(CMxOK);
  173. }
  174.  
  175. int
  176. cmecho(flag)
  177. int flag;
  178. {
  179.   if (flag)
  180.     cmcsb._cmflg &= ~CM_NEC;    /* clear "no echo" flag */
  181.   else
  182.     cmcsb._cmflg |= CM_NEC;    /* or set it */
  183. }
  184.  
  185.  
  186.  
  187. /* cmact
  188. **
  189. ** Purpose:
  190. **   Install a user-specified action character table.  When a break
  191. **   character is encountered in the input, this table is examined,
  192. **   indexed by the character's ASCII code.  If the entry is non-NULL,
  193. **   it points to a function that will be invoked to perform an action
  194. **   on behalf of that character.
  195. **
  196. ** Input arguments:
  197. **   acttab - Pointer to the beginning of the action table, which
  198. **     should be an array of pointers to integer functions.  If
  199. **     NULL is passed, the standard action table will be installed.
  200. **
  201. ** Output arguments: None.
  202. ** Returns: CMxOK always.
  203. **/
  204.  
  205. int
  206. cmact(acttab)
  207. int (**acttab)();
  208. {
  209.   if (acttab == NULL)
  210.     cmcsb._cmact = stdact;        /* install standard table */
  211.   else
  212.     cmcsb._cmact = acttab;        /* or caller's table */
  213.   return(CMxOK);
  214. }
  215.  
  216.  
  217.  
  218. /* cmseti
  219. **
  220. ** Purpose:
  221. **   Set the input stream for command parsing.  If NULL is passed, 
  222. **   subsequent parsing will be taken from the console.  If the
  223. **   stream is associated with a terminal device, echoing and other
  224. **   output will be performed to the same terminal.  If that terminal
  225. **   is capable of cursor control, command line editing will cause
  226. **   screen updates.  Otherwise, other mechanisms will be employed
  227. **   for presenting edits to the user.  If the source is not a terminal,
  228. **   echoing and other output will be suppressed.
  229. **
  230. ** Input arguments:
  231. **   input: input file stream (normally stdin)
  232. **   output: output file stream (normally stdout)
  233. ** Output arguments: None.
  234. ** Returns: CMxOK always.
  235. **/
  236.  
  237. int
  238. cmseti(input,output,error)
  239. FILE *input,*output,*error;
  240. {
  241.   static int priorset = FALSE;    /* true only after first invocation */
  242.   if (priorset) 
  243.     cmtend();            /* break down prior source if any */
  244.   else
  245.     priorset = TRUE;        /* do that every time after first */
  246.   cmcsb._cmij = input;        /* set input stream in CSB */
  247.   cmcsb._cmoj = output;        /* set output stream in CSB */
  248.   cmcsb._cmej = error;        /* set errout stream in CSB */
  249.   cmtset();                   /* do any necessary terminal initialization */
  250.   return(CMxOK);
  251. }
  252.  
  253.  
  254.  
  255. /* cmdone
  256. **
  257. ** Purpose:
  258. **   Invoked when the program is finished with command parsing, to
  259. **   give the terminal cleanup routines a chance to clean up the
  260. **   most recent command source.
  261. **
  262. ** Input arguments: None.
  263. ** Output arguments: None.
  264. ** Returns: Nothing.
  265. **/
  266.  
  267. cmdone()
  268. {
  269.   cmtend();            /* clean up routine */
  270. }
  271.  
  272.  
  273.  
  274. /* prompt
  275. **
  276. ** Purpose:
  277. **   Issues a prompt to the user and sets a CSB in an appropriate
  278. **   state to begin parsing a new command line.  Prompt should
  279. **   be used whenever a command line is to be parsed from the beginning
  280. **   for any reason EXCEPT a reparse.  For a reparse, skip the prompt
  281. **   call and proceed immediately with calls to 'parse' to parse the
  282. **   fields of the command as required.
  283. **
  284. ** Input arguments:
  285. **   prompt - The prompt string to be issued.  The string must be preserved
  286. **     throughout the parsing of the command line, as it will be referenced
  287. **     whenever the command line must be redisplayed (after a help request,
  288. **     when the user types ^R, etc.).  In other words, do not pass
  289. **     a pointer to dynamic storage that may be deallocated before parsing
  290. **     is complete.
  291. **
  292. ** Output arguments: none
  293. **
  294. ** Returns: Standard return code.
  295. **/
  296.  
  297.  
  298. int
  299. prompt(p)
  300. char *p;
  301. {
  302.   int ret;
  303.   cmcsb._cmflg &= ~CM_CMT;    /* new line...not in a comment */
  304.   if ((cmcsb._cmbfp == NULL) ||
  305.       (cmcsb._cmabp == NULL) ||
  306.       (cmcsb._cmwbp == NULL)
  307.      )
  308.     return(CMxBUFS);        /* no buffers set up yet */
  309.  
  310.   if (cmcsb._cmcmx == -1) {    /* haven't set CONSOLE max col position? */
  311.     ret = cmseti(stdin,stdout,stderr);    /* set up all the source attributes */
  312.     if (ret != CMxOK)
  313.       return(ret);        /* propagate problems */
  314.   }
  315.   cmxbol();            /* get to beginning of line */
  316.   cmxputs(p);            /* and issue prompt */
  317.   cmcsb._cmrty = p;        /* save pointer to prompt string */
  318.   cmcsb._cmflg &= ~(CM_ESC | CM_RPT | CM_PFE | CM_DRT | CM_CFM |
  319.             CM_SWT | CM_ACT | CM_PRS);
  320.                 /* set flags in a known state */
  321.   cmcsb._cmhst = cmcsb._cmptr+cmcsb._cminc; /* save history parse point */
  322.                 /* clear buffer */
  323.   cmcsb._cmcnt += cmcsb._cminc + (cmcsb._cmptr - cmcsb._cmbfp);
  324.   cmcsb._cmcur = cmcsb._cmptr = cmcsb._cmbfp;
  325.   cmcsb._cminc = 0;
  326.   cmcsb._cmerr = CMxOK;        /* clear last parse error */
  327.   return(CMxOK);        /* return success */
  328. }
  329.  
  330.  
  331.  
  332. /* parse
  333. **
  334. ** Purpose:
  335. **   Attempt to parse a single field of a command line, using a list
  336. **   of alternate FDB's describing the possible field contents.  Gather
  337. **   more input if required, and perform  actions as required for action
  338. **   characters, and defaulting as appropriate.
  339. **
  340. ** Input arguments:
  341. **   fdblist - A pointer to the first FDB in a list of alternative FDB's
  342. **     to be used for the parse.
  343. **
  344. ** Output arguments:
  345. **   value - A pointer to a pval item which will be filled in with the
  346. **     value resulting from a successful parse, as appropriate for the 
  347. **     type of field parsed.  (A pointer to an existing pval item must
  348. **     be passed.  No item will be allocated by the ccmd package.  Rather,
  349. **     the passed item will be filled in by ccmd.)
  350. **   usedfdb - A pointer to the FDB that succeeded in a successful parse.
  351. **
  352. ** Returns: Standard return code (CMxOK for good parse)
  353. **/
  354.  
  355. int
  356. parse(fdblist,value,usedfdb)
  357. fdb *fdblist;
  358. pval *value;
  359. fdb **usedfdb;
  360. {
  361.   int ret;            /* return codes from aux routines */
  362.   char brk;            /* break character from command input */
  363.   brktab *btab;            /* break table for current field */
  364.   fdb *f;            /* for stepping through chain */
  365.   int clean = !(cmcsb._cmflg & CM_DRT); /* if buffer is clean */
  366.  
  367.   ret = CMxOK;            /* assume everything will be fine */
  368.   if (cmcsb._cmrty == NULL)
  369.     ret = CMxPMT;        /* can't do this until prompt issued */
  370.  
  371.   else if (fdblist == NULL)    /* Check for empty list of alternatives */
  372.     ret = CMxNFDB;
  373.   else 
  374.     for (f = fdblist; f != NULL; f = f->_cmlst)
  375.       if (badfnc(f->_cmfnc))    /* Check each FDB's function code */
  376.         ret = CMxUNKF;
  377.  
  378.   if (ret == CMxOK)        /* OK so far? */
  379.     ret = getbrk(fdblist,&btab); /* find correct break table for this field */
  380.  
  381.   if (ret == CMxOK)        /* still OK? */
  382.     do {            /* try parsing until success or error */
  383.       cmcsb._cmflg &= ~CM_ESC;
  384.       skipws(btab);        /* skip whitespace */
  385.       ret = checkcfm(fdblist,btab); /* stuff default string if needed */
  386.       if (ret != CMxOK)
  387.     return(ret);        /* propagate errors */
  388.       skipws(btab);        /* skip any space that got stuffed */
  389.       if (cmcsb._cmcur > cmcsb._cmptr) {
  390.       int temp = *cmcsb._cmptr & 0x7f;
  391.       if (BREAK(btab,temp,cmcsb._cminc)) {
  392.           if (*cmcsb._cmptr & CC_ACT) {
  393.           cmcsb._cmflg |= CM_ACT;
  394.           ret = checkbrk(fdblist,btab,(char) *cmcsb._cmptr, 
  395.                      *cmcsb._cmptr);
  396.           if (ret == CMxGO)
  397.               ret = checkact(fdblist,btab,*cmcsb._cmptr);
  398.           }
  399.           }
  400.       }
  401.       
  402.       if (!clean) {
  403.       ret = tryparse(fdblist,value,usedfdb); /* attempt a parse */
  404.       if (ret == CMxOK)
  405.           cmcsb._cmflg |= CM_PRS; /* something parsed now */
  406.             
  407.       if (ret != CMxINC)    /* keep going if more input needed */
  408.           break;        /* anything else... exit loop and handle */
  409.       cmcsb._cmifd = *usedfdb; /* save pointer to FDB that gave CMxINC */
  410.       ret = checkact(fdblist,btab,0);
  411.       if (ret == CMxGO)    /* if action wants wakeup... */
  412.           continue;        /*  then back to top of this loop */
  413.       }
  414.       else {
  415.       cmcsb._cmifd = NULL;    /* XXX */
  416.       ret = CMxOK;
  417.       }
  418.  
  419.       while (ret == CMxOK) {    /* if action ok, loop until wakeup or error */
  420.     ret = fill(btab,&brk);    /* get more input from user */
  421.     clean = FALSE;
  422.     if (cmcsb._cmptr + cmcsb._cminc > cmcsb._cmbfp)
  423.         cmcsb._cmflg |= CM_DRT;
  424.     if (ret == CMxEOF) {
  425.         if (checkeof(fdblist)) {
  426.         cmcsb._cmflg2 |= CM_EOF;
  427.         ret =  CMxGO;
  428.         }
  429.     }
  430.     if (ret != CMxOK)    /* problems with filling... */
  431.       break;        /*  then exit loops to handle */
  432.                 /* handle break character */
  433.     ret = checkbrk(fdblist,btab,brk,0);
  434.       }
  435.     } while (ret == CMxGO);    /* continue loop only if wakeup requested */
  436.  
  437.   /* We get here after a successful parse, or a reparse, or an error */
  438.  
  439.   if (ret == CMxRPT) {        /* reparse needed? */
  440.     cmcsb._cminc += (cmcsb._cmptr - cmcsb._cmbfp); /* all chars unparsed */
  441.     cmcsb._cmptr = cmcsb._cmbfp;
  442.     cmcsb._cmflg &= ~CM_ESC;
  443.     cmcsb._cmflg &= ~CM_CMT;    /* no longer inside a comment */
  444.     cmcsb._cmflg |= CM_RPT;    /* flag the condition */
  445.     cmcsb._cmflg &= ~CM_PRS;    /* back to nothing parsed */
  446.     if (cmcsb._cmrph != NULL)    /* reparse handler available? */
  447.       ret = (*cmcsb._cmrph)();    /* then call it */
  448.   }
  449.   if ((ret != CMxOK) && (cmcsb._cmerh != NULL)) { /* handlable error? */
  450.     fdb *f;
  451.     
  452.     remember();
  453.     cmcsb._cmerr = ret;        /* save the error */
  454.     for(f = fdblist; f ; f = f->_cmlst) /* check if there is a specialized */
  455.       if (f->_cmest) {            /* error string. */
  456.                     /* and invoke the handler */
  457.     ret = (*cmcsb._cmerh)(ret, f->_cmest,f->_cmffl);
  458.     break;
  459.       }
  460.     ret = (*cmcsb._cmerh)(ret, NULL, 0); /* and invoke the handler */
  461.   }
  462.   /* error without handler, or handler failed, or good parse gets here */
  463.   if (ret != CMxOK)        /* save parse error code in CSB */
  464.     cmcsb._cmerr = ret;
  465.   return(ret);
  466. }
  467.  
  468.  
  469.  
  470. /* getbrk
  471. **
  472. ** Purpose:
  473. **   Locate and return the correct break table to be used in parsing a 
  474. **   field.  If a user-supplied break table is encountered in any of the
  475. **   supplied FDB's, that is returned.  Otherwise, the default break table
  476. **   for the first FDB is returned.
  477. **
  478. ** Input arguments:
  479. **   fdblist - A pointer to the first fdb in the chain of alternates.
  480. **
  481. ** Output arguments:
  482. **   btab - A pointer to the break table to be used.
  483. **
  484. ** Returns: Standard return code (CMxOK always).
  485. **/
  486.  
  487. static int
  488. getbrk(fdblist,btab)
  489. fdb *fdblist;
  490. brktab **btab;
  491. {
  492.   *btab = cmfntb[fdblist->_cmfnc-1]->_ftbrk; /* std tbl until overruled */
  493.   while (fdblist != NULL)     /* search through given FDB's */
  494.     if (fdblist->_cmbrk != NULL) { /* user supplied table? */
  495.       *btab = fdblist->_cmbrk;    /* yup, overrule standard table */
  496.       break;            /* and stop looking */
  497.     }
  498.     else
  499.       fdblist = fdblist->_cmlst; /* otherwise move on down the list */
  500.  
  501.   return(CMxOK);        /* That's all... */
  502. }
  503.  
  504.  
  505.  
  506. /* checkcfm
  507. **
  508. ** Purpose:
  509. **   Checks to see whether the current field needs a default value
  510. **   filled in by reason that the command line has been confirmed.
  511. **   If so, the default is stuffed quietly into the command buffer.
  512. **   To qualify, the CM_CFM flag must be on in the CSB, and the command
  513. **   line buffer must contain exactly one unparsed character, that
  514. **   matching the character stored in _cmbkc of the CSB, the character
  515. **   that caused the confirm in the first place.  In addition, the
  516. **   current chain of FDB's must not contain one whose handler has
  517. **   the FN_DFX default-blocking flag turned on.  If a field meets
  518. **   these criteria but the confirming character is not considered
  519. **   a break character for this field, then the CM_CFM flag is turned
  520. **   off and no default is stuffed.  
  521. **
  522.  
  523. **   The following no longer applies...
  524. **   As a special case, if all criteria
  525. **   for defaulting are met, but the CM_PRS flag is off in the CSB,
  526. **   implying that only whitespace preceded the confirmation, the entire
  527. **   line buffer will be discarded and the prompt reissued, effectively
  528. **   restarting the parse from the top.
  529.  
  530. **
  531. ** Input arguments:
  532. **   fdblist - A pointer to the first FDB in the chain of alternates.
  533. **   btab - A pointer to the break table to be used for this field.
  534. **
  535. ** Output arguments: None
  536. ** Returns: CMxOK for all normal returns, anything else is an error.
  537. **/
  538.  
  539. static int
  540. checkcfm(fdblist,btab)
  541. fdb *fdblist;
  542. brktab *btab;
  543. {
  544.   int ret = CMxOK;        /* assume everything will be OK */
  545.   int blocked = FALSE;        /* assume not blocked */
  546.   int cc;            /* next unparsed char in buffer */
  547.   char c;
  548.   fdb *f;            /* for stepping through FDB chain */
  549.   
  550.   for (f = fdblist; f != NULL; f = f->_cmlst)
  551.     if (cmfntb[f->_cmfnc-1]->_ftflg & FT_DFX) {
  552.       blocked = TRUE;        /* blocked */
  553.       break;            /* stop looking */
  554.     }
  555.  
  556.   c = (cc = *cmcsb._cmptr) & CC_CHR; /* get first unparsed char */
  557.   if (!blocked &&        /* if we're not blocked */
  558.       (cmcsb._cmflg & CM_CFM) && /* and flag is on */
  559.       (cmcsb._cminc == 1) &&    /* and exactly one char in buffer */
  560.       (c == cmcsb._cmbkc)    /* and it's the confirming char */
  561.      ) 
  562.     if (BREAK1(btab,c)) {    /* confirming char still break? */
  563.       char *defstr = NULL;
  564.       for (f = fdblist; f != NULL; f= f->_cmlst)
  565.     if (f->_cmdef) {
  566.       defstr = f->_cmdef;
  567.       break;
  568.     }
  569.                 /* nothing yet parsed? and no default? */
  570.       if ((cmcsb._cmflg & CM_PRS) == 0 && defstr == NULL) {
  571.     cmcsb._cmcnt += cmcsb._cminc + (cmcsb._cmptr - cmcsb._cmbfp);
  572.     cmcsb._cminc = 0;    /* right, empty the buffer */
  573.     cmcsb._cmcur = cmcsb._cmptr = cmcsb._cmbfp;
  574.     cmcsb._cmflg &= ~CM_CFM; /* buffer no longer confirmed */
  575.     cmxbol();        /* and reissue prompt on terminal */
  576.     cmxputs(cmcsb._cmrty);
  577.     return(CMxOK);        /* now go get more input */
  578.       }
  579.       cmcsb._cminc = 0;        /* remove the unparsed confirm char */
  580.       cmcsb._cmcur--;            /* XXXX */
  581.       cmcsb._cmcnt++;
  582.       ret = cmsti1(SPACE,CC_HID); /* hidden separator */
  583.       if (ret == CMxOK)
  584.         ret = cmsti(defstr,CC_HID); /* stuff default, hidden */
  585.       if (ret == CMxOK)
  586.     ret = cmsti1(c,CC_NEC); /* and put back confirm char */
  587.       if (ret == CMxOK)
  588.     *(cmcsb._cmcur) = cc; /* and restore flags */
  589.     }
  590.     else            /* confirm char no longer breaks */
  591.       cmcsb._cmflg &= ~CM_CFM;    /* so turn off confirm flag */
  592.  
  593.  
  594.   return(ret);            /* CMxOK, unless error with stuffing */
  595. }
  596.  
  597.  
  598.  
  599. /* checkact
  600. **
  601. ** Purpose:
  602. **   This routine is invoked when an attempt to parse a field has
  603. **   returned code CMxINC (incomplete parse -- more data required).
  604. **   If there is a deferred action waiting to be invoked, the break
  605. **   character that calls for the action is first checked to make
  606. **   sure it is still considered a break character in the current
  607. **   parsing context.  If not, the character is appended to the
  608. **   command buffer, and the CM_ACT flag is cleared.  Otherwise,
  609. **   the indicated action is invoked.
  610. **
  611. ** Input arguments:
  612. **   fdblist - A pointer to the first FDB on the chain of alternates.
  613. **   btab - A pointer to the break table currently in effect.
  614. **
  615. ** Output arguments: None
  616. **
  617. ** Returns: CMxGO if an action was invoked and indicated that a wakeup
  618. **   should be performed, CMxOK if no action pending or invoked action
  619. **   did not request wakeup, or other standard return code on error.
  620. **/
  621.  
  622. static int
  623. checkact(fdblist,btab,flags)
  624. fdb *fdblist;
  625. brktab *btab;
  626. int flags;
  627. {
  628.   int ret;
  629.   
  630.   if (cmcsb._cmflg & CM_ACT) {    /* action pending? */
  631.     if (BREAK(btab,cmcsb._cmbkc,cmcsb._cminc)) { /* still a break char? */
  632.       if (cmcsb._cmact[cmcsb._cmbkc]) {
  633.     ret = (*cmcsb._cmact[cmcsb._cmbkc])(fdblist,cmcsb._cmbkc,TRUE,flags);
  634.                 /* yes, invoke routine in deferred mode */
  635.       }
  636.       else ret = CMxOK;
  637.     }
  638.     else
  639.       ret = cmsti1(cmcsb._cmbkc,0); /* deposit non-break char */
  640.  
  641.     cmcsb._cmflg &= ~CM_ACT;    /* turn off action flag either way */
  642.     return(ret);        /* and return result */
  643.   }
  644.   else
  645.     return(CMxOK);        /* no action pending => no error */
  646. }
  647.  
  648.  
  649.  
  650.  
  651. /* checkbrk
  652. **
  653. ** Purpose:
  654. **   This routine is invoked after some input has been successfully
  655. **   collected for the command line.  The break character is checked
  656. **   to see whether it is an action character, and if so, the action
  657. **   is invoked.  If the return code indicates that the action should
  658. **   be deferred, the CSB is updated accordingly.
  659. **
  660. ** Input arguments:
  661. **   fdblist - A pointer to the chain of alternate FDB's for the current
  662. **     field.
  663. **   btab - A pointer to the break table currently in effect.
  664. **   brk - The character that broke the input process.
  665. **
  666. ** Output arguments: None
  667. **
  668. ** Returns: CMxGO if a wakeup should be performed, CMxOK if more
  669. **   input should be collected, or a standard error code.
  670. **/
  671.  
  672. static int
  673. checkbrk(fdblist,btab,brk, flags)
  674. fdb *fdblist;
  675. brktab *btab;
  676. char brk;
  677. int flags;
  678. {
  679.   int ret;
  680.   int (*act)();            /* pointer to action routine */
  681.   
  682.   if ((act = cmcsb._cmact[brk]) != NULL) { /* is there an action routine? */
  683.     ret = (*act)(fdblist,brk,FALSE,flags); /* invoke it non-deferred */
  684.     if (ret == CMxDFR) {    /* action wants to defer? */
  685.       cmcsb._cmflg |= CM_ACT;    /* set the flag */
  686.       cmcsb._cmbkc = brk;    /* save the action char */
  687.       ret = CMxGO;        /* and request a wakeup */
  688.     }
  689.   }
  690.   else {            /* No action... */
  691.     ret = cmsti1(brk,0);    /* deposit brk char into command buffer */
  692.     if (ret == CMxOK)
  693.       if (cmcsb._cmflg & CM_WKF) /* waking on every field? */
  694.     ret = CMxGO;        /* yup, set return code to request wakeup */
  695.   }
  696.   return(ret);            /* back to caller... */
  697. }
  698.  
  699.  
  700.  
  701. /* tryparse
  702. **
  703. ** Purpose:
  704. **   Invokes the parsing functions specified by a chain of FDB's, passing
  705. **   the current unparsed command input for them to parse.  Each function
  706. **   either fails or not, depending on whether it is able to parse the
  707. **   current input.  In the case of failure, a standard error code is
  708. **   returned.  Otherwise, either CMxOK or CMxINC is returned.  The former
  709. **   indicates that a complete parse was performed, generally indicating
  710. **   the presence of a suitable break character in addition to the field
  711. **   contents.  CMxINC indicates that the current input is not sufficient
  712. **   for a successful parse, but further input may result in a successful
  713. **   parse.
  714. **
  715. **   If all functions fail, the code returned by the first is given back
  716. **   to the caller.  Otherwise, the first CMxOK or CMxINC code returned
  717. **   by a parsing function is passed on, without invoking the rest of the
  718. **   parsing functions.  In the case of success, the CSB is adjusted so
  719. **   as to consume the newly parsed characters, and any value returned
  720. **   by the parse is passed on to the caller, as well as a pointer to the
  721. **   succeeding FDB.  In addition, on a successful parse, the parsed
  722. **   characters are copied into the atom buffer.  On anything other than
  723. **   success, the CM_PFE flag is turned off in the CSB in order to prevent
  724. **   a following noise word from expanding, in case this field got completion.
  725. **   When CMxINC is returned, a pointer to the FDB that gave an incomplete
  726. **   parse is returned to the user.
  727. **
  728. ** Input arguments:
  729. **   fdblist - A pointer to the first FDB in the list of parsing alternatives.
  730. **
  731. ** Output arguments
  732. **   value - A pointer to a pval item which will be filled in with the
  733. **     value resulting from a successful parse, as appropriate for the 
  734. **     type of field parsed.  (A pointer to an existing pval item must
  735. **     be passed.  No item will be allocated by the ccmd package.  Rather,
  736. **     the passed item will be filled in by ccmd.)
  737. **   usedfdb - A pointer to the FDB that succeeded in a successful parse.
  738. **
  739. ** Returns: standard return code.
  740. **/
  741.  
  742. int
  743. tryparse(fdblist,value,usedfdb)
  744. fdb *fdblist;
  745. pval *value;
  746. fdb **usedfdb;
  747. {
  748.   int ret;            /* return codes from parsers */
  749.   int failret = CMxOK;        /* return code of first failing fdb */
  750.   int inputlen;            /* number of chars available for parse */
  751.   int parselen;            /* number of chars successfully parsed */
  752.   ftspec *ft;            /* function handler structure */
  753.  
  754.   ret = cmprep(cmcsb._cmwbp,cmcsb._cmwbc,&inputlen); /* clean up input */
  755.   if (ret != CMxOK)
  756.     return(ret);        /* propagate errors */
  757.   while (fdblist != NULL) {    /* loop through chain of FDB's */
  758.     ft = cmfntb[fdblist->_cmfnc-1]; /* get correct handler structure */
  759.                 /* and call the parser */
  760.     while (TRUE) {        /* loop til handler is finished */
  761.       cmcsb._cmflg &= ~CM_NAC;    /* assume ok to copy good parse to atom */
  762.       ret = (*ft->_ftprs)(cmcsb._cmwbp,inputlen,fdblist,&parselen,value);
  763.       if (ret != CMxAGN)    /* exit if handler did not ask for retry */
  764.     break;
  765.       ret = cmprep(cmcsb._cmwbp,cmcsb._cmwbc,&inputlen); /* rebuild input */
  766.       if (ret != CMxOK)
  767.     return(ret);        /* propagate failure */
  768.     }
  769.     if (ret == CMxOK) {        /* successful parse? */
  770.       *usedfdb = fdblist;    /* save ptr to successful FDB */
  771.       if ((cmcsb._cmflg & CM_NAC) == 0)
  772.         ret = toatom(cmcsb._cmwbp,parselen); /* parsed text to atom bfr */
  773.                  /* adjust cnt for skipped chars */
  774.       parselen = skipadj(cmcsb._cmptr,cmcsb._cminc,parselen);
  775.       cmcsb._cmptr += parselen;    /* update buffer pointers */
  776.       cmcsb._cminc -= parselen;    /*  to account for newly parsed text */
  777.       return(CMxOK);        /* and return success */
  778.     }
  779.     else if (ret == CMxINC) {    /* incomplete parse? */
  780.       *usedfdb = fdblist;    /* give back pointer to this FDB */
  781.       cmcsb._cmflg &= ~CM_PFE;    /* completion did not succeed */
  782.       return(CMxINC);        /* pass incomplete code along */
  783.     }
  784.     else {            /* unsuccessful parse */
  785.       if (failret == CMxOK)    /* first one? */
  786.     failret = ret;        /* yup, save first failure code */
  787.       fdblist = fdblist->_cmlst; /* and move on to next FDB */
  788.     }
  789.   }
  790.   cmcsb._cmflg &= ~CM_PFE;    /* all failed... no noise words */
  791.   return(failret);        /* and return first failure code */
  792. }
  793.  
  794.  
  795.  
  796. /* fill
  797. **
  798. ** Purpose:
  799. **   Accepts characters from the command input source and loads them
  800. **   into the command buffer until a break character or action
  801. **   character is encountered or an error condition occurs.  If
  802. **   a break character terminates the fill operation, the character
  803. **   is returned to the caller via the 'brk' argument, and a return
  804. **   code of CMxOK is given.  If an action character that is not
  805. **   currently a break character is encountered, a return is made
  806. **   with return code CMxGO, indicating that a wakeup should occur,
  807. **   but the 'brk' argument is not filled in, nor is the action
  808. **   character stuffed into the command buffer.  Instead, it is
  809. **   held until the next call to fill, at which time, if it is
  810. **   still not a break character, it is simply stuffed into the
  811. **   buffer like any non-break character.  If by the next call
  812. **   to fill, the break table has changed so that the action character
  813. **   breaks, a normal break-character return will be made to the
  814. **   caller, with 'brk' containing the action character.  The
  815. **   purpose of all these contortions is to ensure that action
  816. **   characters that are turned off for a field will be reactivated
  817. **   when the user finishes typing that field.  The reactivation
  818. **   requires a wakeup, to allow the user program to proceed to
  819. **   following parse fields.
  820. **
  821. **   As a side-effect of this call, CM_PFE flag is turned off in
  822. **   the CSB, in case the current field got completion and then 
  823. **   came up with an incomplete parse anyway.
  824. **
  825. ** Input arguments:
  826. **   btab - A pointer to the break table in effect.
  827. **
  828. ** Output arguments:
  829. **   brk - The character that broke the input.
  830. **
  831. ** Returns: Standard return code.
  832. **/
  833.  
  834. static int
  835. fill(btab,brk)
  836. brktab *btab;
  837. char *brk;
  838. {
  839.   static int heldact = -1;    /* non-breaking action char from prior call */
  840.   int ret;            /* return codes */
  841.   int c;            /* input character */
  842.   int indirend = FALSE;
  843.   int cmgetc();
  844.   
  845.   cmcsb._cmflg &= ~CM_PFE;    /* no noise word expansion */
  846.   while (cmcsb._cmcnt > 0) {    /* quit when we overflow the buffer */
  847.     if (heldact != -1)
  848.       c = heldact;        /* pick up held character */
  849.     else {
  850.       ret = cmgetc(&c,cmcsb._cmij);    /* or get a new character */
  851.       if (ret != CMxOK) {
  852.     if (ret == CMxEOF && cmcsb._cmblh) { /* nonblocking and a handler? */
  853.         ret = (*cmcsb._cmblh)(ret);    /* invoke the handler */
  854.     }
  855.     if (ret == CMxEOF && cmcsb._cmflg2 & CM_IND) {
  856.         cmindend();
  857.         cmcsb._cmflg |= CM_CFM;
  858.         cmsti1('\n',CC_NEC|CC_HID);
  859.         cmcsb._cmcol = 0;
  860.         return(CMxGO);
  861.     }
  862.     else 
  863.           return(ret);        /* propagate errors */
  864.       }
  865.     }
  866.     if ((cmcsb._cminc == 0) && /* skip white space at beginning of line */
  867.     ((c == SPACE) || (c == TAB)) && /* space or tab? */
  868.     BREAK1(btab,c) &&    /* and a break character for this field? */
  869.     (cmcsb._cmact[c] == NULL) /* and not an action char? */
  870.        ) {
  871.       ret = cmsti1(c,0);    /* add char to buffer and echo */
  872.       heldact = -1;        /* if this was held, it's not anymore */
  873.       if (ret != CMxOK)
  874.     return(ret);        /* propagate error */
  875.       continue;            /* and go get more characters */
  876.     }
  877.     else if (BREAK(btab,c,cmcsb._cminc)) { /* break character? */
  878.       *brk = c;            /* yup, pass it back to caller */
  879.       heldact = -1;        /* not held anymore */
  880.       return(CMxOK);        /* and return success */
  881.     }
  882.     else if ((heldact == -1) && /* this one was not held */
  883.          (cmcsb._cmact[c] != NULL) /* and it's a non-breaking action? */
  884.          ) {
  885.       heldact = c;        /* yup, remember it */
  886.       return(CMxGO);        /* and ask for a wakeup */
  887.     }
  888.     else if (c & CM_ACT) {
  889.       heldact = c & 0xff;
  890.       return(CMxGO);
  891.     }
  892.     else {            /* not skipped ws, not break or action */
  893.       ret = cmsti1(c,0);    /* add char to buffer and echo */
  894.       heldact = -1;        /* we're not holding it */
  895.       if (ret != CMxOK)
  896.     return(ret);        /* propagate problems */
  897.     }
  898.   }
  899.  
  900.   /* Dropped out of loop -- buffer must have overflowed */
  901.   return(CMxBOVF);        
  902. }
  903.  
  904.  
  905.  
  906. /*
  907.  * this also skips comments now 
  908.  */
  909.  
  910. /* skipws
  911. **
  912. ** Purpose:
  913. **   Skip past any spaces and tabs (white space) at the current parse
  914. **   position in the command buffer, unless they are not considered
  915. **   break characters in the current parse field.  Also, all characters
  916. **   with the CC_SKP flag and eligible characters with CC_CSK flag
  917. **   are skipped.  (As a side effect, ineligible CC_CSK flags are
  918. **   cleared.)
  919. **
  920. ** Input arguments:
  921. **   btab - A pointer to the current break table.
  922. **
  923. ** Output arguments: None.
  924. ** Returns: Nothing
  925. **/
  926.  
  927. #define CBEG cmcsb._cmntb
  928. #define CST cmcsb._cmnts
  929. #define CEND cmcsb._cmnte
  930. #define NOCOMMENT 0
  931. #define DELIMITED 1
  932. #define TOEOL 2
  933.  
  934. skipws(btab)
  935. brktab *btab;
  936. {
  937.   int cc;            /* characters from input buffer */
  938.   char c;
  939.   int cskip = 0;        /* number of chars in run of CC_CSK chars */
  940.   int cbeglen, cstlen, cendlen;
  941.   static int commenttype = NOCOMMENT;
  942.  
  943.   cendlen= CEND ? strlen(CEND) : 0;
  944.   cstlen= CST ? strlen(CST) : 0;
  945.   cbeglen= CBEG ? strlen(CBEG) : 0;
  946.  
  947.   if (!(cmcsb._cmflg & CM_CMT))
  948.     commenttype = NOCOMMENT;
  949.  
  950.   while (TRUE) {        /* at most twice, if input ends with */
  951.                 /*  a run of ineligible conditional skips */
  952.                 /* loop until no more unparsed data */
  953.  
  954.     while (cmcsb._cmcur - cmcsb._cmptr > cskip) {
  955.       c = (cc = *(cmcsb._cmptr + cskip)) & CC_QCH; /* get next char */
  956.       if (cc & CC_SKP) {    /* unconditional skip char? */
  957.     cmcsb._cmptr += cskip+1; /* yup, skip conditional skips too */
  958.     cmcsb._cminc -= cskip+1;
  959.     cskip = 0;        /* the run is over */
  960.       }
  961.       else if (cc & CC_CSK)    /* conditional skip char? */
  962.     cskip++;        /* yup, count it */
  963.       else {            /* no type of skip flag */
  964.     if (cskip > 0) {
  965.       while (cskip-- > 0)    /* turn off ineligible conditional skips */
  966.         cmcsb._cmptr[cskip] &= ~CC_CSK;
  967.       continue;        /* and rescan those characters */
  968.     }
  969.     if (((c == SPACE) || (c == TAB)) && BREAK1(btab,c)) {
  970.       cmcsb._cmptr++;    /* skip nonbreaking whitespace */
  971.       cmcsb._cminc--;
  972.     }
  973.     else if (BREAK1(btab,c)) { 
  974.       int i,j;
  975.       if (!(cmcsb._cmflg & CM_CMT)) {
  976.         if (commenttype == NOCOMMENT)
  977.           if (cbeglen > 0 && !xstrncmp(cmcsb._cmptr,CBEG,cbeglen)) {
  978.         commenttype = TOEOL;
  979.         cmcsb._cmflg |= CM_CMT;
  980.         cmcsb._cmptr += cbeglen;
  981.         cmcsb._cminc -= cbeglen;
  982.           }
  983.           else if (cstlen > 0 && !xstrncmp(cmcsb._cmptr,CST,cstlen)) {
  984.         commenttype = DELIMITED;
  985.         cmcsb._cmflg |= CM_CMT;
  986.         cmcsb._cmptr += cstlen;
  987.         cmcsb._cminc -= cstlen;
  988.           }
  989.           else 
  990.         break;
  991.         }
  992.         if (cmcsb._cmflg & CM_CMT) {
  993.           if (commenttype == TOEOL) {
  994.         for (j = cmcsb._cminc,i = 0; i < j; i++) {
  995.           char c = *cmcsb._cmptr;
  996.           if (cmcsb._cmflg & CM_CFM && cmcsb._cminc == 1) {
  997.             cmcsb._cmflg &= ~CM_CMT;
  998.           }
  999.           else {
  1000.             cmcsb._cmptr++;
  1001.             cmcsb._cminc--;
  1002.           }
  1003.         }
  1004.         break;
  1005.           }
  1006.           else if (commenttype == DELIMITED) {
  1007.         for (j = cmcsb._cminc,i = 0; i < j; i++) {
  1008.           char c = *cmcsb._cmptr;
  1009.           if (cmcsb._cmflg & CM_CFM && cmcsb._cminc == 1) {
  1010.             cmcsb._cmflg &= ~CM_CMT;
  1011.             break;
  1012.           }
  1013.           else if (!xstrncmp(cmcsb._cmptr,CEND,cendlen) && 
  1014.                cmcsb._cminc >= cendlen || cendlen == 0) {
  1015.             cmcsb._cmflg &= ~CM_CMT;
  1016.             cmcsb._cmptr += cendlen;
  1017.             cmcsb._cminc -= cendlen;
  1018.             break;
  1019.           }
  1020.           else {
  1021.             cmcsb._cmptr++;
  1022.             cmcsb._cminc--;
  1023.           }
  1024.         }
  1025.         break;
  1026.           }
  1027.           if (cmcsb._cminc < 0) cmcsb._cminc = 0;
  1028.           return;
  1029.         }
  1030.     }
  1031.     else {
  1032.       if (cmcsb._cminc < 0) cmcsb._cminc = 0;
  1033.       return;        /* else found our break point */
  1034.     }
  1035.       }
  1036.     }
  1037.     if (cskip > 0) {        /* finished with an ineligible run? */
  1038.       while (cskip-- > 0)    /* turn off ineligible conditional skips */
  1039.     cmcsb._cmptr[cskip] &= ~CC_CSK;
  1040.       continue;            /* and rescan it */
  1041.     }
  1042.     else
  1043.       return;            /* skipped the whole buffer */
  1044.   }
  1045. }
  1046.  
  1047. static int
  1048. xstrncmp(s1,s2,len) int *s1; char *s2; int len; {
  1049.   for (; len > 0; len--) {
  1050.     if ((*s1 & 0xff) < *s2) return (-1);
  1051.     if ((*s1 & 0xff) > *s2) return(1);
  1052.     s1++; s2++;
  1053.   }
  1054.   return(0);
  1055. }
  1056.  
  1057.  
  1058.  
  1059. /* toatom
  1060. **
  1061. ** Purpose:
  1062. **   Copy text into the atom buffer, stripping any CC_QUO flags that
  1063. **   might be encountered.
  1064. **
  1065. ** Input arguments:
  1066. **   text - A pointer to the text to be copied.
  1067. **   textlen - The number of characters to copy.
  1068. **
  1069. ** Output arguments: None.
  1070. ** Returns: Standard return code.
  1071. **/
  1072.  
  1073. static int
  1074. toatom(text,textlen)
  1075. char *text;
  1076. int textlen;
  1077. {
  1078.   int ret = CMxOK;            /* assume it will fit */
  1079.   char *abp;                /* pointer into atom buffer */
  1080.  
  1081.   if (textlen >= cmcsb._cmabc) {    /* too big? */
  1082.     ret = CMxAOVF;            /* yup, we will return this */
  1083.     textlen = cmcsb._cmabc-1;        /* and only copy this much */
  1084.   }
  1085.   abp = cmcsb._cmabp;            /* point to atom buffer */
  1086.   while (textlen-- > 0)
  1087.     *abp++ = (*text++) & CC_CHR;    /* copy the text */
  1088.   *abp = NULCHAR;            /* tie off with a null */
  1089.   return(ret);
  1090. }
  1091.  
  1092.  
  1093.  
  1094. /* skipadj
  1095. ** 
  1096. ** Purpose:
  1097. **   Given a number of characters to consume in a buffer, adjusts that
  1098. **   count to include any skipped that occur in the buffer.
  1099. **   The passed buffer is assumed to be the command buffer, composed of
  1100. **   characters with flag bytes.
  1101. **
  1102. ** Input arguments:
  1103. **   buf - A pointer to the characters to be counted.
  1104. **   bufsize - Number of characters in buffer, including skips.
  1105. **   passcnt - Number of characters to consume, not including skips.
  1106. **
  1107. ** Output arguments: None.
  1108. ** Returns: Number of characters to consume, including skips.
  1109. **/
  1110.  
  1111. static int
  1112. skipadj(buf,bufsize,passcnt)
  1113. int *buf;
  1114. int bufsize, passcnt;
  1115. {
  1116.   int adjusted= 0;        /* char count including skips */
  1117.  
  1118.   while (passcnt > 0) {        /* pass # of chars requested */
  1119.     if ((*buf & (CC_CSK | CC_SKP)) == 0) /* next char not skiped? */
  1120.       passcnt--;        /* no, indicate progress */
  1121.     bufsize--;            /* consume it in any case */
  1122.     buf++;
  1123.     adjusted ++;        /* and bump adjusted count */
  1124.   }
  1125.   return(adjusted);
  1126. }
  1127.  
  1128. extern char *malloc();
  1129. extern char *realloc();
  1130. /* realloc that does malloc if given NULL pointer */
  1131. char *cmrealloc(ptr, size)
  1132. char *ptr;
  1133. int size;
  1134. {
  1135.     return((ptr == NULL) ? malloc(size) : realloc(ptr, size));
  1136. }
  1137.  
  1138. /*
  1139.  * match a string as parse would user input.
  1140.  */
  1141. match(string, len, fdblist, value, usedfdb, parselen)
  1142. char *string;                /* input string */
  1143. int len;                /* length of input string */
  1144. fdb *fdblist;                /* list of fdb's to try parsing */
  1145. pval *value;                /* return value */
  1146. fdb **usedfdb;                /* which fdb got used */
  1147. int *parselen;                /* how much did we parse. */
  1148. {
  1149.   csb savecsb;                /* local csb value */
  1150.   char *atmbuf, *wrkbuf, *malloc();    /* local buffers */
  1151.   int *cmdbuf;
  1152.   int ret;                /* return codes from parsers */
  1153.   brktab* btab;                /* get current break tab */
  1154.   savecsb = cmcsb;            /* save current csb */
  1155.                     /* use our own buffers */
  1156.   atmbuf = malloc(len+1);
  1157.   wrkbuf = malloc(len+1);
  1158.   cmdbuf = (int *) malloc((len + 1) * sizeof(int));
  1159.  
  1160.   cmbufs(cmdbuf,len+1, atmbuf, len+1, wrkbuf, len+1);
  1161.   cmstin(string,len,CC_NEC);        /* STI in the string */
  1162.   ret = getbrk(fdblist,&btab); /* find correct break table for this field */
  1163.   skipws(btab);
  1164.   ret = tryparse(fdblist,value,usedfdb); /* try to parse it */
  1165.   if (ret != CMxOK) {
  1166.       cmsti1('\n',CC_NEC);        /* make it break...kinda kludgy */
  1167.       ret = getbrk(fdblist,&btab);    /* find correct break table for this
  1168.                        field */
  1169.       skipws(btab);
  1170.       ret = tryparse(fdblist,value,usedfdb); /* try to parse it */
  1171.   }
  1172.   *parselen = cmcsb._cmptr - cmcsb._cmbfp;
  1173.   cmcsb = savecsb;            /* restore original csb */
  1174.   free(atmbuf);
  1175.   free(wrkbuf);
  1176.   free(cmdbuf);
  1177.   return(ret);
  1178. }
  1179.  
  1180.  
  1181. /*
  1182.  * turn on/off command history
  1183.  */
  1184.  
  1185. /*
  1186.  * create a "ring" buffer for command history.  
  1187.  * maintain a NULL entry at the end of the ring.  We need to allocate one 
  1188.  * extra buffer to do this.
  1189.  * 
  1190.  * next points to the NULL entry.
  1191.  * current points to the entry to show when going thorugh the history.
  1192.  * len is the length of the entire buffer, including the hole.
  1193.  */
  1194.  
  1195. cmhst(n) {
  1196.     cmhist *h;
  1197.     int i;
  1198.     if (n > 0) {
  1199.     n++;                /* space for a "hole" */
  1200.                     /* no previous history, allocate. */
  1201.     if (cmcsb._cmhist == NULL || cmcsb._cmhist->len == 0) {
  1202.         h=cmcsb._cmhist=(cmhist *)cmrealloc(cmcsb._cmhist,sizeof(cmhist));
  1203.         h->current = 0;
  1204.         h->enabled = TRUE;
  1205.         h->next = 0;
  1206.         h->bufs = (cmhistbuf *)malloc(n * sizeof(cmhistbuf));
  1207.         bzero(h->bufs, n * sizeof(cmhistbuf));
  1208.         h->len = n;
  1209.         return;
  1210.     }
  1211.  
  1212.     h = cmcsb._cmhist;
  1213.  
  1214.     if (n < h->len) {        /* making history smaller */
  1215.         int start = (h->next + h->len - (n - 1)) % h->len;
  1216.         int cnt = 0;
  1217.         cmhistbuf *b = (cmhistbuf *)malloc(n*sizeof(cmhistbuf));
  1218.         bzero(b,n*sizeof(cmhistbuf));
  1219.         for(i = start; i < h->next; i = (i + 1) % h->len)
  1220.         if (h->bufs[i].buf) 
  1221.             break;
  1222.         else
  1223.             start++;
  1224.         for (i = start; i%h->len != h->next; i++) {
  1225.         b[i-start].buf = h->bufs[i%h->len].buf;
  1226.         b[i-start].len = h->bufs[i%h->len].len;
  1227.         h->bufs[i%h->len].buf = NULL;
  1228.         cnt++;
  1229.         }
  1230.         free_histbuf(h->bufs, h->len);
  1231.         h->bufs = b;
  1232.         h->next = h->current = cnt;
  1233.         for(i = cnt; i < n; i++) {
  1234.         b[i].buf = NULL;
  1235.         b[i].len = 0;
  1236.         }
  1237.     }
  1238.     else {                /* enlarging.   easy case */
  1239.         cmhistbuf *b =(cmhistbuf *)cmrealloc(h->bufs, n*sizeof(cmhistbuf));
  1240.         bzero(b,n*sizeof(cmhistbuf));
  1241.         if (h->len > 0) {
  1242.         int start = (h->next +h->len - 1) % h->len;
  1243.         int cnt = 0;
  1244.         
  1245.         for(i = start; h->bufs[i].buf != NULL;
  1246.             i = (i + h->len-1) % h->len)
  1247.             start = i;
  1248.         for(i = start; i%h->len != h->next; i++) {
  1249.             b[i-start].buf = h->bufs[i%h->len].buf;
  1250.             b[i-start].len = h->bufs[i%h->len].len;
  1251.             h->bufs[i%h->len].buf = NULL;
  1252.             cnt++;
  1253.         }
  1254.         free_histbuf(h->bufs, h->len);
  1255.         h->bufs = b;
  1256.         for(i = cnt; i < n; i++) {
  1257.             h->bufs[i].buf = NULL;
  1258.  
  1259.             h->bufs[i].len = 0;
  1260.         }
  1261.         h->next = h->current = cnt;
  1262.         }
  1263.         else {
  1264.         h->current = h->next = 0;
  1265.         h->enabled = TRUE;
  1266.         }
  1267.     }
  1268.     h->len = n;
  1269.     }
  1270.     else {
  1271.     if (h = cmcsb._cmhist) {
  1272.         if (h->bufs) free(h->bufs);
  1273.         h->bufs = NULL;
  1274.         h->len = h->current = h->next = 0;
  1275.     }
  1276.     else {
  1277.          h = cmcsb._cmhist = (cmhist *)malloc(sizeof(cmhist));
  1278.         h->bufs = NULL;
  1279.         h->len = h->current = h->next = 0;
  1280.     }
  1281.     }
  1282. }
  1283.  
  1284.  
  1285. cmhst_enable() {
  1286.     cmcsb._cmhist->enabled = TRUE;
  1287. }
  1288.  
  1289. cmhst_disable() {
  1290.     cmcsb._cmhist->enabled = FALSE;
  1291. }
  1292.  
  1293. free_histbuf(b,n)
  1294. cmhistbuf *b;
  1295. {
  1296.     int i;
  1297.     for(i = 0; i < n; i++)
  1298.     if (b[i].buf) {
  1299.         free(b[i].buf);
  1300.         b[i].buf = NULL;
  1301.     }
  1302. }
  1303.  
  1304. static int
  1305. checkeof(fdblist)
  1306. fdb *fdblist;
  1307. {
  1308.     fdb *f;
  1309.  
  1310.     for(f = fdblist; f ; f = f->_cmlst)
  1311.     if (f->_cmffl & CM_NEOF)
  1312.         return(TRUE);
  1313.     return(FALSE);
  1314. }
  1315.