/* Copyright (C) 1994, 1995, 1997, 1998, 1999 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: iscannum.c,v 1.3.2.1.2.1 2003/01/17 00:49:04 giles Exp $ */
/* Number scanner for Ghostscript interpreter */
#include "math_.h"
#include "ghost.h"
#include "errors.h"
#include "scommon.h"
#include "iscannum.h"		/* defines interface */
#include "scanchar.h"
#include "store.h"

/*
 * Warning: this file has a "spaghetti" control structure.  But since this
 * code accounts for over 10% of the execution time of some PostScript
 * files, this is one of the few places we feel this is justified.
 */

/*
 * Scan a number.  If the number consumes the entire string, return 0;
 * if not, set *psp to the first character beyond the number and return 1.
 */
int
scan_number(const byte * str, const byte * end, int sign,
	    ref * pref, const byte ** psp)
{
    const byte *sp = str;
#define GET_NEXT(cvar, sp, end_action)\
  if (sp >= end) { end_action; } else cvar = *sp++

    /*
     * Powers of 10 up to 6 can be represented accurately as
     * a single-precision float.
     */
#define NUM_POWERS_10 6
    static const float powers_10[NUM_POWERS_10 + 1] = {
	1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6
    };
    static const double neg_powers_10[NUM_POWERS_10 + 1] = {
	1e0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6
    };

    int ival;
    long lval;
    double dval;
    int exp10;
    int code = 0;
    int c, d;
    const byte *const decoder = scan_char_decoder;
#define IS_DIGIT(d, c)\
  ((d = decoder[c]) < 10)
#define WOULD_OVERFLOW(val, d, maxv)\
  (val >= maxv / 10 && (val > maxv / 10 || d > (int)(maxv % 10)))

    GET_NEXT(c, sp, return_error(e_syntaxerror));
    if (!IS_DIGIT(d, c)) {
	if (c != '.')
	    return_error(e_syntaxerror);
	/* Might be a number starting with '.'. */
	GET_NEXT(c, sp, return_error(e_syntaxerror));
	if (!IS_DIGIT(d, c))
	    return_error(e_syntaxerror);
	ival = 0;
	goto i2r;
    }
    /* Accumulate an integer in ival. */
    /* Do up to 4 digits without a loop, */
    /* since we know this can't overflow and since */
    /* most numbers have 4 (integer) digits or fewer. */
    ival = d;
    if (end - sp >= 3) {	/* just check once */
	if (!IS_DIGIT(d, (c = *sp))) {
	    sp++;
	    goto ind;
	}
	ival = ival * 10 + d;
	if (!IS_DIGIT(d, (c = sp[1]))) {
	    sp += 2;
	    goto ind;
	}
	ival = ival * 10 + d;
	sp += 3;
	if (!IS_DIGIT(d, (c = sp[-1])))
	    goto ind;
	ival = ival * 10 + d;
    }
    for (;; ival = ival * 10 + d) {
	GET_NEXT(c, sp, goto iret);
	if (!IS_DIGIT(d, c))
	    break;
	if (WOULD_OVERFLOW(ival, d, max_int))
	    goto i2l;
    }
  ind:				/* We saw a non-digit while accumulating an integer in ival. */
    switch (c) {
	case '.':
	    GET_NEXT(c, sp, c = EOFC);
	    goto i2r;
	default:
	    *psp = sp;
	    code = 1;
	    break;
	case 'e':
	case 'E':
	    if (sign < 0)
		ival = -ival;
	    dval = ival;
	    exp10 = 0;
	    goto fe;
	case '#':
	    {
		const uint radix = (uint)ival;
		ulong uval = 0, lmax;

		if (sign || radix < min_radix || radix > max_radix)
		    return_error(e_syntaxerror);
		/* Avoid multiplies for power-of-2 radix. */
		if (!(radix & (radix - 1))) {
		    int shift;

		    switch (radix) {
			case 2:
			    shift = 1, lmax = max_ulong >> 1;
			    break;
			case 4:
			    shift = 2, lmax = max_ulong >> 2;
			    break;
			case 8:
			    shift = 3, lmax = max_ulong >> 3;
			    break;
			case 16:
			    shift = 4, lmax = max_ulong >> 4;
			    break;
			case 32:
			    shift = 5, lmax = max_ulong >> 5;
			    break;
			default:	/* can't happen */
			    return_error(e_rangecheck);
		    }
		    for (;; uval = (uval << shift) + d) {
			GET_NEXT(c, sp, break);
			d = decoder[c];
			if (d >= radix) {
			    *psp = sp;
			    code = 1;
			    break;
			}
			if (uval > lmax)
			    return_error(e_limitcheck);
		    }
		} else {
		    int lrem = max_ulong % radix;

		    lmax = max_ulong / radix;
		    for (;; uval = uval * radix + d) {
			GET_NEXT(c, sp, break);
			d = decoder[c];
			if (d >= radix) {
			    *psp = sp;
			    code = 1;
			    break;
			}
			if (uval >= lmax &&
			    (uval > lmax || d > lrem)
			    )
			    return_error(e_limitcheck);
		    }
		}
		make_int(pref, uval);
		return code;
	    }
    }
iret:
    make_int(pref, (sign < 0 ? -ival : ival));
    return code;

    /* Accumulate a long in lval. */
i2l:
    for (lval = ival;;) {
	if (WOULD_OVERFLOW(lval, d, max_long)) {
	    /* Make a special check for entering the smallest */
	    /* (most negative) integer. */
	    if (lval == max_long / 10 &&
		d == (int)(max_long % 10) + 1 && sign < 0
		) {
		GET_NEXT(c, sp, c = EOFC);
		dval = -(double)min_long;
		if (c == 'e' || c == 'E' || c == '.') {
		    exp10 = 0;
		    goto fs;
		} else if (!IS_DIGIT(d, c)) {
		    lval = min_long;
		    break;
		}
	    } else
		dval = lval;
	    goto l2d;
	}
	lval = lval * 10 + d;
	GET_NEXT(c, sp, goto lret);
	if (!IS_DIGIT(d, c))
	    break;
    }
    switch (c) {
	case '.':
	    GET_NEXT(c, sp, c = EOFC);
	    exp10 = 0;
	    goto l2r;
	case EOFC:
	    break;
	default:
	    *psp = sp;
	    code = 1;
	    break;
	case 'e':
	case 'E':
	    exp10 = 0;
	    goto le;
	case '#':
	    return_error(e_syntaxerror);
    }
lret:
    make_int(pref, (sign < 0 ? -lval : lval));
    return code;

    /* Accumulate a double in dval. */
l2d:
    exp10 = 0;
    for (;;) {
	dval = dval * 10 + d;
	GET_NEXT(c, sp, c = EOFC);
	if (!IS_DIGIT(d, c))
	    break;
    }
    switch (c) {
	case '.':
	    GET_NEXT(c, sp, c = EOFC);
	    exp10 = 0;
	    goto fd;
	default:
	    *psp = sp;
	    code = 1;
	    /* falls through */
	case EOFC:
	    if (sign < 0)
		dval = -dval;
	    goto rret;
	case 'e':
	case 'E':
	    exp10 = 0;
	    goto fs;
	case '#':
	    return_error(e_syntaxerror);
    }

    /* We saw a '.' while accumulating an integer in ival. */
i2r:
    exp10 = 0;
    while (IS_DIGIT(d, c)) {
	if (WOULD_OVERFLOW(ival, d, max_int)) {
	    lval = ival;
	    goto l2r;
	}
	ival = ival * 10 + d;
	exp10--;
	GET_NEXT(c, sp, c = EOFC);
    }
    if (sign < 0)
	ival = -ival;
    /* Take a shortcut for the common case */
    if (!(c == 'e' || c == 'E' || exp10 < -NUM_POWERS_10)) {	/* Check for trailing garbage */
	if (c != EOFC)
	    *psp = sp, code = 1;
	make_real(pref, ival * neg_powers_10[-exp10]);
	return code;
    }
    dval = ival;
    goto fe;

    /* We saw a '.' while accumulating a long in lval. */
l2r:
    while (IS_DIGIT(d, c)) {
	if (WOULD_OVERFLOW(lval, d, max_long)) {
	    dval = lval;
	    goto fd;
	}
	lval = lval * 10 + d;
	exp10--;
	GET_NEXT(c, sp, c = EOFC);
    }
le:
    if (sign < 0)
	lval = -lval;
    dval = lval;
    goto fe;

    /* Now we are accumulating a double in dval. */
fd:
    while (IS_DIGIT(d, c)) {
	dval = dval * 10 + d;
	exp10--;
	GET_NEXT(c, sp, c = EOFC);
    }
fs:
    if (sign < 0)
	dval = -dval;
fe:
    /* Now dval contains the value, negated if necessary. */
    switch (c) {
	case 'e':
	case 'E':
	    {			/* Check for a following exponent. */
		int esign = 0;
		int iexp;

		GET_NEXT(c, sp, return_error(e_syntaxerror));
		switch (c) {
		    case '-':
			esign = 1;
		    case '+':
			GET_NEXT(c, sp, return_error(e_syntaxerror));
		}
		/* Scan the exponent.  We limit it arbitrarily to 999. */
		if (!IS_DIGIT(d, c))
		    return_error(e_syntaxerror);
		iexp = d;
		for (;; iexp = iexp * 10 + d) {
		    GET_NEXT(c, sp, break);
		    if (!IS_DIGIT(d, c)) {
			*psp = sp;
			code = 1;
			break;
		    }
		    if (iexp > 99)
			return_error(e_limitcheck);
		}
		if (esign)
		    exp10 -= iexp;
		else
		    exp10 += iexp;
		break;
	    }
	default:
	    *psp = sp;
	    code = 1;
	case EOFC:
	    ;
    }
    /* Compute dval * 10^exp10. */
    if (exp10 > 0) {
	while (exp10 > NUM_POWERS_10)
	    dval *= powers_10[NUM_POWERS_10],
		exp10 -= NUM_POWERS_10;
	if (exp10 > 0)
	    dval *= powers_10[exp10];
    } else if (exp10 < 0) {
	while (exp10 < -NUM_POWERS_10)
	    dval /= powers_10[NUM_POWERS_10],
		exp10 += NUM_POWERS_10;
	if (exp10 < 0)
	    dval /= powers_10[-exp10];
    }
    /*
     * Check for an out-of-range result.  Currently we don't check for
     * absurdly large numbers of digits in the accumulation loops,
     * but we should.
     */
    if (dval >= 0) {
	if (dval > MAX_FLOAT)
	    return_error(e_limitcheck);
    } else {
	if (dval < -MAX_FLOAT)
	    return_error(e_limitcheck);
    }
rret:
    make_real(pref, dval);
    return code;
}
