/*
**
** PARSE.C     Divides an input string into tokens and evaluates an
**             expression.
**
** Originally written 5/89 in ANSI C
**
** Eval is a floating point expression evaluator.
** This file last updated in version 1.10
** For the version number, see eval.h
** Copyright (C) 1993-2003 http://willus.com
**
** 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 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.,
** 675 Mass Ave, Cambridge, MA 02139, USA.
**
*/

#include   "eval.h"


/* modes */
#define     M_UNARY     1
#define     M_BINARY    2
#define     M_FUNCTION  3

static double   last_value=0.0;
static int      base;

static int     is_binary     (char *s);
static int     is_unary      (char c);
static int     instr         (char c,char *s);
static BOOLEAN legal         (char *s);
static BOOLEAN is_digit      (char c);
static BOOLEAN is_exp        (char c);
static BOOLEAN get_token     (int mode,char *s,int *n,TOKENPTR t,VARPTR vlist,
                              VARPTR clist,char *newname);
static BOOLEAN assign_as_var (char *s,int *n,TOKENPTR t,VARPTR vlist,
                              VARPTR clist,char *newname);
static BOOLEAN assign_as_num (char *s,int *n,TOKENPTR t,int max,int base);
static BOOLEAN get_value     (char *s,VARPTR vlist,VARPTR clist,
                              double *ret_val);
static int     base_override (char *s,int *n);
static int     rank          (TOKEN *t);


static int is_binary(char *s)

    {
    if (s[0]=='<' && s[1]=='<')
        return(SHLEFT);
    if (s[0]=='>' && s[1]=='>')
        return(SHRIGHT);
    return(instr(s[0],BSTRING));
    }


static int is_unary(char c)

    {
    return(instr(c,USTRING));
    }


static int instr(char c,char *s)

    {
    int    i;

    for (i=0;s[i]!=EOS && s[i]!=c;i++);
    return(s[i]==EOS ? 0 : i+1);
    }


static BOOLEAN legal(char *s)

    {
    int     c;

    c=s[0];
    return(c!=EOS && c!='(' && c!=')' && c!=','
            && c!='=' && !isspace(c) && !is_binary(s));
    }


static BOOLEAN is_digit(char c)

    {
    c=tolower((int)c);
    if (base>10)
        return((c>='0' && c<='9') || (c>='a' && c<='a'+base-11));
    return(c>='0' && c<='0'+base-1);
    }


static BOOLEAN is_exp(char c)

    {
    return((base > 14) ? (c=='\\') : (c=='\\' || c=='e' || c=='E'));
    }

/*
** get_token(int mode,char *s,int *n,TOKENPTR t,VARPTR vlist,
**           VARPTR clist,char *newname)
**
** Figures out what the next token in the string s is, starting at (*n).
** (*n) is returned pointing to the part of the string just after the
** evaluated token.  vlist and clist are used to search through in case
** the token is a variable or constant.  newname is assigned if the token
** is a variable name not found in any of the lists.
**
** Returns 0 for end of string, 1 for valid token found
**
*/

static BOOLEAN get_token(int mode,char *s,int *n,TOKENPTR t,VARPTR vlist,
                         VARPTR clist,char *newname)

    {
    int     i,c,h;

    t->type=0;
    t->code=0;
    t->value=0;
    for (i=(*n);isspace(s[i]);i++);
    (*n)=i;
    if (s[i]==EOS)
        {
        (*n)=i;
        return(0);
        }
    t->type=0;
    if (mode==M_UNARY && is_unary(s[i]))
        {
        t->type=UNARY;
        t->code=is_unary(s[i]);
        (*n)=(*n)+1;
        return(1);
        }
    if (is_binary(&s[i]) && (mode!=M_UNARY || s[i]!='&'))
        {
        t->type=BINARY;
        t->code=is_binary(&s[i]);
        if (t->code==SHLEFT || t->code==SHRIGHT)
            (*n)=(*n)+1;
        }
    else
        {
        t->type=instr(s[i],"(),=");
        if (t->type>0)
                t->type+=LEFT_PAREN-1;
        }
    if (t->type!=0)
        {
        (*n)=(*n)+1;
        return(1);
        }
    if (s[i]=='!')
        return(assign_as_var(s,n,t,vlist,clist,newname));
    base=getibase();
    c=base_override(&s[i],&h);
    if (c)
        {
        base=c;
        i+=h;
        }
    if (!is_digit(s[i]) && s[i]!='.')
        return(assign_as_var(s,n,t,vlist,clist,newname));
    if (s[i]=='.' && !is_digit(s[i+1]))
        return(assign_as_var(s,n,t,vlist,clist,newname));
    for (;legal(&s[i]);i++)
        {
        if (s[i]=='.' || is_exp(s[i]))
            break;
        if (!is_digit(s[i]))
                return(assign_as_var(s,n,t,vlist,clist,newname));
        }
    if (!legal(&s[i]))
        {
        (*n)=(*n)+h;
        return(assign_as_num(s,n,t,i,base));
        }
    if (s[i]=='.')
        for (i++;legal(&s[i]);i++)
                {
                if (is_exp(s[i]))
                    break;
                if (!is_digit(s[i]))
                    return(assign_as_var(s,n,t,vlist,clist,newname));
                }
    if (!legal(&s[i]))
        {
        (*n)=(*n)+h;
        return(assign_as_num(s,n,t,i,base));
        }
    if (s[i+1]=='+' || s[i+1]=='-')
        i++;
    if (!is_digit(s[i+1]))
        return(assign_as_var(s,n,t,vlist,clist,newname));
    for (i++;legal(&s[i]);i++)
        {
        if (!is_digit(s[i]))
                return(assign_as_var(s,n,t,vlist,clist,newname));
        }
    (*n)=(*n)+h;
    return(assign_as_num(s,n,t,i,base));
    }


static BOOLEAN assign_as_var(char *s,int *n,TOKENPTR t,VARPTR vlist,
                             VARPTR clist,char *newname)

    {
    VAR     v;
    char    vname[MAXINPUT+1];
    int     l,i,j;

    i=(*n);
    l=(s[i]=='!');
    if (l)
        i++;
    for (j=0;legal(&s[i]) || (!j && s[i]=='&');i++,j++)
        vname[j]=s[i];
    if (!j && l)
        vname[j++]='!';
    (*n)=i;
    vname[j]=EOS;
    if (strlen(vname)>MAXNAMELEN)
        vname[MAXNAMELEN]=EOS;
    strcpy(newname,vname);
    if (vname[0]=='\"' && vname[1]==EOS)
        {
        t->value=last_value;
        t->type=QUOTE;
        return(1);
        }
    if (strlen(vname)<=MAXFLEN && (i=func_code(vname))!=0)
        {
        t->code=i;
        t->type=FUNCTION;
        }
    else
        {
        strcpy(v.name,vname);
        if (search_varlist(&v,clist,&i,MAXC))
                {
                t->code=i;
                t->type=CONSTANT;
                t->value=clist[i].value;
                }
        else
                {
                t->type=VARIABLE;
                if (search_varlist(&v,vlist,&i,MAXV))
                    {
                    t->code=i;
                    t->value=vlist[i].value;
                    }
                else
                    t->code=-1;
                }
        }
    return(1);
    }


static BOOLEAN assign_as_num(char *s,int *n,TOKENPTR t,int max,int base)

    {
    char    num[MAXINPUT+1];
    int     i,j;

    for (i=(*n),j=0;i<max;i++,j++)
        num[j]=s[i];
    (*n)=i;
    num[j]=EOS;
    t->type=NUMBER;
    t->value=asciiconv(base,num);
    return(1);
    }


/*
** evaluate(char *s,int showout,VARPTR vlist,VARPTR clist)
**
** Evaluate parses the input string looking for help queries or an "equals"
** sign that divides the string into an assignment variable and its
** assigned expression.  To evaluate the actual expression, get_value is
** called.
**
** Returns nothing
**
*/

void evaluate(char *s,int showout,VARPTR vlist,VARPTR clist)

    {
    char    varname[MAXINPUT+1];
    int     i,j,n,vn;
    VAR     x;
    TOKEN   t;
    int     checked,outbase,oldbase;
    char    bigbuf[MAXOUTLEN];


    oldbase=getobase();
    outbase=base_override(s,&j);
    checked=0;
    if (outbase && isspace(s[j]))
        {
        j++;
        checked=1;
        }
    else
        {
        j=0;
        outbase=oldbase;
        }
    for (i=j;s[i]!=EOS && s[i]!='=';i++);
    if (s[i]!=EOS)
        {
        strcpy(varname,&s[j]);
        varname[i++]=EOS;
        fixup(varname);
        n=0;
        j=get_token(M_UNARY,varname,&n,&t,vlist,clist,x.name);
        if (!j)
                {
                printf("There must be valid variable name on the left side of the '='.\n");
                return;
                }
        if (varname[n]!=EOS || (t.type!=VARIABLE && t.type!=CONSTANT))
                {
                printf("\"%s\" is not a valid variable name.\n",varname);
                return;
                }
        if (t.type==CONSTANT)
                {
                printf("\"%s\" is a pre-assigned constant.  It cannot be reassigned.\n",x.name);
                return;
                }
        vn=(t.type==VARIABLE);
        }
    else
        {
        vn=0;
        i=j;
        }
    if (!checked)
        {
        outbase=base_override(&s[i],&j);
        if (outbase && isspace(s[i+j]))
            j++;
        else
            {
            j=0;
            outbase=oldbase;
            }
        i+=j;
        }
    if (!get_value(&s[i],vlist,clist,&x.value))
        return;
    last_value=x.value;
    if (outbase!=oldbase)
        setobase(outbase);
    baseconv(x.value,bigbuf);
    if (showout)
        printf("%s\n",bigbuf);
    if (outbase!=oldbase)
        setobase(oldbase);
    if (vn)
        {
        if (!insert_var(&x,vlist))
            printf("No more variables can be assigned!  Limit = %d.\n",MAXV);
        }
    }


/*
** get_value(char *s,VARPTR vlist,VARPTR clist)
**
** get_value evaluates an expression string by parsing it into tokens
** (using get_token) and converting the string of tokens to a table that
** contains tokens in reverse polish order.  table_value is then called
** to evaluate the final value of the reverse polish table of tokens.
** That value is returned by get_value.
**
*/

static BOOLEAN get_value(char *s,VARPTR vlist,VARPTR clist,double *ret_val)

    {
    char    nn[MAXNAMELEN+1];
    TOKEN   t,t2,t3;
    int     spos,mode;
    char    argcount[MAXINPUT+1];
    int     argptr;

    clear_stack();
    clear_table();
    spos=0;
    argptr=-1;
    mode=M_UNARY;
    while (get_token(mode,s,&spos,&t,vlist,clist,nn))
        {
        if (t.type==ILLEGAL)
            return(eerror("The '=' character is not allowed in expressions."));
        if (mode==M_UNARY && (t.type==BINARY || t.type==COMMA ||
                              t.type==RIGHT_PAREN))
            return(eerror("Operand expected in expression."));
        if (mode==M_BINARY && (t.type==UNARY || t.type==CONSTANT ||
                               t.type==VARIABLE || t.type==FUNCTION ||
                               t.type==NUMBER || t.type==LEFT_PAREN ||
                               t.type==QUOTE))
            return(eerror("Operator expected in expression."));
        if (t.type==VARIABLE && t.code<0)
            {
            printf("\"%s\" is an unassigned variable.\n",nn);
            return(0);
            }
        if (mode==M_FUNCTION && t.type!=LEFT_PAREN)
            return(eerror("Function names must be immediately followed "
                          "by a left parenthesis."));
        switch(t.type)
            {
            case NUMBER:
            case VARIABLE:
            case CONSTANT:
            case QUOTE:
                if (!add_token(&t))
                    return(0);
                mode=M_BINARY;
                break;
            case LEFT_PAREN:
                if (!push_token(&t))
                    return(0);
                mode=M_UNARY;
                break;
            case UNARY:
                if (!push_token(&t))
                    return(0);
                mode=M_UNARY;
                break;
            case BINARY:
                while (top_of_stack(&t2))
                    {
                    /* ^ operator goes right to left in Fortran */
                    if (t2.type==BINARY && rank(&t2)==10 && rank(&t)==10)
                        break;
                    if ((t2.type==BINARY || t2.type==UNARY)
                                    && rank(&t) > rank(&t2))
                        break;
                    if (t2.type==LEFT_PAREN)
                        break;
                    pop_token(&t2);
                    if (!add_token(&t2))
                        return(0);
                    }
                if (!push_token(&t))
                    return(0);
                mode=M_UNARY;
                break;
            case FUNCTION:
                if (!push_token(&t))
                    return(0);
                argptr++;
                argcount[argptr]=0;
                mode=M_FUNCTION;
                break;
            case RIGHT_PAREN:
                while (pop_token(&t2) && t2.type!=LEFT_PAREN)
                    if (!add_token(&t2))
                        return(0);
                if (t2.type!=LEFT_PAREN)
                    return(eerror("Unmatched parentheses in expression."));
                if (top_of_stack(&t2) && t2.type==FUNCTION)
                    {
                    pop_token(&t2);
                    if (!add_token(&t2))
                        return(0);
                    if (func_nargs(t2.code)!=argcount[argptr]+1)
                        {
                        printf("Incorrect number of arguments "
                               "specified for function %s.\n",
                               func_name(t2.code));
                        return(0);
                        }
                    argptr--;
                    }
                mode=M_BINARY;
                break;
            case COMMA:
                while (pop_token(&t2) && t2.type!=LEFT_PAREN)
                    if (!add_token(&t2))
                        return(0);
                if (t2.type!=LEFT_PAREN || !top_of_stack(&t3) ||
                    t3.type!=FUNCTION)
                    return(eerror("Misplaced comma in expression."));
                if (!push_token(&t2))
                    return(0);
                mode=M_UNARY;
                argcount[argptr]++;
                break;
            }
        }
    if (mode==M_UNARY)
        return(eerror("Expression is improperly terminated."));
    while (pop_token(&t2))
        {
        if (t2.type==LEFT_PAREN)
            return(eerror("Unmatched parentheses in expression."));
        if (!add_token(&t2))
            return(0);
        }
    return(table_value(ret_val));
    }


BOOLEAN eerror(char *message)

    {
    printf("%s\n",message);
    return(0);
    }


static int rank(TOKEN *t)

    {
    if (t->type==UNARY)
        return(9);
    if (t->type==BINARY)
        {
        switch (t->code)
            {
            case POWER:
                return(10);
            case DIVIDE:
            case MULTIPLY:
            case MOD:
                return(8);
            case ADD:
            case SUBTRACT:
                return(7);
            case SHRIGHT:
            case SHLEFT:
                return(6);
            case AND:
                return(5);
            case XOR:
                return(4);
            case OR:
                return(3);
            }
        }
    return(0);
    }


static int base_override(char *s,int *n)

    {
    int     base;
    int     c;

    (*n)=0;
    base=0;
    if (s[0]=='&')
        {
        if (s[1]!=EOS)
            {
            c=tolower((int)s[1]);
            if (c=='h' || c=='o' || c=='b' || c=='d')
                {
                base= c=='h' ? 16 : (c=='o' ? 8 :(c=='b' ? 2 : 10));
                (*n)=2;
                }
            }
        }
    else if (s[0]=='0' && (s[1]=='x' || s[1]=='X'))
        {
        base=16;
        (*n)=2;
        }
/* Assuming base 8 for a leading zero removed because of
   confusion with floating point numbers */
    else if (s[0]=='\\')
        {
        c=tolower((int)s[1]);
        if (c=='0' || (c>='2' && c<='9') || (c>='a' && c<='z'))
            {
            base= c=='0' ? 10:((c>='2'&&c<='9')?c-'0':c-'a'+11);
            (*n)=2;
            }
        }
    else if (s[0]=='$')
        {
        base=16;
        (*n)=1;
        }
    return(base);
    }


void tokcpy(TOKENPTR dest,TOKENPTR source)

    {
    dest->type=source->type;
    dest->code=source->code;
    dest->value=source->value;
    }
