#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
*/
/* cmnum
**
** This module contains the standard break table and handlers for
** parsing integers.  Parsing succeeds if current input contains an
** integer in a given radix (up to 16), and the return value is the int
** value parsed.  Optional flags can specify that a sign may or may not
** prefix the integer, and whether a successful parse should result if
** the integer is terminated by anything other than punctuation (as in
** "12am" as opposed to "12 am").  If parse-only is specified in the
** FDB (flag NUM_PO), the syntax of the number is checked, but no attempt
** at binary conversion is made, so overflow errors cannot occur.  In this
** case, the return value is the (possibly signed) string representing
** the integer.  It is left in the atom buffer.
**
** Full completion simply terminates current input with a space if
** there is a valid integer.  If the input is empty or consists only
** of a sign, full completion beeps.  Partial completion always beeps.
** The standard break table allows digits, and letters A-F (and a-f).
** Minus and plus signs are also allowed in first position.
**/

#define	NUMERR			/* declare cmnum parse errors here */

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

/* Forward declaration of handler routines */

PASSEDSTATIC int numcplt ARGS((char *text, int textlen, fdb *fdbp,
	 int full, const char **cplt, int *cpltlen));
PASSEDSTATIC int numhlp ARGS((char *text, int textlen, fdb *fdbp,
	 int cust, int lines));
PASSEDSTATIC int numprs ARGS((char *text, int textlen, fdb *fdbp,
	 int *parselen, pval *value));
static int prsnum ARGS((char *text, int textlen, flag_t flags, int radix,
	 int *sign, char **digits, int *ndig, char **term));

static brktab numbrk = {
  {					/* 1st char break array */
    0xff, 0xff, 0xff, 0xff, 0xff, 0xeb, 0x00, 0x3f, /* allow - and + */
    0x81, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff
  },
  {					/* subsequent char break array */
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, /* disallow - and + */
    0x81, 0xff, 0xff, 0xff, 0x81, 0xff, 0xff, 0xff
  }
};

ftspec ft_num = { numprs, numhlp, numcplt, 0, &numbrk }; /* handler structure */



/* numprs - Locate the digit string in the current input.  If there is
** none, error.  If NUM_PO is on in the FDB, the integer is parsed and
** returned as a string, with no attempt at binary conversion.  Otherwise
** convert to binary, keeping watch for overflow.
**/

PASSEDSTATIC int
#if HAVE_STDC
numprs(char *text, int textlen, fdb *fdbp, int *parselen, pval *value)
#else /* K&R style */
numprs(text,textlen,fdbp,parselen,value)
char *text;
int textlen,*parselen;
fdb *fdbp;
pval *value;
#endif /* HAVE_STDC */
{
  char *digits,*term;		/* for decomposing the input */
  int sign;
  int ndig;			/* number of digits */
  int radix;			/* radix specified in the FDB */
  flag_t flags;			/* flags specified in the FDB */
  unsigned int valhi,vallo;	/* high and low pieces of binary value */
  int dig;			/* integer value of individual digit */
  int intbits;			/* number of bits per integer */
  int ret;			/* return code from aux routine */

  radix = (int) fdbp->_cmdat;	/* get the radix */
  flags = fdbp->_cmffl;		/* and parse flags */
				/* parse the input */
  ret = prsnum(text,textlen,flags,radix,&sign,&digits,&ndig,&term);
  if (ret != CMxOK)
    return(ret);		/* propagate problems */
  if (term == NULL)
    return(CMxINC);		/* unterminated field is incomplete */
  if (digits == NULL)
    return(NUMxNP);		/* terminated but no digits */
  if (ndig < 1)			/* check ndig too - chris */
    return (NUMxNP);		/* terminated but no digits */
  if (flags & NUM_PO) {		/* parse only? */
    value->_pvstr = cmcsb._cmabp; /* yup, string value in atom buffer */
    *parselen = term - text;	/* compute number of characters */
    return(CMxOK);
  }
  valhi = (unsigned int)(-1);		/* count bits */
  for (intbits = 0; valhi != 0; intbits++)
    valhi <<= 1;		/* shift out bits until zeros remain */

  valhi = vallo = 0;		/* start with zero, then add in digits */
  while (ndig-- > 0) {		/* loop through all the digits */
    vallo *= radix;		/* multiply both pieces by radix */
    valhi *= radix;
    dig = ((*digits++) & CC_CHR) - '0'; /* get value of next digit */
    if (dig > 9)
      dig -= ('A'-'0')-10;	/* adjust non-numeric digits */
    if (dig > 15)
      dig -= 'a'-'A';		/* further adjust lowercase */
    vallo += dig;		/* add in the new digit */
    valhi += vallo >> 4;	/* leave only 4 bits in low part */
    vallo &= 0xf;
    if ((valhi >> (intbits-4)) != 0)
      return(NUMxOV);		/* overflow */
  }				/* go do next digit */
  if (flags & NUM_US)		/* unsigned integer? */
    valhi = (valhi << 4) | vallo; /* shift all bits into high piece */
  else if (sign == -1)		/* need to negate it? */
    if ((valhi >> (intbits-5)) == 0) /* in range for positive? */
      valhi = -((valhi << 4) | vallo); /* yup, make abs val and negate */
    else if (((valhi << 5) == 0) && (vallo == 0))
      valhi = 1 << (intbits-1);	/* no, handle special case -2^(intbits-1) */
    else
      return(NUMxOV);		/* overflow */
  else				/* positive signed integer */
    if ((valhi >> (intbits-5)) == 0) /* in range? */
      valhi = (valhi << 4) | vallo; /* yup, put pieces together */
    else
      return(NUMxOV);		/* nope, overflow */

  value->_pvint = (int) valhi;	/* set value in place */
  *parselen = term - text;	/* compute consumed region */
  return(CMxOK);		/* and give a good parse */
}



/* numcplt - Partial completion always comes up empty.  Full completion
** gets a space if there are any digits in the buffer.
**/

PASSEDSTATIC int
#if HAVE_STDC
numcplt(char *text, int textlen, fdb *fdbp, int full, const char **cplt, int *cpltlen)
#else /* K&R style */
numcplt(text,textlen,fdbp,full,cplt,cpltlen)
char *text;
const char **cplt;
int textlen,full,*cpltlen;
fdb *fdbp;
#endif /* HAVE_STDC */
{
  int radix,flags;		/* radix and flags for parse */
  char *digits,*term;		/* decomposed input */
  int sign,ndig;

  radix = (int) fdbp->_cmdat;	/* get radix and flags */
  flags = fdbp->_cmffl;
				/* try to parse the data */
  (void)prsnum(text,textlen,flags,radix,&sign,&digits,&ndig,&term);

  *cplt = NULL;			/* we never supply completion text */
  if (!full || (ndig == 0))	/* partial or no digits yet? */
    return(CMP_BEL);		/* then just beep */
  else
    return(CMP_SPC | CMP_GO);	/* full gets a space and wakeup */
}



/* numhlp - Just give a message customized by the requested radix.
**/

PASSEDSTATIC int
#if HAVE_STDC
numhlp(char *text, int textlen, fdb *fdbp, int cust, int lines)
#else /* K&R style */
numhlp(text,textlen,fdbp,cust,lines)
char *text;
int textlen,cust;
fdb *fdbp;
int lines;
#endif /* HAVE_STDC */
{
  int radix;			/* requested radix and flags */
  flag_t flags;
  int caseadj = 0;		/* used when lowercasing initials */

/* macro to print upper or lowercase char depending on value of caseadj */
#define caseout(x) cmxputc((char) (x)+caseadj)

  radix = (int) fdbp->_cmdat;	/* get flags and radix */
  flags = fdbp->_cmffl;

  if (flags & NUM_US) {
    cmxputs("Unsigned ");	/* indicate if signs not allowed */
    caseadj = 'a'-'A';		/* set up for lowercase initials */
  }
  switch (radix) {
    case 2: caseout('B'); cmxputs("inary integer"); break;
    case 8: caseout('O'); cmxputs("ctal integer"); break;
    case 10: caseout('D'); cmxputs("ecimal integer"); break;
    case 16: caseout('H'); cmxputs("exadecimal integer"); break;
    default: caseout('I'); cmxputs("nteger in base ");
      if (radix >= 10)		/* output radix in decimal */
	cmxputc('1');
      cmxputc((char) (radix % 10) + '0');
      break;
  }
  return(lines-1);
}



/* prsnum - Auxiliary routine for parse and completion.
**
** Purpose:
**   Breaks the current input into three pieces - sign, digits, and
**   terminator, and checks to make sure that restrictions called
**   for in the parse data flags are observed.
**
** Input arguments:
**   text - Pointer to input text.
**   textlen - Number of characters of input text.
**   flags - Flags supplied in the FDB.
**   radix - Radix specified in the FDB.
**
** Output arguments:
**   sign - 0 for no sign, 1 for +, -1 for -.
**   digits - Points to beginning of digit string.
**   ndig - Number of digits in digit string.
**   term - Points to terminating character.
**
** Returns: Standard error code.
**/

static int
#if HAVE_STDC
prsnum(char *text, int textlen, flag_t flags, int radix, int *sign, char **digits, int *ndig, char **term)
#else /* K&R style */
prsnum(text,textlen,flags,radix,sign,digits,ndig,term)
char *text, **digits, **term;
int textlen;
flag_t flags;
int radix, *sign, *ndig;
#endif /* HAVE_STDC */
{
  int dig;			/* value of individual digit */
  char c;

  if ((radix < 2) || (radix > 16)) /* check for bad radix */
    return(NUMxRAD);

  *ndig = 0;			/* no digits scanned yet */
  *sign = 0;			/* assume no sign */
  *digits = text;		/* so digits start right away */
  *term = NULL;			/* and no terminator given */

  if (textlen == 0)
    return(CMxOK);		/* no text - all assumptions true */

  c = *text & CC_CHR;		/* pick up first char */
  if ((c == '+') || (c == '-')) { /* sign at front? */
    if (flags & NUM_US)		/* yup, check unsigned flag */
      return(NUMxSGN);		/* was not supposed to be there */
    *sign = (c == '+') ? 1 : -1; /* set the sign value */
    *digits = ++text;		/* move digit start */
    textlen--;			/* and count the character*/
  }

  while (textlen-- > 0) {
    c = *text & CC_CHR;		/* get next digit */
    if (isdigit(c))
      dig = c - '0';		/* adjust numeric digits */
    else if (isupper(c))
      dig = (c - 'A') + 10;	/* adjust uppercase digits */
    else if (islower(c))
      dig = (c - 'a') + 10;	/* adjust lowercase digits */
    else
      dig = 16;			/* anything else invalid */
    if (dig >= radix)		/* terminator found? */
      if (!isalnum(c) || (flags & NUM_BNP)) { /* valid terminator? */
	*term = text;		/* point at terminator */
        return(CMxOK);		/* and return */
      }
      else
	return(NUMxNP);		/* bad terminator */
    else {
      (*ndig)++;		/* else bump digit counter */
      text++;			/* and move on */
    }
  }
  return(CMxOK);
}
