/*
 Author: Andrew Lowry

 Columbia University Center for Computing Activities, July 1986.
 Copyright (C) 1986, 1987, Trustees of Columbia University in the City
 of New York.  Permission is granted to any individual or institution
 to use, copy, or redistribute this software so long as it is not sold
 for profit, provided this copyright notice is retained.
*/
/* cmqst
**
** This module contains the standard break table and handlers for
** parsing quoted strings.  Parsing succeeds if the current input
** contains a quote, followed by an acceptable content string,
** followed by a quote and any character other than a quote.
** Doubled quotes are converted to singleton quotes.  An exception
** is when the doubled quote occurs at the beginning of the field,
** in which case an empty string is parsed.  The parsed
** string is returned, without opening or closing quotes, via
** the atom buffer.  The characters that are allowed inside the
** quoted string are precisely those that do not break in non-first
** position.  By default, this includes all printing characters.
** Note that this default implies that question marks lose their
** standard actions.
**
** The first-position break array can be used to indicate which
** characters are allowed to serve as quotes.  If the first character
** is any of these characters, it becomes the quote character for
** the field, and the ASCII quote character (") is treated as an
** ordinary character.  Thus, when quote is not the field delimiter,
** it need not be doubled, but the field delimiter must be doubled.
** For example, the following would yield equivalent results:
**
**		"The character, ""\,"" is called ""back-slash."""
**		\The character, "\\," is called "back-slash."\
**
** By default, the ASCII quote and apostrophe are the only
** characters allowed to be quotes (" and ').
**
** Full completion, when invoked immediately following a singleton quote,
** inserts a space after the quote.  Otherwise it beeps.  Partial completion
** always beeps.  The standard help string is "Quoted string," unless
** the first-position break array is not identical to the standard,
** in which case "Delimited string" is used instead.  If the delimiter
** is already present in the buffer, the message "String terminated by 
** 'x'" is used, where x is the delimiter.
**/

#define	QSTERR			/* declare cmqst parse errors here */

#include "ccmdlib.h"		/* get ccmd package symbols */
#include "cmfncs.h"		/* and internal symbols */

/* Forward declarations of handler routines */

int qstprs(), qsthlp(), qstcplt();

static brktab qstbrk = {
  {					/* chars that can serve as quote */
    0xff, 0xff, 0xff, 0xff, 0xde, 0xff, 0xff, 0xff, /* allow " and ' */
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
  },
  {					/* chars that can be in strings */
    0xff, 0xbf, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, /* tab and all */
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01  /*  printable chars */
  }
};

ftspec ft_qst = { qstprs, qsthlp, qstcplt, 0, &qstbrk }; /* handler structure */



/* qstprs - If the current input contains a proper quoted string,
** copy it into the atom buffer and return, with the CSB flag set
** to prevent automatic atom buffer copying.
**/

PASSEDSTATIC int
qstprs(text,textlen,fdbp,parselen,value)
char *text;
int textlen,*parselen;
fdb *fdbp;
pval *value;
{
  char quote;			/* character used as quote */
  int qstlen;			/* length of (undoubled) quoted string */
  char *term;			/* pointer to char after closing quote */
  int ret;			/* returned by auxiliary routine */
  int i;

  ret = fndqst(text,textlen,fdbp,&quote,&qstlen,&term); /* find the string */
  if (ret != CMxOK)
    return(ret);		/* propagate errors */

  if (term == NULL)		/* not terminated...  */
    return(CMxINC);		/* they might double final quote */

  if ((qstlen+1) > cmcsb._cmabc)
    return(CMxAOVF);		/* no room to return string */

  *parselen = term - text;	/* set buffer consumption */

  for (i = 0; i < qstlen; i++)	/* copy chars to atom buffer */
    if (*(++text) == quote)	/* quote char? (will fail if quoted) */
      cmcsb._cmabp[i] = *(++text) & CC_CHR; /* yes, must be doubled */
    else
      cmcsb._cmabp[i] = *text & CC_CHR; /* otherwise copy it in */
  cmcsb._cmabp[qstlen] = NULCHAR; /* tie off with null character */
  cmcsb._cmflg |= CM_NAC;	/* don't let ccmd copy to atom */
  value->_pvstr = cmcsb._cmabp;
  return(CMxOK);		/* all done */
}



/* qsthlp - Decide what the quote situation is, then give the appropriate
** message.
**/

PASSEDSTATIC int
qsthlp(text,textlen,fdbp,cust)
char *text;
int textlen,cust;
fdb *fdbp;
{
  brktab *btab;			/* break table in use */
  int qmsg = TRUE;		/* use "Quote" message if TRUE */
  int i;
  char c;

  if (fdbp->_cmbrk != NULL)
    btab = fdbp->_cmbrk;	/* get user break table */
  else
    btab = &qstbrk;

  for (i = 0; i < 16; i++)	/* check for matching 1st char brk array */
    if (btab->_br1st[i] != qstbrk._br1st[i]) {
      qmsg = FALSE;		/* no match - use other message */
      break;
    }
    
  if (qmsg)			/* standard quote set? */
    cmxputs("quoted string");	/* use standard message */
  else if (textlen == 0)	/* nonstandard quotes... delimiter given? */
    cmxputs("delimited string"); /* use alternate message */
  else {			/* quote known... include it in message */
    cmxputs("string terminated by "); /* beginning of message */
    c = *text & CC_CHR;	/* get quote character */
    if (c == TAB)
      cmxputs("tab");		/* take care of some special cases */
    else if (c == NEWLINE)
      cmxputs("newline");
    else if (c == '\'')
      cmxputs("\" \' \"");
    else {
      cmxputc('\'');		/* standard case... surround with squotes */
      cmechx(c);
      cmxputc('\'');
    }
  }
  return(CMxOK);
}



/* qstcplt - Beep for anything but full completion when a proper
** quoted string is there but not yet terminated.  In that case,
** if the quote character is a space, beep.  Else return a single
** space as completion text.
**/

PASSEDSTATIC int
qstcplt(text,textlen,fdbp,full,cplt,cpltlen)
char *text,**cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
{
  char quote;			/* character used as quote */
  int qstlen;			/* length of (undoubled) quoted string */
  char *term;			/* pointer to char after closing quote */
  int ret;			/* returned by auxiliary routine */

  ret = fndqst(text,textlen,fdbp,&quote,&qstlen,&term); /* find the string */

  *cplt = NULL;			/* no completion text */
  if (!full || (ret == CMxINC)) /* partial completion or no closing quote? */
    return(CMP_BEL);		/* then just beep at them */
  else if (quote == SPACE)	/* also beep if quote char is space */
    return(CMP_BEL);
  else
    return(CMP_SPC | CMP_GO);	/* otherwise, stuff a space and wakeup */
}



/* fndqst - Auxiliary routine for quoted string handlers.
**
** Purpose:
**   Isolate a quoted string in the given text.
**
** Input arguments:
**   text - A pointer to the text to be scanned.
**   textlen - The number of chars to be scanned.
**   fdbp - A pointer to the FDB for the current parse.
**
** Output arguments:
**   quote - The character being used for quoting.
**   qstlen - The number of chars in the string (counting doubled quot
**     characters only once, and not counting opening and closing
**     quotes).
**   term - A pointer to the character terminating the field, that is,
**     following the closing quote (not the closing quote itself).  NULL
**     if there is no such character (the text ran out).
**
** Returns: Standard return code.
**/

static int
fndqst(text,textlen,fdbp,quote,qstlen,term)
char *text, *quote, **term;
int textlen, *qstlen;
fdb *fdbp;
{
  int qseen = FALSE;		/* TRUE when last char was a quote char */
  char c;			/* characters from text */
  brktab *btab;			/* break table for the parse */

  if (textlen == 0)
    return(CMxINC);		/* nothing to parse */

  if ((btab = fdbp->_cmbrk) == NULL) /* get supplied break table */
    btab = &qstbrk;		/* or use default */

  *qstlen = 0;			/* start length count at zero */
  *quote = *text++;		/* pick up quote character */
  textlen--;			/* and count it */
  
  if ((*quote & CC_QUO) == 0) {	/* first char unquoted? */
    if (BREAK1(btab,*quote))	/* and a 1st position break char? */
      return(QSTxQC);		/* bad quote character */
  }
  else
    *quote &= CC_CHR;		/* turn off quoted flag in returned char */

  while (textlen-- > 0) {	/* scan characters */
    c = *text++;		/* pick up next char */
    if ((c & CC_CHR) == *quote) /* quote character? */
      if (qseen) {		/* second in a doubled pair? */
	(*qstlen)++;		/* yup, bump length count */
	qseen = FALSE;		/* and turn off flag */
      }
      else
	qseen = TRUE;		/* by itself - set flag */

    else			/* not a quote */
      if (qseen) {		/* following a quote? */
	*term = text-1;		/* yup, it's the terminator */
	return(CMxOK);		/* so we're done */
      }
      else {			/* otherwise part of the string */
	if ((c & CC_QUO) == 0)	/* if it's unquoted */
          if (BREAKR(btab,c))	/* make sure it's allowed */
	    return(QSTxBC);
	(*qstlen)++;		/* ok, bump count */
      }
  }
  *term = NULL;			/* fall thru when no terminator present */
  if (qseen)
    return(CMxOK);		/* last char was quote - it's complete */
  else
    return(CMxINC);		/* else still waiting */
}
