
/*
 *  $Id: agFunEval.c,v 2.9 1999/07/07 19:30:51 bkorb Exp $
 *
 *  This module evaluates macro expressions.
 */

/*
 *  AutoGen copyright 1992-1999 Bruce Korb
 *
 *  AutoGen is free software.
 *  You may 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, or (at your option) any later version.
 *
 *  AutoGen 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 AutoGen.  See the file "COPYING".  If not,
 *  write to:  The Free Software Foundation, Inc.,
 *             59 Temple Place - Suite 330,
 *             Boston,  MA  02111-1307, USA.
 */

#include "autogen.h"

#ifdef WITH_INCLUDED_REGEX
#  include "compat/gnu-regex.h"
#else
#  include <regex.h>
#endif

#define EVAL_PRIVATE
#include "agexpr.h"
STATIC tMacro* pCurrentMac;

tSCC zExprErr[] = "%s Expression error in %s on line %d\n";

STATIC int
doStackExpr( int         level,
             tValStack*  pStk,
             char*       pzOp );

STATIC int
doStackFunc( int         level,
             tValStack*  pStk,
             char*       pzName,
             tDefEntry*  pCurDef );


    STATIC int
runEval( int        tknCt,
         char**     tknList,
         tDefEntry* pCurDef,
         tValStack* pStack )
{
    int          level   = 0;
    char**       ppTkn   = tknList;
    int          leftCt  = tknCt;
    tValStack*   pStkEnt = &(pStack[0]) - 1;

    /*
     *  FOR each token in the list, ...
     */
    if (leftCt == 0) {
        level = 1;
        pStack[0].valType = VT_NONE;
        pStack[0].val.pz  = "";

    } else for (;;) {
        char*  pz  = *ppTkn++;
        char   ch  = *pz;

        /*
         *  IF the first char is alpha or a space, it is a string
         *  IF it is a digit, it is a number
         *  IF it is a '#', the following chars are a string
         *  IF it is a '_', it introduces a function name
         *  IF it is '+' or '-' and a digit, then it is a number
         *  IF it is one or two punctuation characters,
         *        then it is an operator of some sort.
         *  ELSE is is a string
         */
        if (isalpha( ch ) || isspace( ch )) {
            pStack[ level ].valType = VT_STRING;
            pStack[ level ].val.pz  = pz;
            level++;

        } else if (isdigit( ch )) {
            pStack[ level ].valType = VT_VALUE;
            pStack[ level ].val.val = (t_word)strtol( pz, (char**)NULL, 0 );
            level++;

        } else switch (ch) {
        case '#':
            pStack[ level ].valType = VT_STRING;
            pStack[ level ].val.pz  = pz+1;
            level++;
            break;

        case NUL: /* Empty string */
            pStack[ level ].valType = VT_STRING;
            pStack[ level ].val.pz  = pz;
            level++;
            break;

        case '_':
            level = doStackFunc( level, pStkEnt, pz+1, pCurDef );
            break;

        case '-':
        case '+':
            if (isdigit( pz[1] )) {
                pStack[ level ].valType = VT_VALUE;
                pStack[ level ].val.val =
                    (t_word)strtol( pz, (char**)NULL, 0 );
                level++;
                break;
            }
            /* FALLTHROUGH */

        default:
            if (pz[1] == NUL) {
                level = doStackExpr( level, pStkEnt, pz );

            } else if ( (! ispunct( pz[1] )) || (pz[2] != NUL) ) {
                pStack[ level ].valType = VT_STRING;
                pStack[ level ].val.pz  = pz;
                level++;

            } else {
                level = doStackExpr( level, pStkEnt, pz );
            }
            break;
        }

        /*
         *  IF the stack level drops to or below 0,
         *  THEN there was some sort of problem.
         *       The caller will have to resolve it.
         */
        if (level <= 0)
            return VT_NONE;

        /*
         *  IF the list of tokens has been exhausted,
         *  THEN exit the loop
         */
        if (--leftCt <= 0)
            break;

        pStkEnt = pStack - 1 + level;
    }

    return level;
}


/*
 *  eval
 *
 *  The global evaluation function.
 *  It evaluates the tokens in the current macro descriptor.
 *  It returns the type of object stuffed into the "evalRes" field.
 */
    teValType
eval( tMacro*    pMac,
      tDefEntry* pCurDef )
{
    /*
     *  Do something funny if we did not get any tokens
     */
    if (pMac->tknCt == 0) {
        pMac->evalRes = (void*)"";
        return VT_NONE;
    }

    pCurrentMac = pMac;

    {
        tValStack   stack[ MAX_STK ];
        int         endLevel = runEval( pMac->tknCt, pMac->ppTkns,
                                        pCurDef, stack );
        tValStack*  pVS;

        /*
         *  IF the tokens were reduced to the normal one,
	 *  THEN we put its value into the "evalRes" and return the type.
         */
        if (endLevel == 1) {
            pMac->evalRes = (void*)(stack[ 0 ].val.pz);
            return stack[ 0 ].valType;
        }

        /*
         *  We need to return a stack, so we allocate one.
         *  The caller will either choke on it or deallocate it.
         */
        pVS = (tValStack*)AGALOC( sizeof( *pVS ) * (endLevel+1) );
        if (pVS == (tValStack*)NULL) {
            fprintf( stderr, zAllocErr, pzProg,
                     sizeof( *pVS ) * (endLevel+1), "value stack" );
            longjmp( fileAbort, FAILURE );
        }
        memcpy( (void*)pVS, (void*)stack, sizeof( *pVS ) * (endLevel+1) );
        pMac->evalRes = (void*)pVS;
        pVS[ endLevel ].valType = VT_NONE;
    }
    return VT_STACK;
}


/*
 *  evalError
 *
 *  When there was an evaluation error, this routine is called
 *  to print out the stack so the user can better determine the error.
 */
    void
evalError( tMacro* pMac )
{
    int         endLevel;
    tValStack*  pVS = (tValStack*)pMac->evalRes;

    for (endLevel = 0; pVS[ endLevel ].valType != VT_NONE; endLevel++ )
        ;

    fprintf( stderr, zExprErr, pzProg, pzTemplFileName,
             pMac->lineNo );
    fprintf( stderr, "\t%d stacked items remain in expression:\n",
             endLevel );

    for (endLevel = 0; endLevel < pMac->tknCt; endLevel++ )
        fprintf( stderr, "\t%4d  %s\n", endLevel, pMac->ppTkns[ endLevel ]);

    longjmp( fileAbort, FAILURE );
}


    STATIC void
unwind( int         level,
        const char* pzWhy,
        const char* pzWhat )
{
    fprintf( stderr, zExprErr, pzProg, pzTemplFileName,
             pCurrentMac->lineNo );
    fprintf( stderr, "invalid expression %s%s\n", pzWhy, pzWhat );

    longjmp( fileAbort, FAILURE );
}


    STATIC int
stringMatch( int        level,
             tValStack* pStk )
{
    regex_t reBuf;
    int     rerr = regcomp( &reBuf, pStk[1].val.pz, REG_NOSUB );
    if (rerr != 0) {
        char   zBuf[ 256 ];
        char*  p = zBuf;
        p += sprintf( zBuf, "RE error %d compiling %s - ",
                      rerr, pStk[1].val.pz );
        (void)regerror( rerr, &reBuf, p, sizeof( zBuf ) - (p - zBuf));
        unwind( level, zBuf, "" );
    }

    if (pStk[1].valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk[1].val.pz );

    {
        int  res;
        res = (regexec( &reBuf, pStk[0].val.pz, (size_t)0,
                        (regmatch_t*)NULL, 0 ) != REG_NOMATCH);

        if (pStk[0].valType == VT_ALLOC_STR)
            AGFREE( (void*)pStk->val.pz );

        pStk->valType = VT_VALUE;
        pStk->val.val = res;
    }

    regfree( &reBuf );
    return level;
}


/*
 *  Concatenate the two TOS string elements
 */
    STATIC int
concatenate( int        level,
             tValStack* pStk )
{
    size_t l = strlen( pStk[0].val.pz ) + strlen( pStk[1].val.pz ) + 1;
    char*  p = (char*)AGALOC( l );

    if (p == (char*)NULL) {
        fprintf( stderr, zAllocErr, pzProg,
                 l, zTokenList );
        LOAD_ABORT;
    }

    strcpy( p, pStk[0].val.pz );
    strcat( p, pStk[1].val.pz );
    if (pStk[0].valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk->val.pz );
    if (pStk[1].valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk[1].val.pz );
    pStk->valType = VT_ALLOC_STR;
    pStk->val.pz  = p;
    return level;
}



    STATIC void
setStrValue( tValStack* pStk )
{
    char*  pz;
    /*
     *  Get the numeric value of the string
     */
    long   valu = strtol( pStk->val.pz, &pz, 0 );

    /*
     *  IF the string was not a number (there are left over
     *     characters), then the value is the length.
     */
    if (*pz != NUL)
        valu = strlen( pStk->val.pz );

    if (pStk->valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk->val.pz );

    pStk->val.val = (t_word)valu;
    pStk->valType = VT_VALUE;
}



/*
 *  doStackExpr
 *
 *  Some sort of punctuation character was found, other than '#'.
 *  The stack *MUST* have at least one entry to operate on.
 *  However, only '!' (negation) can accept so few.  The rest
 *  require 2.
 */
    STATIC int
doStackExpr( int         level,
             tValStack*  pStk,
             char*       pzOp )
{
    static const char zInvalOp[] = "Unknown operator: ";

    teRel  op = REL_NONE;

    if (level <= 0)
        return -1;

    /*
     *  Convert the character sequence into an enumeration
     */
    switch (*pzOp++) {
    case '>':
        switch (*pzOp) {
        case '=':
            op = REL_GE;
            break;

        case NUL:
            op = REL_GT;
            break;

        default:
            unwind( level, zInvalOp, pzOp-1 );
        }
        break;

    case '<':
        switch (*pzOp) {
        case '=':
            op = REL_LE;
            break;

        case NUL:
            op = REL_LT;
            break;

        default:
            unwind( level, zInvalOp, pzOp-1 );
        }
        break;

    case '=':
        if ((*pzOp != NUL) && (*pzOp != '='))
            unwind( level, zInvalOp, pzOp-1 );

        op = REL_EQ;
        break;

    case '!':
        switch (*pzOp) {
        case '=':
            op = REL_NE;
            break;

        case NUL:
            op = REL_NOT;
            break;

        default:
            unwind( level, zInvalOp, pzOp-1 );
        }
        break;

    case '~':
    case '&':
    case '|':
    case '+':
    case '-':
    case '*':
    case '/':
    case '^':
    case '%':
        if (*pzOp == NUL) {

            /*
             *  We forced the enumeration values for these to be equal
             *  to their character values.
             */
            op = (teRel)(pzOp[-1]);
            break;
        }
        /* FALLTHROUGH */

    default:
        unwind( level, zInvalOp, pzOp-1 );
    }

    /*
     *  NOT takes one stack element of any kind.
     */
    if (op == REL_NOT) {
        switch (pStk->valType) {
        case VT_VALUE:
            if (pStk->val.val != 0)
                 pStk->val.val = AG_FALSE;
            else pStk->val.val = AG_TRUE;
            return level;

        case VT_STRING:
        case VT_ALLOC_STR:
            /*
             *  IF the string starts with a digit,
             *  THEN it evaluates to the numeric value
             *  ELSE it is TRUE IFF the string is not empty
             */
            {
                long resVal = isdigit( pStk->val.pz[0] )
                    ? strtol( pStk->val.pz, (char**)NULL, 0 )
                    : ((pStk->val.pz[0] == NUL) ? 0 : 1);

                /*
                 *  Deallocate, if we need to
                 */
                if (pStk->valType == VT_ALLOC_STR)
                    AGFREE( (void*)pStk->val.pz );

                pStk->val.val = resVal ? AG_FALSE : AG_TRUE;
                pStk->valType = VT_VALUE;
                return level;
            }

        default:
            break;
        }

        unwind( level, zInvalOp, pzOp-1 );
    }

    /*
     *  Everything else will take two elements
     */
    if (--level <= 0)
        unwind( level, pzOp-1, " requires two arguments" );

    pStk--;

    /*
     *  Make sure the types match.  If they do not match,
     *  then the string will be converted to either its value,
     *  or to its length.
     */
    if (pStk->valType == VT_VALUE) {
        if (pStk[1].valType != VT_VALUE)
            setStrValue( pStk+1 );
    }

    /*
     *  The first is a string.  See if the other is a value.
     */
    else if (pStk[1].valType == VT_VALUE) {
        setStrValue( pStk );
    }

    /*
     *  Both sides are strings.  The operator must be '+', '~' or a
     *  relationship ('<', '>', '=', '<=', '>=', or '!=').
     *  In this code, we will either call and return the match or
     *  concatenation, or else we will compute the result of the compare
     *  and put two values on the stack that will cause the right result
     *  when compared in the arithmetic section below.
     */
    else {
        /*
         *  See if we are concatenating strings
         */
        if (op == REL_PLUS)
            return concatenate( level, pStk );

        if (op == REL_MATCH)
            return stringMatch( level, pStk );

        /*
         *  Otherwise, the operator must be comparison
         */
        if (op > REL_NOT)
            unwind( level, pzOp-1, " is not '+', '~' or a relation op" );

        /*
         *  Compare the two top of stack entries and deallocate, if needed.
         *  Later, we will decide "TRUE" or "FALSE" by casing on the op-code
         *  and comparing this result with zero.
         */
        {
            t_word  value = strcmp( pStk->val.pz, pStk[1].val.pz );
            if (pStk->valType == VT_ALLOC_STR)
                AGFREE( (void*)pStk->val.pz );
            if (pStk[1].valType == VT_ALLOC_STR)
                AGFREE( (void*)pStk[1].val.pz );

            /*
             *  Both entries are now "VT_VALUE".  Top Of Stack
             *  gets the result of the string comparison,
             *  next-to-top gets zero.  Below, these values are
             *  used to compute the final result.
             */
            pStk->valType   = pStk[1].valType = VT_VALUE;
            pStk->val.val   = value;
            pStk[1].val.val = 0;
        }
    }

    /*
     *  Evaluate (compare) the two TOS values replacing them with
     *  AG_TRUE or AG_FALSE, depending on the compare
     */
    switch (op) {
    case REL_EQ:
        pStk->val.val = (pStk->val.val == pStk[1].val.val);
        break;

    case REL_NE:
        pStk->val.val = (pStk->val.val != pStk[1].val.val);
        break;

    case REL_GT:
        pStk->val.val = (pStk->val.val > pStk[1].val.val);
        break;

    case REL_LT:
        pStk->val.val = (pStk->val.val < pStk[1].val.val);
        break;

    case REL_GE:
        pStk->val.val = (pStk->val.val >= pStk[1].val.val);
        break;

    case REL_LE:
        pStk->val.val = (pStk->val.val <= pStk[1].val.val);
        break;

    case REL_MOD:
        pStk->val.val = (pStk->val.val % pStk[1].val.val);
        break;

    case REL_AND:
        pStk->val.val = (pStk->val.val && pStk[1].val.val);
        break;

    case REL_OR:
        pStk->val.val = (pStk->val.val || pStk[1].val.val);
        break;

    case REL_PLUS:
        pStk->val.val = (pStk->val.val + pStk[1].val.val);
        break;

    case REL_MINUS:
        pStk->val.val = (pStk->val.val - pStk[1].val.val);
        break;

    case REL_MULT:
        pStk->val.val = (pStk->val.val * pStk[1].val.val);
        break;

    case REL_DIV:
        pStk->val.val = (pStk->val.val / pStk[1].val.val);
        break;

    case REL_XOR:
        pStk->val.val = ( (pStk[0].val.val)
                        ? (pStk[1].val.val ? 0 : 1 )
                        : (pStk[1].val.val ? 1 : 0 ) );
        break;

    case REL_MATCH:
    case REL_NONE:
    case REL_NOT:
        break;
    }

    return level;
}



/*
 *  Stack functions operate *ONLY* on strings.
 *  VT_VALUE values must use arithmetic/boolean operators.
 */
    STATIC int
doStackFunc( int         level,
             tValStack*  pStk,
             char*       pzName,
             tDefEntry*  pCurDef )
{
    upcase( pzName, CC_ALL_UP );

    /*
     *  Do a binary search on apzExprName.
     *  Call the appropriate function if we find it,
     *  else generate an error
     */
    {
        int   min = 0;
        int   max = funcTableCt - 1;

        for (;;) {
            int   try = (min + max) / 2;
            int   res = strcmp( pzName, apzExprName[ try ]);

            if (res == 0) {
                level = (*(pExprFuncTable[ try ]))( level, pStk, pCurDef );
                break;
            }

            if (res > 0) {
                min = try+1;
            } else {
                max = try-1;
            }

            if (min > max)
                unwind( level, "Cannot find function: ", pzName );
        }
    }

    return level;
}


/*=macfunc EVAL
 *
 *  what:   Evaluate and Emit an Expression
 *
 *  desc:
 *  The arguments are evaluated and the result printed to the output file.
 *  The expression must be in reverse polish notation.  Strings and numbers
 *  are pushed on the stack, operators and functions pop values and push a
 *  result.  The 'operators' are:
 *
 *  @example
 *  = != > < >= <= ! % & * + - / | ~ #
 *  @end example
 *
 *  @noindent
 *  @strong{NOTE}:  the @code{and} (@code{&}) and @code{or} (@code{|})
 *  operators are logical operators, not bitwise operations.  The
 *  @code{tilde} (@code{~}) operator is a pattern match that yields 1
 *  (@code{true}) if the first (string) argument is fully matched by
 *  the second (pattern) argument, otherwise 0 (@code{false}).  The
 *  @code{#} operator "stringifies" whatever it is attached to.  This
 *  is the only way to push a string onto the stack that does not
 *  begin with an alphabetic or whitespace character.  The other
 *  operators have their normal meanings.
 *
 *  Expression function names are denoted by strings that start with a
 *  @code{_}.  The defined functions are:
 *
 *  table:   agexpr_func
=*/
    tMacro*
mFunc_Eval( tMacro* pM, tDefEntry* pCurDef )
{
    if (pM->tknCt > 0)
        switch ( eval( pM, pCurDef )) {
        default:
        case VT_NONE:
            break;

        case VT_VALUE:
            fprintf( pCurFp->pFile, "%d", (t_word)pM->evalRes );
            break;

        case VT_STRING:
            fputs( (char*)pM->evalRes, pCurFp->pFile );
            break;

        case VT_ALLOC_STR:
            fputs( (char*)pM->evalRes, pCurFp->pFile );
            AGFREE( pM->evalRes );
            break;

        case VT_STACK:
            evalError( pM );
        }
    return pM+1;
}
/* end of agFunEval.c */
