/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
**********/

/* This routine parses a number.  */

#include "ngspice/ngspice.h"
#include "ngspice/bool.h"
#include "ngspice/ftedefs.h"
#include "numparse.h"


static double
power10(double num)   /* Chris Inbody */
{
    double d = 1.0;

    while (num-- > 0)
        d *= 10.0;
    return (d);
}


bool ft_strictnumparse = FALSE;


/* Parse a number. This will handle things like 10M, etc... If the number
 * must not end before the end of the string, then whole is TRUE.
 * If whole is FALSE and there is more left to the number, the argument
 * is advanced to the end of the word. Returns NULL
 * if no number can be found or if there are trailing characters when
 * whole is TRUE.
 *
 * If ft_strictnumparse is TRUE, and whole is FALSE, the first of the
 * trailing characters must be a '_'.  */

double *
ft_numparse(char **s, bool whole)
{
    double mant = 0.0;
    int sign = 1, exsign = 1, p;
    double expo = 0.0;
    static double num;
    char *string = *s;

    /* See if the number begins with + or -. */
    if (*string == '+') {
        string++;
    } else if (*string == '-') {
        string++;
        sign = -1;
    }

    /* We don't want to recognise "P" as 0P, or .P as 0.0P... */
    if ((!isdigit_c(*string) && *string != '.') ||
        ((*string == '.') && !isdigit_c(string[1])))
        return (NULL);

    /* Now accumulate a number. Note ascii dependencies here... */
    while (isdigit_c(*string))
        mant = mant * 10.0 + (*string++ - '0');

    /* Now maybe a decimal point. */
    if (*string == '.') {
        string++;
        p = 1;
        while (isdigit_c(*string))
            mant += (*string++ - '0') / power10(p++);
    }

    /* Now look for the scale factor or the exponent (can't have both). */
    switch (*string) {
    case 'e':
    case 'E':
        /* Parse another number. */
        string++;
        if (*string == '+') {
            exsign = 1;
            string++;
        } else if (*string == '-') {
            exsign = -1;
            string++;
        }
        while (isdigit_c(*string))
            expo = expo * 10.0 + (*string++ - '0');
        if (*string == '.') {
            string++;
            p = 1;
            while (isdigit_c(*string))
                expo += (*string++ - '0') / power10(p++);
        }
        expo *= exsign;
        break;
    case 't':
    case 'T':
        expo = 12.0;
        string++;
        break;
    case 'g':
    case 'G':
        expo = 9.0;
        string++;
        break;
    case 'k':
    case 'K':
        expo = 3.0;
        string++;
        break;
    case 'u':
    case 'U':
        expo = -6.0;
        string++;
        break;
    case 'n':
    case 'N':
        expo = -9.0;
        string++;
        break;
    case 'p':
    case 'P':
        expo = -12.0;
        string++;
        break;
    case 'f':
    case 'F':
        expo = -15.0;
        string++;
        break;
    case 'm':
    case 'M':
        /* Can be either m, mil, or meg. */
        if (string[1] && string[2] &&
            ((string[1] == 'e') || (string[1] == 'E')) &&
            ((string[2] == 'g') || (string[2] == 'G')))
        {
            expo = 6.0;
            string += 3;
        } else if (string[1] && string[2] &&
                   ((string[1] == 'i') || (string[1] == 'I')) &&
                   ((string[2] == 'l') || (string[2] == 'L')))
        {
            expo = -6.0;
            mant *= 25.4;
            string += 3;
        } else {
            expo = -3.0;
            string++;
        }
        break;
    }

    if (whole && *string != '\0') {
        return (NULL);
    } else if (ft_strictnumparse && *string && isdigit_c(string[-1])) {
        if (*string == '_')
            while (isalpha_c(*string) || (*string == '_'))
                string++;
        else
            return (NULL);
    } else {
        while (isalpha_c(*string) || (*string == '_'))
            string++;
    }
    *s = string;
    num = sign * mant * pow(10.0, expo);
    if (ft_parsedb)
        fprintf(cp_err, "numparse: got %e, left = %s\n", num, *s);
    return (&num);
}
