/*-
 * 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,
 * advertising materials, and 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 <reent.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 "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))
			break;		/* EOF */
	}
	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_r (rptr, (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_r.  Only use it if we have a long double
		   result.  */
		fp = _strtod_r (rptr, 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 (NULL);
			else
				*flp = fp;
		}
		pdata->nassigned++;
	}
    return 0;
}
#endif

