//-----------------------------------------------------------------------------
// Copyright © 2004 - Philip Howard - 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, USA.
//-----------------------------------------------------------------------------
// package	libh/string
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

#include <errno.h>

#include <libh/arith.h>

#include "string_lib.h"

__PROTO_BEGIN__
//-----------------------------------------------------------------------------
// function	str_expr_to_sll
//
// purpose	Convert a string containing a numeric expression into a
//		signed long long integer.
//
// arguments	1 (const char *) pointer to string to convert
//		2 (char * *) pointer to string to store end pointer
//		3 (int) numeric base value, 2 to 36
//
// returns	(signed long long) converted result
//
// note		Numeric values that exceed the maximums for this data type
//		will result in undefined values being returned.
//-----------------------------------------------------------------------------
signed long long
str_expr_to_sll (
    const char *	arg_str
    ,
    char * *		arg_end_str
    ,
    int			arg_base
    )
__PROTO_END__
{
    struct stack_node {
	signed long long	value		;
	int			level		;
	int			oper		;
    };

    struct stack_node		stack_array	[64];
    struct stack_node *		stack_top	;
    struct stack_node *		stack_end	;

    signed long long		number		;
    char *			str_ptr		;
    char *			end_ptr		;
    int				erange		;
    int				nest		;
    int				op_code		;
    int				op_level	;


    stack_top		= stack_array;
    stack_top->level	= 0;
    ++ stack_top;
    stack_end		= stack_array + 64;

    erange		= 0;
    nest		= 0;
    op_level		= 0;

    * (const char * *) & str_ptr = arg_str;

    for (;;) {

	//--------------------
	// Expecting a number.
	//--------------------

	//-- Check for end of string.
	if ( * str_ptr == 0 ) {
	    errno = EINVAL;
	    number = 0;
	    break;
	}

	//-- Check for spaces.
	if ( isspace( * str_ptr ) ) {
	    ++ str_ptr;
	    continue;
	}

	//-- Check for nesting.
	if ( * str_ptr == '(' ) {
	    nest += 8;
	    ++ str_ptr;
	    continue;
	}

	//-- Convert a number.
	errno = 0;
	end_ptr = NULL;
	number = str_to_ll( str_ptr, & end_ptr, arg_base );
	if ( ! end_ptr ) {
	    errno = EINVAL;
	    number = 0;
	    break;
	}
	if ( errno == ERANGE ) {
	    ++ erange;
	}
	str_ptr = end_ptr;

	//-----------------------
	// Expecting an operator.
	//-----------------------

	//-- Get the operator code.
	for (;;) {
	    op_code = * str_ptr ++;

	    //-- Skip spaces.
	    if ( isspace( op_code ) ) {
		continue;
	    }

	    //-- Check for unnesting.
	    if ( op_code == ')' && nest >= 8 ) {
		nest -= 8;
		continue;
	    }

	    //-- Operator or not, go with it.
	    break;
	}

	//-- Determine level of this operator.
	op_level = 0;
	if ( op_code == '+' || op_code == '-' ) {
	    op_level = nest + 2;
	}
	else if ( op_code == '*' || op_code == '/' || op_code == '%' ) {
	    op_level = nest + 3;
	}
	else if ( op_code == '^' ) {
	    op_level = nest + 4;
	}

	//-- Do stacked arithmetic.
	while ( stack_top->level > op_level ) {
	    if ( stack_top->oper == '+' ) {
		number = stack_top->value + number;
	    }
	    else if ( stack_top->oper == '-' ) {
		number = stack_top->value - number;
	    }
	    else if ( stack_top->oper == '*' ) {
		number = stack_top->value * number;
	    }
	    else if ( stack_top->oper == '/' ) {
		number = stack_top->value / number;
	    }
	    else if ( stack_top->oper == '%' ) {
		number = stack_top->value % number;
	    }
	    else if ( stack_top->oper == '^' ) {
		number = ipow( stack_top->value, number );
	    }
	    if ( -- stack_top == stack_array ) break;
	}

	//-- If end of expression.
	if ( op_level == 0 ) {
	    -- str_ptr;
	    if ( op_code != 0 || nest > 0 ) {
		errno = EINVAL;
		number = 0;
	    }
	    break;
	}

	//-- Push the new operator into the stack.
	if ( ++ stack_top >= stack_end ) {
	    errno = ENOBUFS;
	    number = 0;
	    break;
	}
	stack_top->value = number;
	stack_top->oper = op_code;
	stack_top->level = op_level;
    }

    if ( arg_end_str ) * arg_end_str = str_ptr;
    return number;
}

