#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
*/
/* cmnoi
**
** This module contains the default break table and handlers for
** parsing noise words.  Parsing succeeds if the current input
** begins with a left paren, then matches the given noise word
** modulo case and whitespace, then closes with a right paren.
** If the current input begins with anything other than a left
** paren, parsing also succeeds, but no characters are consumed.
** Unusual handling is performed when the current input is empty
** and the CM_PFE flag is on in the CSB.  In this case, the noise
** word with parentheses is stuffed into the command buffer, and
** another parse attempt is requested.  (We cannot merely succeed,
** since the characters we stuff into the buffer are not in the
** text buffer that was passed to us.  The caller must rebuild
** this text buffer for us.)  Another unusual case is when the
** input is empty and the CM_ACT flag is on in the CSB, indicating
** that action is pending.  In this case, the parse succeeds,
** consuming no characters, so that the action will apply to
** the next real field, not to the noise word.  Finally,
** noise word parses do not affect the atom buffer.
**
** Completion can only occur with a non-empty buffer (due to the
** special action with CM_ACT and an empty buffer just described).
** (This is not true if completion is immediate on some key, but if
** that's the case, something strange is probably going on anyway.)
** The completion text consists of the remainder of the noise word,
** with closing paren and trailing space.  Partial completion will
** complete only up to and including the next punctuation, and will
** not add a trailing blank.
**
** Help, like completion, should never occur at the beginning of
** a noise word.  It consists of the string, "Noise word."
**
** The break table has no effect on the parsing of a noise word.
** The default is set to include only the printing characters
** other than question mark, so that standard actions will be
** active.  For first character, everything but left paren breaks,
** so that CM_WKF parsing will work correctly.
**/

#define	NOIERR			/* declare cmnoi parse errors here */

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

/* Forward declarations of handlers */

PASSEDSTATIC int noicplt ARGS((char *text, int textlen, fdb *fdbp,
	 int full, const char **cplt, int *cpltlen));
PASSEDSTATIC int noihlp ARGS((char *text, int textlen, fdb *fdbp,
	 int cust, int lines));
PASSEDSTATIC int noiprs ARGS((char *text, int textlen, fdb *fdbp,
	 int *parselen, pval *value));
static int fndnoi ARGS((const char *text, int textlen, const char *noise_,
	const char **cplt, const char **term));
static int skipws ARGS((const char **text, int *textlen, int mandatory));

static brktab noibrk = {
  {					/* only open paren allowed first */
    0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
  },
  {					/* all print chars but '?' */
    0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
  }
};

ftspec ft_noi = { noiprs, noihlp, noicplt, 0, &noibrk }; /* handler structure */



/* noiprs - If current input is empty and CM_PFE is on, stuff the noise
** word and ask for another try.  If empty and CM_ACT is on, succeed
** consuming nothing.  If nonempty and 1st char is not paren, succeed
** consuming nothing.  Otherwise succeed if the rest matches the noise
** word string followed by a right paren and another character.
**/

PASSEDSTATIC int
#if HAVE_STDC
noiprs(char *text, int textlen, fdb *fdbp, int *parselen, pval *value)
#else /* K&R style */
noiprs(text,textlen,fdbp,parselen,value)
char *text;
int textlen,*parselen;
fdb *fdbp;
pval *value;
#endif /* HAVE_STDC */
{
  int ret;			/* results of stuffs */
  const char *term;		/* pointer to char after close paren */
  const char *cplt;		/* unused arg to fndnoi */

  cmcsb._cmflg |= CM_NAC;	/* protect the atom buffer */
  if (textlen == 0) {		/* empty buffer? */
    if (cmcsb._cmflg & CM_PFE) {/* and prior field got completion? */
      cmcsb._cmflg |= (CM_ACT|CM_ESC);	/* keep the action going. */
      return(CMxINC);
    }
    else if (cmcsb._cmflg & CM_ACT) { /* empty and action waiting? */
      *parselen = 0;		/* consume nothing */
      return(CMxOK);		/* and succeed */
    }
    else
      return(CMxINC);		/* empty, no PFE, no action waiting */
  }
				/* find noise in the input */
  ret = fndnoi(text,textlen,(const char *) fdbp->_cmdat,&cplt,&term);
  if (ret == NOIxNP) {		/* not a noise word */
    *parselen = 0;		/* consume nothing */
    if (cmcsb._cmcur != cmcsb._cmptr + cmcsb._cminc)
	cmcsb._cmflg |= CM_ACT;	/* keep the action going. */
    return(CMxOK);		/* and succeed */
  }
  if (ret != CMxOK) {
    return(ret);		/* propagate errors */
  }
  if (term == NULL)
    return(CMxINC);		/* not terminated -- need more input */
  *parselen = term-text;	/* compute length of noise */
  return(CMxOK);		/* and succeed */
}



/* noihlp - Always print "Noise word" */

PASSEDSTATIC int
#if HAVE_STDC
noihlp(char *text, int textlen, fdb *fdbp, int cust, int lines)
#else /* K&R style */
noihlp(text,textlen,fdbp,cust,lines)
char *text;
int textlen,cust;
fdb *fdbp;
int lines;
#endif /* HAVE_STDC */
{
  cmxputs("noise word");
  return(lines-1);
}



/* noicplt - Just finish up the noise where the current input
** leaves off.  Full completion also adds a space, partial
** completion never does.
**/

PASSEDSTATIC int
#if HAVE_STDC
noicplt(char *text, int textlen, fdb *fdbp, int full, const char **cplt, int *cpltlen)
#else /* K&R style */
noicplt(text,textlen,fdbp,full,cplt,cpltlen)
char *text;
const char **cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
#endif /* HAVE_STDC */
{
  int ret;			/* return code from aux routine */
  const char *term;		/* char after noise word if any */
  static char *work = NULL;	/* dynamic area to build completion */
  char *cp;			/* for copying completion text */
  int badalc = FALSE;		/* true if malloc fails below */

  static char right_paren[] = ")";

  if (work != NULL) {		/* buffer hanging around from last time? */
    free(work);			/* get rid of it */
    work = NULL;		/* and mark it released */
  }
				/* find noise in the input */
  if (textlen == 0) {
    work = (char*)malloc(strlen(fdbp->_cmdat) + 3);
    sprintf(work, "(%s)", fdbp->_cmdat);
    *cpltlen = strlen(work);
    *cplt = work;
  }
  else {
    ret = fndnoi(text,textlen,(char *) fdbp->_cmdat,cplt,&term);

    *cpltlen = -1;		/* always complete to end of string */
    if (*cplt == NULL) {	/* noise is all there */
      if (ret == CMxINC)	/* no closing paren */
	*cplt = right_paren;	/* so give them one */
    }
    else {			/* noise not all there */
      work = (char*)malloc(strlen(*cplt)+2); /* noise, paren, and null */
      if (work != NULL) {	/* allocation succeeded? */
	cp = work;		/* point to work space */
	while (*cp++ = *(*cplt)++); /* copy remainder of noise word */
	*(--cp) = ')';		/* finish with right paren */
	*(++cp) = NULCHAR;	/* and tie off with a null */
	*cplt = work;		/* point to constructed text */
      }
      else
	badalc = TRUE;		/* flag allocation failure */
    }
  }
  if (full)			/* full completion */
    if (badalc)
      return(0);		/* just fill in if allocation failed */
    else
      return(CMP_SPC | CMP_GO);	/* otherwise add a space and a wakeup */
  else
    return(CMP_PNC);		/* partial - complete to punctuation */
}



/* fndnoi - Auxiliary routine for noise word handlers
**
** Determines how much of the noise word is in the input buffer,
** and indicates where completion, if any, should start.
**
** Input parameters:
**   text - The input text to be parsed.
**   textlen - Size of the input text.
**   noise - Pointer to the noise word.
**
** Output parameters:
**   cplt - A pointer to the start of completion text within the noise.
**   term - A pointer to the character following the right paren if there
**     was one in the input.
**
** Returns: Standard error code.  CMxOK means complete noise word with
**   following terminator.  CMxINC means good match so far, but needs
**   more for a CMxOK.  Anything else is error.
**/

static int
#if HAVE_STDC
fndnoi(const char *text, int textlen, const char *noise, const char **cplt, const char **term)
#else /* K&R style */
fndnoi(text,textlen,noise,cplt,term)
const char *text;
const char *noise;
const char **cplt;
const char **term;
int textlen;
#endif /* HAVE_STDC */
{
  char cc,tc;			/* completion and text chars */
  int ret;
  int inws = FALSE;		/* TRUE during whitespace in completion */

  *term = NULL;			/* assume no terminator */
  *cplt = noise;		/* start completion all the way back */

  if (*text == '(') {		/* noise starts with a paren */
    text++;
    textlen--;
  }
  else {
    return(NOIxNP);		/* then bad parse */
  }

  ret = skipws(&text,&textlen,FALSE); /* optional space after open paren */
  if (ret != CMxOK)
    return(ret);			/* propagate errors */

  while ((cc = **cplt) != NULCHAR) /* loop to end of noise */
    if ((cc == SPACE) || (cc == TAB)) /* hit some white space */
      if (inws)
	(*cplt)++;		/* already in it... keep looking */
      else {
	ret = skipws(&text,&textlen,TRUE); /* mandatory ws in text */
	if (ret != CMxOK)
	  return(ret);		/* propagate problems */
        inws = TRUE;		/* don't need it again */
	(*cplt)++;		/* and consume the character */
      }
    else if (textlen == 0)	/* more noise and no more text? */
      return(CMxINC);		/* yup, ask for more text */
    else {
      inws = FALSE;		/* no longer in white space */
      tc = (*text++) & CC_CHR;	/* try to match noise */
      if ((cc >= 'a') && (cc <= 'z'))
	cc -= 'a'-'A';		/* uppercase both chars */
      if ((tc >= 'a') && (tc <= 'z'))
	tc -= 'a'-'A';
      if (cc != tc)		/* different chars? */
	return(NOIxNP);		/* then no parse */
      textlen--;		/* else update counts and pointers */
      (*cplt)++;
    }				/* and keep looping */

  *cplt = NULL;			/* got through whole noise - no completion */

  ret = skipws(&text,&textlen,FALSE); /* optional space before closing */
  if (ret != CMxOK)
    return(ret);

  if (textlen == 0)		/* nothing left? */
    return(CMxINC);		/* we still need a closing paren */
  else if ((*text & CC_CHR) != ')') /* next char not closing paren? */
    return(NOIxNP);		/* then bad parse */
  else {
    if (textlen > 1)		/* something after closing paren? */
      *term = text+1;		/* yup, give them a pointer to it */
    return(CMxOK);		/* entire noise word parsed ok */
  }
}



/* skipws - aux routine for fndnoi.
**
** Purpose:
**   Skips spaces and tabs in the input text.  Fails if there are none
**   and the mandatory flag was on in the call.

** Input parameters:
**   text - Pointer to text to be scanned.
**   textlen - Number of chars in text.
**   mandatory - TRUE if an error should result if no space is there.
**
** Output parameters:
**   text, textlen - updated according to chars skipped.
**
** Returns: Standard error code.
**/

static int
#if HAVE_STDC
skipws(const char **text, int *textlen, int mandatory)
#else /* K&R style */
skipws(text,textlen,mandatory)
const char **text;
int *textlen,mandatory;
#endif /* HAVE_STDC */
{
  char c;
  while (*textlen > 0) {	/* scan whole text */
    c = **text & CC_CHR;	/* get next char */
    if ((c == SPACE) || (c == TAB)) {
      (*text)++;		/* found white space - skip it */
      (*textlen)--;		/* count it */
      mandatory = FALSE;	/* and no longer mandatory */
    }
    else
      break;			/* exit loop when non-ws found */
  }

  if (mandatory)		/* no ws found and it was required? */
    if (*textlen == 0)		/* yes, no input? */
      return(CMxINC);		/* then it's incomplete */
    else
      return(NOIxNP);		/* otherwise bad parse */
  else
    return(CMxOK);		/* otherwise it's fine */
}
