#include "config.h"
/*
 * Copyright (c) 1986, 2014 by The Trustees of Columbia University in
 * the City of New York.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  + Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  + Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 *  + Neither the name of Columbia University nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.

 Author: Andrew Lowry
*/
/* 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 */

PASSEDSTATIC int qstcplt ARGS((char *text, int textlen, fdb *fdbp,
	 int full, const char **cplt, int *cpltlen));
PASSEDSTATIC int qsthlp ARGS((char *text, int textlen, fdb *fdbp,
	 int cust, int lines));
PASSEDSTATIC int qstprs ARGS((char *text, int textlen, fdb *fdbp,
	 int *parselen, pval *value));
static int fndqst ARGS((char *text, int textlen, fdb *fdbp, char *quote,
	 int *qstlen, char **term));

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
#if HAVE_STDC
qstprs(char *text, int textlen, fdb *fdbp, int *parselen, pval *value)
#else /* K&R style */
qstprs(text,textlen,fdbp,parselen,value)
char *text;
int textlen,*parselen;
fdb *fdbp;
pval *value;
#endif /* HAVE_STDC */
{
  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;

  cmcsb._cmflg2 |= CM_NIN;
  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 */
  cmcsb._cmflg2 &= ~CM_NIN;
  value->_pvstr = cmcsb._cmabp;
  return(CMxOK);		/* all done */
}



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

PASSEDSTATIC int
#if HAVE_STDC
qsthlp(char *text, int textlen, fdb *fdbp, int cust, int lines)
#else /* K&R style */
qsthlp(text,textlen,fdbp,cust,lines)
char *text;
int textlen,cust;
fdb *fdbp;
int lines;
#endif /* HAVE_STDC */
{
  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((int)(c));
      cmxputc('\'');
    }
  }
  return(lines-1);
}



/* 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
#if HAVE_STDC
qstcplt(char *text, int textlen, fdb *fdbp, int full, const char **cplt, int *cpltlen)
#else /* K&R style */
qstcplt(text,textlen,fdbp,full,cplt,cpltlen)
char *text;
const char **cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
#endif /* HAVE_STDC */
{
  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
#if HAVE_STDC
fndqst(char *text, int textlen, fdb *fdbp, char *quote, int *qstlen, char **term)
#else /* K&R style */
fndqst(text,textlen,fdbp,quote,qstlen,term)
char *text, *quote, **term;
int textlen, *qstlen;
fdb *fdbp;
#endif /* HAVE_STDC */
{
  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 */
}
