/*-
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * and/or other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <_ansi.h>
#include <newlib.h>
#include <ctype.h>
#include <wctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>
#include <wchar.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include "local.h"
#include "../stdlib/local.h"
#include "nano-vfscanf_local.h"

#ifdef FLOATING_POINT
int
_scanf_float (struct _reent *rptr,
	      struct _scan_data_t *pdata,
	      FILE *fp, va_list *ap)
{
  int c;
  char *p;
  float *flp;
  _LONG_DOUBLE *ldp;

  /* Scan a floating point number as if by strtod.  */
  /* This code used to assume that the number of digits is reasonable.
     However, ANSI / ISO C makes no such stipulation; we have to get
     exact results even when there is an unreasonable amount of leading
     zeroes.  */
  long leading_zeroes = 0;
  long zeroes, exp_adjust;
  char *exp_start = NULL;
  unsigned width_left = 0;
  char nancount = 0;
  char infcount = 0;
#ifdef hardway
  if (pdata->width == 0 || pdata->width > BUF - 1)
#else
  /* size_t is unsigned, hence this optimisation.  */
  if (pdata->width - 1 > BUF - 2)
#endif
    {
      width_left = pdata->width - (BUF - 1);
      pdata->width = BUF - 1;
    }
  pdata->flags |= SIGNOK | NDIGITS | DPTOK | EXPOK;
  zeroes = 0;
  exp_adjust = 0;
  for (p = pdata->buf; pdata->width; )
    {
      c = *fp->_p;
      /* This code mimicks the integer conversion code,
	 but is much simpler.  */
      switch (c)
	{
	case '0':
	  if (pdata->flags & NDIGITS)
	    {
	      pdata->flags &= ~SIGNOK;
	      zeroes++;
	      if (width_left)
		{
		  width_left--;
		  pdata->width++;
		}
	      goto fskip;
	    }
	/* Fall through.  */
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	  if (nancount + infcount == 0)
	    {
	      pdata->flags &= ~(SIGNOK | NDIGITS);
	      goto fok;
	    }
	  break;

	case '+':
	case '-':
	  if (pdata->flags & SIGNOK)
	    {
	      pdata->flags &= ~SIGNOK;
	      goto fok;
	    }
	  break;
	case 'n':
	case 'N':
	  if (nancount == 0 && zeroes == 0
	      && (pdata->flags & (NDIGITS | DPTOK | EXPOK)) ==
				  (NDIGITS | DPTOK | EXPOK))
	    {
	      pdata->flags &= ~(SIGNOK | DPTOK | EXPOK | NDIGITS);
	      nancount = 1;
	      goto fok;
	    }
	  if (nancount == 2)
	    {
	      nancount = 3;
	      goto fok;
	    }
	  if (infcount == 1 || infcount == 4)
	    {
	      infcount++;
	      goto fok;
	    }
	  break;
	case 'a':
	case 'A':
	  if (nancount == 1)
	    {
	      nancount = 2;
	      goto fok;
	    }
	  break;
	case 'i':
	case 'I':
	  if (infcount == 0 && zeroes == 0
	      && (pdata->flags & (NDIGITS | DPTOK | EXPOK)) ==
				  (NDIGITS | DPTOK | EXPOK))
	    {
	      pdata->flags &= ~(SIGNOK | DPTOK | EXPOK | NDIGITS);
	      infcount = 1;
	      goto fok;
	    }
	  if (infcount == 3 || infcount == 5)
	    {
	      infcount++;
	      goto fok;
	    }
	  break;
	case 'f':
	case 'F':
	  if (infcount == 2)
	    {
	      infcount = 3;
	      goto fok;
	    }
	  break;
	case 't':
	case 'T':
	  if (infcount == 6)
	    {
	      infcount = 7;
	      goto fok;
	    }
	  break;
	case 'y':
	case 'Y':
	  if (infcount == 7)
	    {
	      infcount = 8;
	      goto fok;
	    }
	  break;
	case '.':
	  if (pdata->flags & DPTOK)
	    {
	      pdata->flags &= ~(SIGNOK | DPTOK);
	      leading_zeroes = zeroes;
	      goto fok;
	    }
	  break;
	case 'e':
	case 'E':
	  /* No exponent without some digits.  */
	  if ((pdata->flags & (NDIGITS | EXPOK)) == EXPOK
	      || ((pdata->flags & EXPOK) && zeroes))
	    {
	      if (! (pdata->flags & DPTOK))
		{
		  exp_adjust = zeroes - leading_zeroes;
		  exp_start = p;
	        }
	      pdata->flags =
		(pdata->flags & ~(EXPOK | DPTOK)) | SIGNOK | NDIGITS;
	      zeroes = 0;
	      goto fok;
	    }
	  break;
	}
      break;
fok:
      *p++ = c;
fskip:
      pdata->width--;
      ++pdata->nread;
      if (--fp->_r > 0)
	fp->_p++;
      else if (pdata->pfn_refill (rptr, fp))
	/* "EOF".  */
	break;
    }
  if (zeroes)
    pdata->flags &= ~NDIGITS;
  /* We may have a 'N' or possibly even [sign] 'N' 'a' as the
     start of 'NaN', only to run out of chars before it was
     complete (or having encountered a non-matching char).  So
     check here if we have an outstanding nancount, and if so
     put back the chars we did swallow and treat as a failed
     match.

     FIXME - we still don't handle NAN([0xdigits]).  */
  if (nancount - 1U < 2U)
    {
      /* "nancount && nancount < 3".  */
      /* Newlib's ungetc works even if we called __srefill in
	 the middle of a partial parse, but POSIX does not
	 guarantee that in all implementations of ungetc.  */
      while (p > pdata->buf)
	{
	  pdata->pfn_ungetc (rptr, *--p, fp); /* "[-+nNaA]".  */
	  --pdata->nread;
	}
      return MATCH_FAILURE;
    }
  /* Likewise for 'inf' and 'infinity'.	 But be careful that
     'infinite' consumes only 3 characters, leaving the stream
     at the second 'i'.	 */
  if (infcount - 1U < 7U)
    {
      /* "infcount && infcount < 8".  */
      if (infcount >= 3) /* valid 'inf', but short of 'infinity'.  */
	while (infcount-- > 3)
	  {
	    pdata->pfn_ungetc (rptr, *--p, fp); /* "[iInNtT]".  */
	    --pdata->nread;
	  }
      else
        {
	  while (p > pdata->buf)
	    {
	      pdata->pfn_ungetc (rptr, *--p, fp); /* "[-+iInN]".  */
	      --pdata->nread;
	    }
	  return MATCH_FAILURE;
	}
    }
  /* If no digits, might be missing exponent digits
     (just give back the exponent) or might be missing
     regular digits, but had sign and/or decimal point.  */
  if (pdata->flags & NDIGITS)
    {
      if (pdata->flags & EXPOK)
	{
	  /* No digits at all.  */
	  while (p > pdata->buf)
	    {
	      pdata->pfn_ungetc (rptr, *--p, fp); /* "[-+.]".  */
	      --pdata->nread;
	    }
	  return MATCH_FAILURE;
	}
      /* Just a bad exponent (e and maybe sign).  */
      c = *--p;
      --pdata->nread;
      if (c != 'e' && c != 'E')
	{
	  pdata->pfn_ungetc (rptr, c, fp); /* "[-+]".  */
	  c = *--p;
	  --pdata->nread;
	}
      pdata->pfn_ungetc (rptr, c, fp); /* "[eE]".  */
    }
  if ((pdata->flags & SUPPRESS) == 0)
    {
      double fp;
      long new_exp = 0;

      *p = 0;
      if ((pdata->flags & (DPTOK | EXPOK)) == EXPOK)
	{
	  exp_adjust = zeroes - leading_zeroes;
	  new_exp = -exp_adjust;
	  exp_start = p;
	}
      else if (exp_adjust)
	new_exp = strtol ((exp_start + 1), NULL, 10) - exp_adjust;

      if (exp_adjust)
	{
	  /* If there might not be enough space for the new exponent,
	     truncate some trailing digits to make room.  */
	  if (exp_start >= pdata->buf + BUF - MAX_LONG_LEN)
	    exp_start = pdata->buf + BUF - MAX_LONG_LEN - 1;
	  sprintf (exp_start, "e%ld", new_exp);
	}

      /* Current strtold routine is markedly slower than
	 strtod.  Only use it if we have a long double
	 result.  */
      fp = strtod (pdata->buf, NULL);

      /* Do not support long double.  */
      if (pdata->flags & LONG)
	*GET_ARG (N, *ap, double *) = fp;
      else if (pdata->flags & LONGDBL)
	{
	  ldp = GET_ARG (N, *ap, _LONG_DOUBLE *);
	  *ldp = fp;
	}
      else
	{
	  flp = GET_ARG (N, *ap, float *);
	  if (isnan (fp))
	    *flp = nanf ("");
	  else
	    *flp = fp;
	}
      pdata->nassigned++;
    }
  return 0;
}
#endif

