/**********************************************************************/
/*   Function  to  parse  a  floating  point number. Shared by crisp  */
/*   (cm) and crunch languages.					      */
/**********************************************************************/
# include	"pfloat.h"
# include	"chkalloc.h"
# include	<ctype.h>

# if	!defined(NULL)
#	define	NULL	0
# endif

# define	TRUE	1
# define	FALSE	0

struct pstruct {
	int	(*p_input)();
	void	(*p_unget)();
	char	*p_str;
	int	p_ch;
	long	*p_long;
	double	*p_float;
	};

static	struct pstruct p;
double	atof();
int	sscanf();
int	input();
int	ungetchar();
/**********************************************************************/
/*   Function  to  parse  a  number  which  could be an integer or a  */
/*   floating  point  constant, and handle the ambiguity of '.', and  */
/*   '...'.							      */
/**********************************************************************/
static int
__parse()
{	int	ch = p.p_ch;
	register	char *tokp;
	int	seen_exp_sign = FALSE;
static char *token_buf = NULL;
enum {BEFORE_DOT, AFTER_DOT, AFTER_EXP, HEX_CONST} state;

	if (token_buf == NULL)
		token_buf = chk_alloc(128);
	tokp = token_buf;
	*tokp++ = ch;
	if (ch == '.') {
		state = AFTER_DOT;
		}
	else {
		state = BEFORE_DOT;
		}
	/***********************************************/
	/*   Read  the  number into a temporary token  */
	/*   buffer,  so  we  can  decide  what it is  */
	/*   before  we  actually  convert  it to the  */
	/*   appropriate number type.		       */
	/***********************************************/
	while ((ch = (*p.p_input)()) != 0) {
		switch (state) {
		  case BEFORE_DOT:
		  	if (ch == 'e' || ch == 'E') {
				state = AFTER_EXP;
				*tokp++ = ch;
				break;
				}
			if (ch == '.') {
			  	*tokp++ = ch;
				state = AFTER_DOT;
				break;
				}
			if (ch == 'x' || ch == 'X') {
				if (token_buf[0] != '0' || tokp != token_buf+1) {
					(*p.p_unget)(ch);
					goto end_of_number;
					}
				*tokp++ = 'x';
				state = HEX_CONST;
				break;
				}
			if (!isdigit(ch)) {
				(*p.p_unget)(ch);
				goto end_of_number;
				}
			*tokp++ = ch;
			break;
		  case AFTER_DOT:
			/***********************************************/
			/*   Dont allow ".e34" as a valid number.      */
			/***********************************************/
		  	if ((ch == 'e' || ch == 'E') && 
			   (token_buf[0] != '.' || tokp > token_buf+1)) {
				state = AFTER_EXP;
				*tokp++ = ch;
				if ((ch = (*p.p_input)()) == 0) {
					return PARSE_ERR_EOF_IN_FLOAT;
					}
				if (isdigit(ch) || ch == '+' || ch == '-')
					*tokp++ = ch;
				else {
					return PARSE_ERR_FLOAT_SYNTAX_ERROR;
					}
				break;
				}
				
			/***********************************************/
			/*   Check  for  '.',  and  '...'  which  are  */
			/*   valid tokens on their own		       */
			/***********************************************/
			if (!isdigit(ch)) {
				if (ch == '.' && token_buf[0] == '.' && tokp == token_buf+1) {
					if ((*p.p_input)() != '.')
						return PARSE_ERR_ELLIPSIS_ERROR;
					return PARSE_ELLIPSIS;
					}
				if (token_buf[0] == '.' && tokp == token_buf + 1)
					return PARSE_DOT;
				(*p.p_unget)(ch);
				goto end_of_number;
				}
			*tokp++ = ch;
			break;
		  case AFTER_EXP:
		  	if (!seen_exp_sign && (ch == '-' || ch == '+' || isdigit(ch)))
				seen_exp_sign = TRUE;
		  	else if (!isdigit(ch)) {
				(*p.p_unget)(ch);
				goto end_of_number;
				}
			*tokp++ = ch;
		  	break;
		  case HEX_CONST:
		  	if (isdigit(ch) || (ch >= 'a' && ch <= 'f') || 
			    (ch >= 'A' && ch <= 'F')) {
			    	*tokp++ = ch;
				break;
				}
			(*p.p_unget)(ch);
		  	goto end_of_number;
		  }
		}
end_of_number:
	*tokp = NULL;

	/***********************************************/
	/*   Check for a lone sign.		       */
	/***********************************************/
	if (token_buf[1] == NULL && (token_buf[0] == '+' || token_buf[0] == '-'))
		return PARSE_ERR_FLOAT_SYNTAX_ERROR;
	/***********************************************/
	/*   Check for a hex number first.	       */
	/***********************************************/
	if (token_buf[0] == '0' && token_buf[1] == 'x') {
		sscanf(token_buf+2, "%lx", p.p_long);
		return PARSE_INTEGER;
		}
		
	/***********************************************/
	/*   Now check for an octal constant.	       */
	/***********************************************/
	if (token_buf[0] == '0' && state == BEFORE_DOT) {
		sscanf(token_buf, "%lo", p.p_long);
		return PARSE_INTEGER;
		}
		
	/***********************************************/
	/*   Now a decimal constant.		       */
	/***********************************************/
	if (state == BEFORE_DOT) {
		sscanf(token_buf, "%ld", p.p_long);
		return PARSE_INTEGER;
		}
		
	/***********************************************/
	/*   Ok,  so  its  gotta  be a floating point  */
	/*   number.				       */
	/***********************************************/
	*p.p_float = atof(token_buf);
	return PARSE_FLOAT;
}

static int 
str_input()
{
	if (*p.p_str)
		return *p.p_str++;
	return 0;
}
static void
str_ungetchar()
{
	p.p_str--;
}
/**********************************************************************/
/*   Function  to  parse  a number stored in a string and return int  */
/*   or double value depending on what sort of number it is.	      */
/**********************************************************************/
int
parse_str_number(str, dp, intp, lenp)
char	*str;
double	*dp;
long	*intp;
int	*lenp;
{	int	ret;
	char	*start_str = str;

	p.p_ch = *str++;
	p.p_str = str;	
	p.p_float = dp;
	p.p_long = intp;
	p.p_input = str_input;
	p.p_unget = str_ungetchar;
	ret = __parse();
	if (lenp)
		*lenp = p.p_str - start_str;
	return ret;
}

/**********************************************************************/
/*   Parser   using   functions   'input()'   and  'ungetchar()'  as  */
/*   defaults for the source of input.				      */
/**********************************************************************/
int
parse_number(ch, dp, intp)
int	ch;
double	*dp;
long	*intp;
{
	p.p_ch = ch;
	p.p_float = dp;
	p.p_long = intp;
	p.p_input = input;
	p.p_unget = (void (*)()) ungetchar;
	return __parse();
}
