
/*
 *  agExpr.c
 *  $Id: agExpr.c,v 2.19 1999/08/03 23:11:17 bkorb Exp $
 *  This module implements the expression functions.
 */

/*
 *  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.
 */

static const char zGpl[] =
"%2$s%1$s is free software.\n%2$s\n"
"%2$sYou may redistribute it and/or modify it under the terms of the\n"
"%2$sGNU General Public License, as published by the Free Software\n"
"%2$sFoundation; either version 2, or (at your option) any later version.\n"
"%2$s\n"
"%2$s%1$s is distributed in the hope that it will be useful,\n"
"%2$sbut WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"%2$sMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"%2$sSee the GNU General Public License for more details.\n"
"%2$s\n"
"%2$sYou should have received a copy of the GNU General Public License\n"
"%2$salong with %1$s.  See the file \"COPYING\".  If not,\n"
"%2$swrite to:  The Free Software Foundation, Inc.,\n"
"%2$s           59 Temple Place - Suite 330,\n"
"%2$s           Boston,  MA  02111-1307, USA.";

#define GPL_PFX_CT  16
#define GPL_NAME_CT 3

static const char zLgpl[] =
"%2$s%1$s is free software.\n%2$s\n"
"%2$sYou may redistribute it and/or modify it under the terms of the\n"
"%2$sGNU General Public License, as published by the Free Software\n"
"%2$sFoundation; either version 2, or (at your option) any later version.\n"
"%2$s\n"
"%2$s%1$s is distributed in the hope that it will be useful,\n"
"%2$sbut WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"%2$sMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
"%2$sSee the GNU General Public License for more details.\n"
"%2$s\n"
"%2$sYou should have received a copy of the GNU General Public License\n"
"%2$salong with %1$s.  See the file \"COPYING\".  If not,\n"
"%2$swrite to:  The Free Software Foundation, Inc.,\n"
"%2$s           59 Temple Place - Suite 330,\n"
"%2$s           Boston,  MA  02111-1307, USA.\n"
"%2$s\n"
"%2$sAs a special exception, %3$s gives permission for additional\n"
"%2$suses of the text contained in the release of %1$s.\n"
"%2$s\n"
"%2$sThe exception is that, if you link the %1$s library with other\n"
"%2$sfiles to produce an executable, this does not by itself cause the\n"
"%2$sresulting executable to be covered by the GNU General Public License.\n"
"%2$sYour use of that executable is in no way restricted on account of\n"
"%2$slinking the %1$s library code into it.\n"
"%2$s\n"
"%2$sThis exception does not however invalidate any other reasons why\n"
"%2$sthe executable file might be covered by the GNU General Public License.\n"
"%2$s\n"
"%2$sThis exception applies only to the code released by %3$s under\n"
"%2$sthe name %1$s.  If you copy code from other sources under the\n"
"%2$sGeneral Public License into a copy of %1$s, as the General Public\n"
"%2$sLicense permits, the exception does not apply to the code that you add\n"
"%2$sin this way.  To avoid misleading anyone as to the status of such\n"
"%2$smodified files, you must delete this exception notice from them.\n"
"%2$s\n"
"%2$sIf you write modifications of your own for %1$s, it is your choice\n"
"%2$swhether to permit this exception to apply to your modifications.\n"
"%2$sIf you do not wish that, delete this exception notice.";

#define LGPL_PFX_CT   39
#define LGPL_NAME_CT   9
#define LGPL_OWNER_CT  2

static const char zDne[] =
"%1$sDO NOT EDIT THIS FILE   (%2$s)\n"
"%1$s\n"
"%1$sIt has been autogen-ed  %3$s\n"
"%1$sFrom the definitions    %4$s\n"
"%1$sand the template file   %5$s";

#define DNE_PFX_CT 5

#include <signal.h>
#include <string.h>

#include "autogen.h"
#define  EXPR_PRIVATE
#include "agexpr.h"

#ifndef HAVE_STRFTIME
#  include "compat/strftime.c"
#endif

tSCC zShortArgs[]  = "ERROR:  collective function %s requires two arguments\n";
tSCC zNotString[]  = "ERROR:  %s requires one string argument\n";
tSCC zNotString2[] = "ERROR:  %s requires two or more string arguments\n";
tSCC zStringBuf[]  = "string buffer";

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *  Utility Routines
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *  findBase
 *
 *  Find the index offset to the base entry for the stack.
 *  Normally, the base is at level 1, but sometimes there is
 *  a marker that says "stop here".
 */
    STATIC int
findBase( int          level,
           tValStack*  pStk )
{
    int  res = 0;
    for (;;) {
        /*
         *  IF we find a stack mark
         *  THEN "res" is the offset down to this marker
         */
        if (pStk->valType == VT_STK_MARK)
            return res;
        /*
         *  IF there are no entries after the current one,
         *  THEN this one must be the base.
         */
        if (--level <= 0)
            return res;
        /*
         *  Go down to the next entry
         */
        pStk--;
        res++;
    }
}


/*
 *  stackEntries
 *
 *  Push all the text macros of a compound macro name onto the stack
 */
    STATIC int
stackEntries( tValStack* pStk, char* pzName, tDefEntry*  pCurDef )
{
    char*      pzField = strchr( pzName, '.' );
    tDefEntry* pE;
    ag_bool    isIndexed;
    int        ttlCt = 0;


    /*
     *  IF there are sub fields, then temporarily trim off the names
     *     and find the name of the block entry.
     */
    if (pzField != (char*)NULL)
        *(pzField++) = NUL;

    pE = findDefEntry( pzName, pCurDef, &isIndexed );

    /*
     *  IF we cannot find it,
     *  THEN we have a naming error.
     */
    if (pE == (tDefEntry*)NULL) {
        if (pzField != (char*)NULL)
            pzField[-1] = '.';
        return -99999;
    }

    /*
     *  IF the entry is a text block,
     *  THEN put it (them) on the stack.
     */
    if (pE->macType == MACTYP_TEXT) {
        int ct = 1;

        for (;;) {
            pStk->val.pz  = pE->pzValue;
            pStk->valType = VT_STRING;
            pE = pE->pTwin;
            if (isIndexed || (pE == (tDefEntry*)NULL))
                return ct;
            ct++;
            pStk++;
        }
        /* NOTREACHED */
    }

    /*
     *  IF there are no subfields for a block macro,
     *  THEN we cannot stack the entries.  We can only
     *       stack text macro values!!
     */
    if (pzField == (char*)NULL) {
        if (pzField != (char*)NULL)
            pzField[-1] = '.';
        return -99999;
    }

    /*
     *  FOR each twin of the type we found, ...
     */
    do  {
        tDefEntry* pList = (tDefEntry*)(void*)(pE->pzValue);

        /*
         *  add the result of the deeper searches to our
         *       current search
         */
        int lpCt = stackEntries( pStk, pzField, pList );

        /*
         *  Advance our stack pointer and total count by what was found
         */
        if (lpCt > 0) {
            pStk  += lpCt;
            ttlCt += lpCt;
        }

        pE = pE->pTwin;
    } while (pE != (tDefEntry*)NULL);

    pzField[-1] = '.';
    return ttlCt;
}


    STATIC int
makeString( int         level,
            tValStack*  pStk,
            tDefEntry*  pCurDef,
	    tCC*        pzNewLine,
            size_t      newLineSize )
{
    char*      pzDta;
    char*      pzScn;
    char*      pzRet;
    size_t     dtaSize    = sizeof( "\"\"" );

    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "STACK" );
        return -1;
    }

    pzDta = pStk->val.pz;

    for (;;) {
        char ch = *(pzDta++);
        if ((ch >= ' ') && (ch <= '~')) {
            dtaSize++;
            if ((ch == '"') || (ch == '\\'))
                dtaSize++;

        } else switch (ch) {
        case NUL:
            goto loopBreak;

        case '\n':
            dtaSize += newLineSize;
            break;

        case '\t':
        case '\a':
        case '\b':
        case '\f':
        case '\r':
        case '\v':
            dtaSize += 2;
            break;

        default:
            dtaSize += 4;
        }
    } loopBreak:;

    pzRet = pzDta = (char*)AGALOC( dtaSize );
    if (pzDta == (char*)NULL) {
        fprintf( stderr, zAllocErr, pzProg,
                 dtaSize, zStringBuf );
        longjmp( fileAbort, FAILURE );
    }

    pzScn = pStk->val.pz;
    *(pzDta++) = '"';

    for (;;) {
        unsigned char ch = (unsigned char)*pzScn;
        if ((ch >= ' ') && (ch <= '~')) {
            if ((ch == '"') || (ch == '\\'))
                /*
                 *  We must escape these characters in the output string
                 */
                *(pzDta++) = '\\';
            *(pzDta++) = ch;

        } else switch (ch) {
        case NUL:
            goto copyDone;

        case '\n':
            /*
             *  Replace the new-line with its escaped representation.
             *  Also, break and restart the output string, indented
             *  7 spaces (so that after the '"' char is printed,
             *  any contained tabbing will look correct).
             *  Do *not* start a new line if there are no more data.
             */
            if (pzScn[1] == NUL) {
                *(pzDta++) = '\\';
                *(pzDta++) = 'n';
                goto copyDone;
            }

            strcpy( pzDta, pzNewLine );
            pzDta += newLineSize;
            break;

        case '\t':
            *(pzDta++) = '\\';
            *(pzDta++) = 't';
            break;

        case '\a':
            *(pzDta++) = '\\';
            *(pzDta++) = 'a';
            break;

        case '\b':
            *(pzDta++) = '\\';
            *(pzDta++) = 'b';
            break;

        case '\f':
            *(pzDta++) = '\\';
            *(pzDta++) = 'f';
            break;

        case '\r':
            *(pzDta++) = '\\';
            *(pzDta++) = 'r';
            break;

        case '\v':
            *(pzDta++) = '\\';
            *(pzDta++) = 'v';
            break;

        default:
            pzDta += sprintf( pzDta, "\\%03o", ch );
        }

        pzScn++;
    } copyDone:

    /*
     *  End of string.  Terminate the quoted output.
     *  If necessary, deallocate the text string.
     *  Return the scan resumption point.
     */
    *(pzDta++) = '"';
    *pzDta = NUL;
    if (pStk->valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk->val.pz );
    pStk->valType = VT_ALLOC_STR;
    pStk->val.pz  = pzRet;
    return level;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *  EXPRESSION EVALUATION ROUTINES
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*=evalexpr CAP
 *
 *  descrip:
 *
 *  Replace the top of stack as a Capitalized String.
=*/
    STATIC int
evalExpr_CAP( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "CAP" );
        return -1;
    }

    if (pStk->valType == VT_STRING) {
        AGDUPSTR( pStk->val.pz, pStk->val.pz );
        pStk->valType = VT_ALLOC_STR;
    }
    upcase( pStk->val.pz, CC_All_Cap );

    return level;
}


/*=evalexpr COUNT
 *
 *  descrip:
 *
 *  Replace TOS with the count of objects in the
 *  macro named in the top of stack.
=*/
    STATIC int
evalExpr_COUNT( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    t_word      resVal = 0;
    tDefEntry*  pE;
    char*       pzField;
    ag_bool     isIndexed;

    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "macro-LEN" );
        return -1;
    }

    do  {  /* do ... once loop */
        pzField = strchr( pStk->val.pz, '.' );

        if (pzField != (char*)NULL)
            *(pzField++) = NUL;

        pE = findDefEntry( pStk->val.pz, pCurDef, &isIndexed );

        /*
         *  IF we cannot find the entry,
         *  THEN the result is zero
         */
        if (pE == (tDefEntry*)NULL)
            break;

        /*
         *  IF there are no subfields specified,
         *  THEN the result is the twin count, whether this is a block
         *       type or a text type macro.
         */
        if (pzField == (char*)NULL) {
            /*
             *  Count twins IFF this is not an indexed entry...
             */
            if (isIndexed)
                resVal = 1;

            else do {
                resVal++;
                pE = pE->pTwin;
            } while (pE != (tDefEntry*)NULL);
            break;
        }

        /*
         *  IF     the macro declaration is text type,
         *     AND subfields were specified
         *  THEN the count is zero (there are no subfields for text macros)
         */
        if (pE->macType == MACTYP_TEXT)
            break;

        /*
         *  Recurse and return the value of the recursion
         */
        level++;
        pStk++;

        /*
         *  IF this is an indexed group,
         *  THEN count just the members of this group.
         */
        if (isIndexed) {
            pStk->val.pz  = pzField;
            pStk->valType = VT_STRING;
            evalExpr_COUNT( level, pStk, (tDefEntry*)(void*)(pE->pzValue) );
            resVal = pStk->val.val;
            break;
        }

        /*
         *  FOR each entry of the type we found, ...
         */
        do  {
            pStk->val.pz  = pzField;
            pStk->valType = VT_STRING;

            /*
             *  Count the members of the current group of defines
             */
            evalExpr_COUNT( level, pStk, (tDefEntry*)(void*)(pE->pzValue) );
            resVal += pStk->val.val;

            pE = pE->pTwin;
        } while (pE != (tDefEntry*)NULL);

        pStk--;
        level--;

    } while (AG_FALSE);

    /*
     *  IF this is an allocated string,
     *  THEN free it.  (we must be done at this point, too)
     */
    if (pStk->valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk->val.pz );

    /*
     *  Else Clean up the name string.
     */
    else if (pzField != (char*)NULL)
        pzField[-1] = '.';

    pStk->valType = VT_VALUE;
    pStk->val.val = resVal;

    return level;
}


/*=evalexpr DFILE
 *
 *  descrip:
 *
 *  Push the name of the definitions file onto the stack
=*/
    STATIC int
evalExpr_DFILE( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    pStk++;
    pStk->valType = VT_STRING;
    pStk->val.pz  = pBaseCtx->pzFileName;
    return level + 1;
}


/*=evalexpr DNE
 *
 *  descrip:
 *
 *  Replace TOS with a Do Not Edit warning string.
 *  Each line of that string will be prefixed with the text string
 *  on the top of the stack.
=*/
    STATIC int
evalExpr_DNE( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    char     zTimeBuf[ 128 ];
    char*    pz;

    if (  (level < 1)
       || (pStk[ 0].valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "DNE" );
        return -1;
    }

    {
        time_t    curTime = time( (time_t*)NULL );
        struct tm*  pTime = localtime( &curTime );
        strftime( zTimeBuf, 128, "%A %B %e, %Y at %r %Z", pTime );
    }

    {
        size_t len = sizeof( zDne )
            + (strlen( pStk->val.pz ) * DNE_PFX_CT)
            + strlen( pzTemplFileName ) + strlen( pBaseCtx->pzFileName )
            + strlen( pCurFp->pzName )  + strlen( zTimeBuf );
        pz = (char*)AGALOC( len );
        if (pz == (char*)NULL) {
            fprintf( stderr, zAllocErr, pzProg, len,
                     "Do-Not-Edit string" );
            longjmp( fileAbort, FAILURE );
        }
    }

    sprintf( pz, zDne, pStk->val.pz, pCurFp->pzName,
             zTimeBuf, pzDefineFileName, pzTemplFileName );

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

    pStk->val.pz  = pz;
    pStk->valType = VT_ALLOC_STR;
    return level;
}


/*=evalexpr DOWN
 *
 *  descrip:
 *
 *  Replace the top of stack as a lower case string.
=*/
    STATIC int
evalExpr_DOWN( int         level,
              tValStack*  pStk,
              tDefEntry*  pCurDef )
{
    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "DOWN" );
        return -1;
    }

    if (pStk->valType == VT_STRING) {
        AGDUPSTR( pStk->val.pz, pStk->val.pz );
        pStk->valType = VT_ALLOC_STR;
    }
    upcase( pStk->val.pz, CC_all_low );

    return level;
}


/*=evalexpr ENV
 *
 *  descrip:
 *
 *  Replace the TOS with contents of a DEFINE or an environment variable
 *  named by it.  These values may be set @strong{either} before
 *  execution using your regular shell commands @strong{or} with the
 *  @code{SETENV} macro function.
=*/
    STATIC int
evalExpr_ENV( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "ENV" );
        return -1;
    }

    {
        char* pz = (char*)getDefine( pStk->val.pz );

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

        pStk->val.pz  = pz ? pz : "";
        pStk->valType = VT_STRING;
    }

    return level;
}


/*=evalexpr EXIST
 *
 *  descrip:
 *
 *  Replace TOS with 1 if top of stack names a defined macro, 0 otherwise.
=*/
    STATIC int
evalExpr_EXIST( int         level,
                tValStack*  pStk,
                tDefEntry*  pCurDef )
{
    t_word      resVal  = 1;
    tDefEntry*  pE;
    char*       pzField;
    ag_bool     isIndexed;

    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "EXIST" );
        return -1;
    }

    /*
     *  Pseudo-loop.  Do once and break out early when
     *  result is known.
     */
    do  {
        pzField = strchr( pStk->val.pz, '.' );

        if (pzField != (char*)NULL)
            *(pzField++) = NUL;

        pE = findDefEntry( pStk->val.pz, pCurDef, &isIndexed );

        /*
         *  No such entry?  return zero
         */
        if (pE == (tDefEntry*)NULL) {
            resVal = 0;
            break;
        }

        /*
         *  No subfield?  return one
         */
        if (pzField == (char*)NULL) {
            resVal = 1;
            break;
        }

        /*
         *  a subfield for a text macro?  return zero
         */
        if (pE->macType == MACTYP_TEXT) {
            resVal = 0;
            break;
        }

        /*
         *  Recurse and return the value of the recursion
         */
        level++;
        pStk++;

        do  {
            tDefEntry*  pMemberList = (tDefEntry*)(void*)(pE->pzValue);
            pStk->val.pz  = pzField;
            pStk->valType = VT_STRING;
            evalExpr_EXIST( level, pStk, pMemberList );

            /*
             *  IF an entry was found THEN at least one exists
             */
            if ((pStk->val.val != 0) || isIndexed)
                break;

            pE = pE->pTwin;
        } while (pE != (tDefEntry*)NULL);

        resVal = pStk->val.val;
        pStk--;
        level--;

    } while (AG_FALSE);

    if (pStk->valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk->val.pz );
    pStk->valType = VT_VALUE;
    pStk->val.val = resVal;
    return level;
}


/*=evalexpr FIRST
 *
 *  descrip:
 *
 *  If AutoGen is currently inside of a _FOR macro function
 *  and this is the first iteration of that loop,
 *  then push a 1 onto the stack, otherwise push a 0.
=*/
    STATIC int
evalExpr_FIRST( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    pStk++;
    pStk->valType = VT_VALUE;
    pStk->val.val = ((forLoopDepth > 0) && (firstForCycle));
    return level + 1;
}


/*=evalexpr GET
 *
 *  descrip:
 *
 *  Replace TOS with the value of the named text macro.
=*/
    STATIC int
evalExpr_GET( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    tDefEntry*  pE;
    ag_bool     isIndexed;

    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "STR" );
        return -1;
    }

    pE = findDefEntry( pStk->val.pz, pCurDef, &isIndexed );

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

    /*
     *  No such macro or a block macro yields an empty string
     */
    if ((pE == (tDefEntry*)NULL) || (pE->macType != MACTYP_TEXT)) {
        pStk->val.pz  = "";

    } else {
        /*
         *  IF this text macro has multiple values,
         *  THEN select the most recent version
         */
        if (  (! isIndexed)
           && (pE->pEndTwin != (tDefEntry*)NULL))
            pE = pE->pEndTwin;

        pStk->val.pz  = pE->pzValue;
    }

    pStk->valType = VT_STRING;

    return level;
}


/*=evalexpr GPL
 *
 *  descrip:
 *
 *  Replace the top two stacked strings with the GNU Public License.  TOS
 *  contains the string to start each output line.  TOS-1 contains the name
 *  of the program the copyright is about.
=*/
    STATIC int
evalExpr_GPL( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    char*    pzName;
    ag_bool  deName;
    char*    pzPfx;
    ag_bool  dePfx;
    size_t   outSize;
    char*    pzOutStr;

    if (  (level <= 1)
       || (pStk[ 0].valType <= VT_VALUE)
       || (pStk[-1].valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "GPL" );
        return -1;
    }

    pzName = pStk[-1].val.pz;
    deName = (pStk[-1].valType == VT_ALLOC_STR);
    pzPfx  = pStk[ 0].val.pz;
    dePfx  = (pStk[ 0].valType == VT_ALLOC_STR);

    outSize = sizeof( zGpl ) - ((GPL_PFX_CT+GPL_NAME_CT) * 4)
            +  (GPL_PFX_CT * strlen( pzPfx ))
            +  (GPL_NAME_CT * strlen( pzName ));

    pzOutStr = (char*)AGALOC( outSize );
    if (pzOutStr == (char*)NULL) {
        fprintf( stderr, zAllocErr, pzProg,
                 outSize, "GPL Notice" );
        longjmp( fileAbort, FAILURE );
    }

    if (sprintf( pzOutStr, zGpl, pzName, pzPfx ) != outSize-1) {
        fprintf( stderr, zAllocErr, pzProg,
                 outSize, "GPL BAD SIZE" );
        longjmp( fileAbort, FAILURE );
    }

    if (deName)
        AGFREE( (void*)pzName );

    if (dePfx)
        AGFREE( (void*)pzPfx );

    pStk[-1].valType = VT_ALLOC_STR;
    pStk[-1].val.pz  = pzOutStr;
    return level-1;
}


/*=evalexpr HILIM
 *
 *  descrip:
 *
 *  Replace TOS with the highest index value for the macro it names.
=*/
    STATIC int
evalExpr_HILIM( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    tDefEntry*  pE;
    ag_bool     isIndexed;

    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "HILIM" );
        return -1;
    }

    pE = findDefEntry( pStk->val.pz, pCurDef, &isIndexed );

    /*
     *  IF we did not find the entry we are looking for
     *  THEN return zero
     *  ELSE search the twin list for the high entry
     */
    if (pE == (tDefEntry*)NULL) {
        pStk->val.val = 0;

    } else if (isIndexed) {
        pStk->val.val = pE->index;

    } else {
        if (pE->pEndTwin != (tDefEntry*)NULL)
            pE = pE->pEndTwin;
        pStk->val.val = pE->index;
    }

    pStk->valType = VT_VALUE;

    return level;
}


/*=evalexpr IN
 *
 *  descrip:
 *
 *  Replace the entire stack with a 1 if the string at the bottom of the
 *  stack occurs again in the rest of the stack, 0 otherwise.  Always leaves
 *  one element on the stack, unless a @code{MARK}-er stops it sooner.
 *  In that case, the @code{MARK}-er will be replaced with the 1 or 0.
=*/
    STATIC int
evalExpr_IN( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    tSCC zFuncName[] = "IN";

    if ((level <= 1) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString2, zFuncName );
        return -1;
    }

    {
        t_word     res = 0;
        int        tCt = findBase( level, pStk );
        tValStack* pSE = pStk - tCt;
        tValStack* pSR = pSE;
        tValStack* pST = pSE+1;

        /*
         *  IF the base found was a stack marker,
         *  THEN the stack bottom for comparisons is one higher,
         *       but the results will go into the marker slot
         */
        if (pSE->valType == VT_STK_MARK) {
            /*
             *  Now we need to play with at least three entries:
             *  the marker, the bottom entry and at least one data entry
             */
            if ((level <= 2) || (tCt <= 1)) {
                fprintf( stderr, zNotString2, zFuncName );
                return -1;
            }
            tCt--;
            pSE++;
            pST++;
            level--;
        }

        if (pSE->valType < VT_STRING) {
            fprintf( stderr, zNotString2, zFuncName );
            return -1;
        }

        /*
         *  The result level will be what "pSR" points to
         */
        level -= tCt;

        /*
         *  Compare the base entry against each of the following entries.
         *  The base entry is "in" the list if there is an exact match
         *  or if one of the test entries is a "*".
         */
        for (;;) {
            /*
             *  IF the value is not a string, it is an error
             */
            if (pST->valType == VT_VALUE) {
                fprintf( stderr, zNotString2, zFuncName );
                return -1;
            }

            /*
             *  Check for either a wild card or a match
             */
            if (strcmp( pST->val.pz, "*" ) == 0)
                 res = 1;
            else res |= (strcmp( pST->val.pz, pSE->val.pz ) == 0);

            /*
             *  IF the value is an allocated string,
             *  THEN deallocate it now.
             */
            if (pST->valType == VT_ALLOC_STR) {
                pST->valType = VT_STRING; /* remove allocation mark */
                AGFREE( (void*)pST->val.pz );
            }

            /*
             *  Advance or exit
             */
            if (--tCt <= 0)
                break;
            pST++;
        }

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

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

    return level;
}


/*=evalexpr INDEX
 *
 *  descrip:
 *
 *  Push the current, most deeply nested block number on top of stack.
 *  It is zero outside the scope of a FOR macro loop.
=*/
    STATIC int
evalExpr_INDEX( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    pStk++;
    pStk->valType = VT_VALUE;
    pStk->val.val = curIndex;
    return level + 1;
}


/*=evalexpr JOIN
 *
 *  descrip:
 *
 *  Glue together the value of every item on the stack with a single space
 *  character between them.  In this fashion, you can easily pass a list of
 *  values to a shell script.  It will stop and replace a @code{MARK}-er
 *  entry, if found.
=*/
    STATIC int
evalExpr_JOIN( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    int         joinCt = findBase( level, pStk );
    int         resLvl = level - joinCt;
    tValStack*  pSR    = pStk  - joinCt;
    tValStack*  pSB    = pSR;
    char*       pzValu;
    char*       pzRetVal;
    size_t      joinSize;

    /*
     *  We *must* join at least one entry.  No empty joins.
     */
    if (joinCt < 0) {
        fprintf( stderr, zShortArgs, "JOIN" );
        return -1;
    }

    /*
     *  IF the join terminates with a mark,
     *  THEN the data starts with the next entry.
     */
    if (pSB->valType == VT_STK_MARK) {
        pSB++;
        joinCt--;
    }

    /*
     *  Scan the entries and allocate the space we need
     */
    {
        int    ct  = joinCt;
        joinSize = joinCt + 1;

        do  {
            switch (pSB->valType) {
            case VT_VALUE:
                /*
                 *  Make sure there is plenty of room
                 */
                joinSize += sizeof( long ) * 3;
                break;

            case VT_ALLOC_STR:
            case VT_STRING:
                joinSize += strlen( pSB->val.pz );
                break;

            default:
                break;
            }
            pSB++;
        } while (ct-- > 0);

        pzRetVal = pzValu = (char*)AGALOC( joinSize );
        if (pzValu == (char*)NULL) {
            fprintf( stderr, zAllocErr, pzProg, joinSize,
                     "JOIN string" );
            longjmp( fileAbort, FAILURE );
        }

        pSB = pStk - joinCt;
    }

    /*
     *  Now copy the data into the output
     */
    for (;;) {
        switch (pSB->valType) {
        case VT_VALUE:
             sprintf( pzValu, "%d", pSB->val.val );
             break;

        case VT_ALLOC_STR:
            strcpy( pzValu, pSB->val.pz );
            AGFREE( (void*)pSB->val.pz );
            break;

        case VT_STRING:
            strcpy( pzValu, pSB->val.pz );
            break;

        default:
            break;
        }

        /*
         *   Advance or exit, leaving "pStk" pointing
         *   to the first valid entry in the stack.
         *
         *   FENCEPOST WARNING:
         *   The first object is not joined.  The second and following
         *   objects are joined to the first.
         */
        if (joinCt-- <= 0)
            break;

        pzValu += strlen( pzValu );
        *pzValu++ = ' ';
        pSB++;
    }

    pSR->valType = VT_ALLOC_STR;
    pSR->val.pz  = pzRetVal;

    return resLvl;
}


/*=evalexpr KRSTR
 *
 *  descrip:
 *
 *  Stringify the TOS, using K&R "C" syntax.  Many non-printing
 *  characters are replaced with escape sequences.  After compilation,
 *  the resulting string should be identical to the input value.
 *  Multiple lines are continued with an escaped new line, per K&R
 *  rules.
=*/
    STATIC int
evalExpr_KRSTR( int         level,
              tValStack*  pStk,
              tDefEntry*  pCurDef )
{
    tSCC       zNewLine[] = "\\n\\\n";

    return makeString( level, pStk, pCurDef,
                       zNewLine, sizeof( zNewLine )-1 );
}


/*=evalexpr LAST
 *
 *  descrip:
 *
 *  If AutoGen is currently inside of a _FOR macro function
 *  and this is the last iteration of that loop,
 *  then push a 1 onto the stack, otherwise push a 0.
=*/
    STATIC int
evalExpr_LAST( int         level,
              tValStack*  pStk,
              tDefEntry*  pCurDef )
{
    pStk++;
    pStk->valType = VT_VALUE;
    pStk->val.val = ((forLoopDepth > 0) && (lastForCycle));
    return level + 1;
}


/*=evalexpr LEN
 *
 *  descrip:
 *
 *  Replace the string at TOS with the length of the macro.
 *  For "text" macros, it is the string length.
 *  For "block" macros, it is the number of occurrences.
 *  For compound macros (where the name is a block name followed
 *  by a period and another macro name), it is the sum of these
 *  values.
=*/
    STATIC int
evalExpr_LEN( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    t_word      resVal = 0;
    tDefEntry*  pE;
    ag_bool     isIndexed;
    char*       pzField;

    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "macro-LEN" );
        return -1;
    }

    do  {  /* do ... once loop */
        pzField = strchr( pStk->val.pz, '.' );

        if (pzField != (char*)NULL)
            *(pzField++) = NUL;

        pE = findDefEntry( pStk->val.pz, pCurDef, &isIndexed );

        /*
         *  IF we cannot find the entry,
         *  THEN the result is zero
         */
        if (pE == (tDefEntry*)NULL)
            break;

        /*
         *  IF the macro declaration is text type,
         *  THEN ...
         */
        if (pE->macType == MACTYP_TEXT) {

            /*
             *  IF there are subfields specified
             *  THEN the result is zero (there are none for text macros)
             */
            if (pzField != (char*)NULL)
                break;

            /*
             *  IF we found a specific index
             *  THEN the result is the string length
             */
            if (isIndexed) {
                resVal = strlen( pE->pzValue );
            }

            /*
             *  ELSE find the string lengths of each member
             */
            else do {
                resVal += strlen( pE->pzValue );
                pE = pE->pTwin;
            } while (pE != (tDefEntry*)NULL);
            break;
        }

        /*
         *  The macro is a block type macro.
         *  IF there are no subfields specified,
         *  THEN the result is the twin count
         */
        if (pzField == (char*)NULL) {
            if (isIndexed)
                resVal = 1;

            else do  {
                resVal++;
                pE = pE->pTwin;
            } while (pE != (tDefEntry*)NULL);

            break;
        }

        /*
         *  Recurse and return the value of the recursion
         */
        level++;
        pStk++;

        /*
         *  IF this is an indexed group,
         *  THEN count just the members of this group.
         */
        if (isIndexed) {
            pStk->val.pz  = pzField;
            pStk->valType = VT_STRING;
            evalExpr_LEN( level, pStk, (tDefEntry*)(void*)(pE->pzValue) );
            resVal = pStk->val.val;
            break;
        }

        /*
         *  FOR each entry of the type we found, ...
         */
        do  {
            pStk->val.pz  = pzField;
            pStk->valType = VT_STRING;
            evalExpr_LEN( level, pStk, (tDefEntry*)(void*)(pE->pzValue) );
            resVal += pStk->val.val;

            pE = pE->pTwin;
        } while (pE != (tDefEntry*)NULL);

        pStk--;
        level--;

    } while (AG_FALSE);

    /*
     *  IF this is an allocated string,
     *  THEN free it.  (we must be done at this point, too)
     */
    if (pStk->valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk->val.pz );

    /*
     *  Else Clean up the name string.
     */
    else if (pzField != (char*)NULL)
        pzField[-1] = '.';

    pStk->valType = VT_VALUE;
    pStk->val.val = resVal;

    return level;
}


/*=evalexpr LGPL
 *
 *  descrip:
 *
 *  Replace the top three stacked strings with the GNU Library Public
 *  License.  TOS contains the string to start each output line.  TOS-1
 *  contains the copyright owner.  TOS-2 contains the name of the program
 *  the copyright is about.
=*/
    STATIC int
evalExpr_LGPL( int         level,
              tValStack*  pStk,
              tDefEntry*  pCurDef )
{
    char*    pzName;
    ag_bool  deName;
    char*    pzPfx;
    ag_bool  dePfx;
    char*    pzOwner;
    ag_bool  deOwner;
    size_t   outSize;
    char*    pzOutStr;
    size_t   printSize;

    if (  (level <= 2)
       || (pStk[ 0].valType <= VT_VALUE)
       || (pStk[-1].valType <= VT_VALUE)
       || (pStk[-2].valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "name, owner and prefix for LGPL" );
        return -1;
    }

    pStk   -= 2;
    pzName  =  pStk[0].val.pz;
    deName  = (pStk[0].valType == VT_ALLOC_STR);
    pzOwner =  pStk[1].val.pz;
    deOwner = (pStk[1].valType == VT_ALLOC_STR);
    pzPfx   =  pStk[2].val.pz;
    dePfx   = (pStk[2].valType == VT_ALLOC_STR);

    outSize = sizeof( zLgpl )
        - ((LGPL_PFX_CT+LGPL_NAME_CT+LGPL_OWNER_CT) * 4)
            + (LGPL_PFX_CT   * strlen( pzPfx ))
            + (LGPL_NAME_CT  * strlen( pzName ))
            + (LGPL_OWNER_CT * strlen( pzOwner ));

    pzOutStr = (char*)AGALOC( outSize );
    if (pzOutStr == (char*)NULL) {
        fprintf( stderr, zAllocErr, pzProg,
                 outSize, "LGPL Notice" );
        longjmp( fileAbort, FAILURE );
    }

    printSize = sprintf( pzOutStr, zLgpl, pzName, pzPfx, pzOwner );
    if (printSize != outSize-1) {
        fprintf( stderr, zAllocErr, pzProg,
                 outSize, "LGPL BAD SIZE" );
        longjmp( fileAbort, FAILURE );
    }

    if (deName)
        AGFREE( (void*)pzName );

    if (deOwner)
        AGFREE( (void*)pzOwner );

    if (dePfx)
        AGFREE( (void*)pzPfx );

    pStk->valType = VT_ALLOC_STR;
    pStk->val.pz  = pzOutStr;
    return level-2;
}


/*=evalexpr LOLIM
 *
 *  descrip:
 *
 *  Replace the TOS with the lowest index value for the named macro.
=*/
    STATIC int
evalExpr_LOLIM( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "LOLIM" );
        return -1;
    }

    {
        tDefEntry* pE = findDefEntry( pStk->val.pz, pCurDef, (ag_bool*)NULL );

        /*
         *  IF we found the entry we are looking for
         *    AND it is not a text entry  ...
         */
        if (pE != (tDefEntry*)NULL)

             pStk->val.val = pE->index;
        else pStk->val.val = 0;

        pStk->valType = VT_VALUE;
    }

    return level;
}


/*=evalexpr MARK
 *
 *  descrip:
 *
 *  Mark a place in the stack where the IN, JOIN, MIN, MAX and SUM
 *  expression functions are to stop processing.
=*/
    STATIC int
evalExpr_MARK( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    tSCC zMark[] = "*MARK*";
    pStk++;
    pStk->valType = VT_STK_MARK;
    pStk->val.pz  = (char*)zMark;
    return level + 1;
}


/*=evalexpr MAP
 *
 *  descrip:
 *
 *  This command is deprecated.  Use @code{_tr}.
=*/
    STATIC int
evalExpr_MAP( int         level,
              tValStack*  pStk,
              tDefEntry*  pCurDef )
{
    if (level < 1) {
        fprintf( stderr, zShortArgs, "MAX" );
        return -1;
    }

    switch (pStk->valType) {
    case VT_STRING:
	pStk->valType = VT_ALLOC_STR;
	pStk->val.pz  = strdup( pStk->val.pz );
	break;

    case VT_ALLOC_STR:
	break;

    default:
        fputs( "the MAP expression function requires a string argument\n",
               stderr );
        return -1;
    }

    strtransform( pStk->val.pz, pStk->val.pz );
    return level;
}


/*=evalexpr MAX
 *
 *  descrip:
 *
 *  Replace entire stack with the highest value in the stack.
=*/
    STATIC int
evalExpr_MAX( int         level,
              tValStack*  pStk,
              tDefEntry*  pCurDef )
{
    if (level < 1) {
        fprintf( stderr, zShortArgs, "MAX" );
        return -1;
    }

    {
        t_word  res = WORD_MIN;

        for (;;) {
            t_word  tVal;

            switch (pStk->valType) {
            case VT_STK_MARK:
                /*
                 *  Replace the stack mark with the value, rather than
                 *  the stack bottom.  Decrement "level" so "level+1"
                 *  will represent the correct value.
                 */
                level--;
                goto endFor;

            case VT_VALUE:
                tVal = pStk->val.val;
                break;

            case VT_ALLOC_STR:
                tVal = (t_word)strtol( pStk->val.pz, (char**)NULL, 0 );
                AGFREE( (void*)pStk->val.pz );
                break;

            case VT_STRING:
                tVal = (t_word)strtol( pStk->val.pz, (char**)NULL, 0 );
                break;

            default:
                tVal = LONG_MIN;
            }

            if (tVal > res)
                res = tVal;

            /*
             *   Advance or exit, leaving "pStk" pointing
             *   to the first valid entry in the stack.
             */
            if (--level <= 0)
                break;
            pStk--;
        } endFor:

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

    return level+1;
}


/*=evalexpr MIN
 *
 *  descrip:
 *
 *  Replace entire stack with the lowest value in the stack.
=*/
    STATIC int
evalExpr_MIN( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    if (level < 1) {
        fprintf( stderr, zShortArgs, "MIN" );
        return -1;
    }

    {
        t_word  res = WORD_MAX;

        for (;;) {
            t_word  tVal;

            switch (pStk->valType) {
            case VT_STK_MARK:
                /*
                 *  Replace the stack mark with the value, rather than
                 *  the stack bottom.  Decrement "level" so "level+1"
                 *  will represent the correct value.
                 */
                level--;
                goto endFor;

            case VT_VALUE:
                 tVal = pStk->val.val;
                 break;

            case VT_ALLOC_STR:
                tVal = (t_word)strtol( pStk->val.pz, (char**)NULL, 0 );
                AGFREE( (void*)pStk->val.pz );
                break;

            case VT_STRING:
                tVal = (t_word)strtol( pStk->val.pz, (char**)NULL, 0 );
                break;

            default:
                tVal = LONG_MAX;
            }

            if (tVal < res)
                res = tVal;

            /*
             *   Advance or exit, leaving "pStk" pointing
             *   to the first valid entry in the stack.
             */
            if (--level <= 0)
                break;
            pStk--;
        } endFor:

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

    return level+1;
}


/*=evalexpr OUTFILE
 *
 *  descrip:
 *
 *  Push the name of the current output file onto the stack.
=*/
    STATIC int
evalExpr_OUTFILE( int         level,
                 tValStack*  pStk,
                 tDefEntry*  pCurDef )
{
    pStk++;
    pStk->valType = VT_STRING;
    pStk->val.pz  = pCurFp->pzName;
    return level + 1;
}


/*=evalexpr PREFIX
 *
 *  descrip:
 *
 *  Prefix every line in TOS-1 with the string in TOS.
 *  E.g. if TOS-1 contains the string, "two\\nlines" and TOS
 *  contains "# ", then TOS gets popped and TOS-1 (new TOS) will
 *  contain "# two\\n# lines".
=*/
    STATIC int
evalExpr_PREFIX( int         level,
                tValStack*  pStk,
                tDefEntry*  pCurDef )
{
    char*    pzText;
    ag_bool  deText;
    char*    pzPfx;
    ag_bool  dePfx;
    size_t   pfxSize;
    size_t   pfxTrimmedSize;

    if (  (level <= 1)
       || (pStk[ 0].valType <= VT_VALUE)
       || (pStk[-1].valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "PREFIX" );
        return -1;
    }

    pzPfx  = pStk->val.pz;
    dePfx  = (pStk->valType == VT_ALLOC_STR);
    pStk--;
    pzText = pStk->val.pz;
    deText = (pStk->valType == VT_ALLOC_STR);

    /*
     *  The prefix size is the string length of the prefix.
     *  The trimmed size is the length with trailing spaces removed.
     */
    pfxTrimmedSize = pfxSize = strlen( pzPfx );
    while ((pfxTrimmedSize > 0) && isspace( pzPfx[ pfxTrimmedSize-1 ]) )
        pfxTrimmedSize--;

    /*
     *  Compute the outSize by adding "pfxSize" times tht number of
     *  newline characters found.  (Actually, one extra because we
     *  emit a prefix to start, and then one more after *each* newline
     *  we find.  Thus, for example, if there are two newline characters,
     *  we will emit the prefix *THREE* times.)  We do not bother
     *  to distinguish trimmed and non-trimmed sizes, so our size
     *  may be a few bytes too large.
     */
    {
        size_t  outSize = strlen( pzText ) + pfxSize + 1;
        char*   pzCur   = pzText;

        for (;;) {
            char* pzNxt = strchr( pzCur, '\n' );
            if (pzNxt == (char*)NULL)
                break;
            outSize += pfxSize;
            pzCur = pzNxt + 1;
        }

        pStk->val.pz  = (char*)AGALOC( outSize );
        if (pStk->val.pz == (char*)NULL) {
            fprintf( stderr, zAllocErr, pzProg,
                     outSize, zStringBuf );
            longjmp( fileAbort, FAILURE );
        }
        pStk->valType = VT_ALLOC_STR;
    }

    {
        char* pzOut = pStk->val.pz;
        char* pzScn = pzText;
        char* pcNewLn;

        for (;;) {
            /*
             *  Copy over the prefix into the output line
             */
            strcpy( pzOut, pzPfx );

            /*
             *  IF we find a newline character,
             *  THEN NUL-terminate the line
             */
            pcNewLn = strchr( pzScn, '\n' );
            if (pcNewLn != (char*)NULL)
                *pcNewLn = NUL;

            /*
             *  IF the source line is empty,
             *  THEN advance the output only by the trimmed size (maybe 0)
             *  ELSE advance the output by the prefix length
             */
            if (*pzScn == NUL)
                 pzOut += pfxTrimmedSize;
            else pzOut += pfxSize;

            /*
             *  Copy the source string to the output buffer
             */
            strcpy( pzOut, pzScn );

            /*
             *  IF we did not find any more newlines,
             *  THEN we are done now.  The copy above will have NUL
             *       terminated the output string.
             */
            if (pcNewLn == (char*)NULL)
                break;

            /*
             *  Advance the output pointer by the number of characters we
             *  copied from the source line.
             */
            pzOut += (off_t)(pcNewLn - pzScn);

            /*
             *  Restore the newline we clobbered and add one to the output.
             *  Advance the scan.
             */
            *(pcNewLn++) = *(pzOut++) = '\n';
            pzScn = pcNewLn;
        }
    }

    if (deText)
        AGFREE( (void*)pzText );

    if (dePfx)
        AGFREE( (void*)pzPfx );

    return level - 1;
}


/*=evalexpr PRINTF
 *
 *  descrip:
 *
 *  Using TOS as a format, use vsprintf to make new TOS.  If more than
 *  1 arg is to be consumed, the format must be prefixed with "n$"
 *  where "n" is the number of arguments needed *after* the format and
 *  '$' is the literal dollar sign character.  The format plus "n"
 *  items will get popped and the resulting string pushed back onto
 *  the stack.
 *
 *  @noindent
 *  For example:
 *
 *  @example
 *  [#_EVAL prog_name _get _outfile
 *       "#2$echo '%s_%s'|tr '[a-z]./' '[A-Z]__'" _printf _shell#]
 *  @end example
 *  @noindent
 *
 *  will do the following:
 *  @enumerate
 *  @item
 *  Push the string "@code{prog_name}" onto the stack.
 *
 *  @item
 *  Replace it with the text the @code{prog_name} definition contains.
 *
 *  @item
 *  Push the name of the current output file.
 *
 *  @item
 *  Push a format string that consumes two additional arguments.
 *  (Note the @code{#2$} prefix.)
 *
 *  @item
 *  Invoke @code{vsprintf} and replace the top three stack entries
 *  with the result.
 *
 *  @item
 *  Lastly, replace this string with the output produced by
 *  running it through the @code{shell} expression function.
 *  @end enumerate
=*/
    STATIC int
evalExpr_PRINTF( int         level,
                 tValStack*  pStk,
                 tDefEntry*  pCurDef )
{
    static char  zFmtBuf[ 0x4000 ];
    static char* pzBuf = zFmtBuf;

    u_int        argCt = (level > 1) ? 1 : 0;
    snv_pointer  argV[ MAX_TKN ];
    char*        pzFmt = pStk->val.pz;
    int          prtSize;

    if ((level <= 1) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString2, "PRINTF" );
        return -1;
    }

    /*
     *  IF the format begins with a number followed by a '$',
     *  THEN the number is the number of format arguments to consume.
     *  Otherwise, we will assume that we consume 1.
     *  0 args is dumb but okay.
     */
    if (isdigit( *pzFmt )) {
        char*  pzEndTest;
        argCt = strtol( pzFmt, &pzEndTest, 0 );
        if ((argCt >= MAX_TKN) || (*pzEndTest++ != '$'))
             argCt = 1;
        else pzFmt = pzEndTest;
    }

    if (level <= argCt)
        return -1;

    level -= argCt;
    pStk  -= argCt;

    /*
     *  move the arguments onto the arg vector stack
     */
    {
        int     ct = argCt;
  
        while (ct-- > 0)
        {
            /* We must be careful not to try and pass a `long' by
               casting it to a `char*'.  `long' is allowed to be
               bigger than `char*'! */
            switch (pStk[ ct ].valType)
            {
            case VT_STRING:
            case VT_ALLOC_STR:
                argV[ ct ] = (snv_pointer)pStk[ ct ].val.pz;
                break;

            case VT_VALUE:
                argV[ ct ] = (snv_pointer)pStk[ ct ].val.val;
                break;

            default:
            case VT_STACK:
            case VT_STK_MARK:
                argV[ ct ] = (snv_pointer)NULL;
                break;
            }
        }
    }

    if (pzBuf > (zFmtBuf + sizeof( zFmtBuf ) - 4096))
        pzBuf = zFmtBuf;

    /*
     *  It is possible for a user to specify a "%s" format and supply
     *  an integer value (invalid address).  So, we write an ugly subroutine
     *  that will catch memory faults and exit in those cases.
     */
    prtSize = safePrintf( pzBuf, sizeof( zFmtBuf ) - 1
                          - (size_t)(pzBuf - zFmtBuf),
                          pzFmt, (snv_constpointer*)argV );

    /*
     *  Free the current stack slot, if necessary
     */
    if (pStk->valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk->val.pz );

    /*
     *  Put the formatted string on the stack
     */
    pStk->val.pz  = pzBuf;
    pStk->valType = VT_STRING;
    pzBuf += prtSize + 1;

    /*
     *  Deallocate any stack residue
     */
    for (;;) {
        if (argCt-- <= 0)
            break;
        if (pStk->valType == VT_ALLOC_STR)
            AGFREE( (void*)pStk->val.pz );
        pStk++;
    }

    /*
     *  Return the size of the remaining stack.
     */
    return level;
}


/*=evalexpr SFX
 *
 *  descrip:
 *
 *  Push the current active suffix.  See @code{generate} in the
 *  @code{Declarations Input} section above.
=*/
    STATIC int
evalExpr_SFX( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    pStk++;
    pStk->valType = VT_STRING;
    pStk->val.pz  = (char*)pzCurSfx;
    return level + 1;
}


/*=evalexpr SHELL
 *
 *  descrip:
 *
 *  Replace the TOS by the output produced by writing the value to
 *  a server shell and reading the output back in.  The template
 *  programmer is responsible for ensuring that it completes
 *  within 10 seconds.  If it does not, the server will be killed,
 *  the output tossed and a new server started.
 *
 *  NB:  This is the same server process used by the '#shell'
 *  definitions directive and backquoted @code{`} definitions.
 *  There may be left over state from previous shell expressions
 *  and the @code{`} processing in the declarations.  However, a
 *  @code{cd} to the original directory is always issued before
 *  the new command is issued.
=*/
    STATIC int
evalExpr_SHELL( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    /*
     *  There must be something on the stack
     *  and it must be a string of some sort.
     */
    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "SHELL" );
        return -1;
    }

    {
        char* pz = runShell( pStk->val.pz );

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

        if (pz == (char*)NULL) {
            pStk->val.pz  = "";
            pStk->valType = VT_STRING;
        } else {
            TAGMEM( pz, "shell expression" );
            pStk->val.pz  = pz;
            pStk->valType = VT_ALLOC_STR;
        }
    }

    return level;
}


/*=evalexpr SHRSTR
 *
 *  descrip:
 *
 *  Convert the text of the string at TOS into a singly quoted string
 *  that a normal shell will process into the original string.
 *  (It will not do macro expansion later, either.)
 *  Contained single quotes become tripled, with the middle quote
 *  escaped with a backslash.  Normal shells will reconstitute the
 *  original string.
=*/
    STATIC int
evalExpr_SHRSTR( int         level,
                tValStack*  pStk,
                tDefEntry*  pCurDef )
{
    static const char zQ[] = "'\\''";

    char*      pzDta = pStk->val.pz;
    char*      pzRet;
    char*      pz;
    size_t     dtaSize;

    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "STACK" );
        return -1;
    }

    dtaSize = strlen( pzDta ) + 3;
    pz = pzDta-1;
    for (;;) {
        pz = strchr( pz+1, '\'' );
        if (pz == (char*)NULL)
            break;
        dtaSize += STRSIZE( zQ );
    }

    pz = pzRet = (char*)AGALOC( dtaSize );
    if (pz == (char*)NULL) {
        fprintf( stderr, zAllocErr, pzProg,
                 dtaSize, zStringBuf );
        longjmp( fileAbort, FAILURE );
    }
    *(pz++) = '\'';

    for (;;) {
        switch (*(pz++) = *(pzDta++)) {
        case NUL:
            goto loopDone;

        case '\'':
            strcpy( pz-1, zQ );
            pz += STRSIZE( zQ ) - 1;
        }
    } loopDone:;
    pz[-1] = '\'';
    *pz    = NUL;

    /*
     *  End of string.  Terminate the quoted output.
     *  If necessary, deallocate the text string.
     *  Return the scan resumption point.
     */
    if (pStk->valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk->val.pz );

    pStk->valType = VT_ALLOC_STR;
    pStk->val.pz  = pzRet;
    return level;
}


/*=evalexpr SHSTR
 *
 *  descrip:
 *
 *  Convert the text of the string at TOS into a double quoted string
 *  that a normal shell will process into the original string.
 *  (Before doing macro expansion, that is.)
 *  The escaped characters are @code{\\\\} and @code{"} and @code{#}.
 *  All others are copied directly into the output.
=*/
    STATIC int
evalExpr_SHSTR( int         level,
               tValStack*  pStk,
               tDefEntry*  pCurDef )
{
    char*      pzDta = pStk->val.pz;
    char*      pzRet;
    char*      pz;
    size_t     dtaSize;

    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "STACK" );
        return -1;
    }

    dtaSize = strlen( pzDta ) + 3;
    pz = pzDta;
    for (;;) {
        switch (*(pz++)) {
        case NUL:
            goto loopDone1;

        case '"':
        case '\\':
            dtaSize++;
        }
    } loopDone1:;

    pz = pzRet = (char*)AGALOC( dtaSize );
    if (pz == (char*)NULL) {
        fprintf( stderr, zAllocErr, pzProg,
                 dtaSize, zStringBuf );
        longjmp( fileAbort, FAILURE );
    }
    *(pz++) = '"';

    for (;;) {
        char ch = *(pzDta++);
        switch (ch) {
        case NUL:
            *(pz++) = '"';
            *pz     = NUL;
            goto loopDone2;

        case '"':
        case '\\':
            *(pz++)  = '\\';
            /* FALLTHROUGH */

        default:
            *(pz++) = ch;
        }
    } loopDone2:;

    /*
     *  End of string.  Terminate the quoted output.
     *  If necessary, deallocate the text string.
     *  Return the scan resumption point.
     */
    if (pStk->valType == VT_ALLOC_STR)
        AGFREE( (void*)pStk->val.pz );
    pStk->valType = VT_ALLOC_STR;
    pStk->val.pz  = pzRet;
    return level;
}


/*=evalexpr STACK
 *
 *  descrip:
 *
 *  Pop a compound macro name and push every occurrence of it onto the
 *  stack.  Compound macro names are: @code{blk.text} and mean for every
 *  defined value of @code{text} within every block @code{blk}, push the
 *  value.  If there are blocks within blocks, each nested block name must
 *  be supplied.
=*/
    STATIC int
evalExpr_STACK( int         level,
                tValStack*  pStk,
                tDefEntry*  pCurDef )
{
    tValStack cur = *pStk;
    int       res;

    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "STACK" );
        return -1;
    }

    res = level + stackEntries( pStk, cur.val.pz, pCurDef ) - 1;

    if (cur.valType == VT_ALLOC_STR)
        AGFREE( (void*)cur.val.pz );

    return res;
}


/*=evalexpr STR
 *
 *  descrip:
 *
 *  Stringify the TOS, using ANSI-C syntax.  Many non-printing characters are
 *  replaced with escape sequences.  After compilation, the resulting
 *  string should be identical to the input value.  Multiple lines
 *  are continued with implicit concatenation, per ANSI specifications.
 *  A K&R compiler will choke.
=*/
    STATIC int
evalExpr_STR( int         level,
              tValStack*  pStk,
              tDefEntry*  pCurDef )
{
    tSCC       zNewLine[] = "\\n\"\n       \"";

    return makeString( level, pStk, pCurDef,
                       zNewLine, sizeof( zNewLine )-1 );
}


/*=evalexpr SUM
 *
 *  descrip:
 *
 *  Replace entire stack with the sum of the values in the stack.
=*/
    STATIC int
evalExpr_SUM( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    t_word  res = 0;

    if (level <= 1) {
        fprintf( stderr, zShortArgs, "SUM" );
        return -1;
    }

    for (;;) {
        switch (pStk->valType) {
        case VT_STK_MARK:
            /*
             *  Replace the stack mark with the value, rather than
             *  the stack bottom.  Decrement "level" so "level+1"
             *  will represent the correct value.
             */
            level--;
            goto endFor;

        case VT_VALUE:
             res += pStk->val.val;
             break;

        case VT_ALLOC_STR:
            res += strtol( pStk->val.pz, (char**)NULL, 0 );
            AGFREE( (void*)pStk->val.pz );
            break;

        case VT_STRING:
            res += strtol( pStk->val.pz, (char**)NULL, 0 );
            break;

        case VT_NONE:
            break;
        }

        /*
         *   Advance or exit, leaving "pStk" pointing
         *   to the first valid entry in the stack.
         */
        if (--level <= 0)
            break;
        pStk--;
    } endFor:

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

    return level+1;
}


/*=evalexpr TFILE
 *
 *  descrip:
 *
 *  Push the name of the template file onto the stack
=*/
    STATIC int
evalExpr_TFILE( int         level,
                tValStack*  pStk,
                tDefEntry*  pCurDef )
{
    pStk++;
    pStk->valType = VT_STRING;
    pStk->val.pz  = pzTemplFileName;
    return level + 1;
}


/*=evalexpr TR
 *
 * descrip:
 *
 *  This is approximately the same as the @code{tr(1)} program, except the
 *  string to transform is the first argument.  The second and third arguments
 *  are used to construct mapping arrays for the transformation of the first
 *  argument.
 *
 *      It is too bad this little program has so many different
 *      and incompatible implementations!
=*/
    STATIC int
evalExpr_TR( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    tValStack*   pTo  = pStk--;
    tValStack*   pFr  = pStk--;
    int          prob = 0;

    if (level <= 2) {
        fprintf( stderr, zShortArgs, "TR" );
        return -1;
    }

    switch (pTo->valType) {
    case VT_ALLOC_STR:
    case VT_STRING:

        switch (pFr->valType) {
        case VT_ALLOC_STR:
        case VT_STRING:

            switch (pStk->valType) {
            case VT_ALLOC_STR:
            case VT_STRING:
                goto types_ok;
            }
        }
        /* FALLTHROUGH */

    default:
        fputs( "the TR expression function requires 3 string arguments\n",
               stderr );
        return -1;
    } types_ok:;

    {
        unsigned char  map[ 256 ];
        int   i = 255;
        unsigned char* pzFrom = (unsigned char*)pFr->val.pz;
        unsigned char* pzTo   = (unsigned char*)pTo->val.pz;

        /*
         *  Initialize the map table to a no-op
         */
        do  {
            map[i] = i;
        } while (--i > 0);

        /*
         *  Insert each mapping
         */
        for (;i <= 255;i++) {
            unsigned char fch = *pzFrom++;
            unsigned char tch = *pzTo++;

            /*
             *  If the map-to character is NUL, we use the last character
             */
            if (tch == NUL) {
                pzTo--;
                tch = pzTo[-1];
            }

            /*
             *  If the map-from character is NUL, we are done
             */
            switch (fch) {
            case NUL:
                goto mapDone;

            /*
             *  If the map-from character is a hyphen
             *    *AND* the map-to is a hyphen
             *  THEN it is a range.  If the 'to' range
             *       is too small, we will extend it with
             *       the last character in that range
             */
            case '-':
                if ((i > 0) && (tch == '-')) {
                    unsigned char fs = pzFrom[-2];
                    unsigned char fe = pzFrom[0];
                    unsigned char ts = pzTo[-2];
                    unsigned char te = pzTo[0];
                    if (te != NUL) {
                        while (fs < fe) {
                            map[ fs++ ] = ts;
                            if (ts < te) ts++;
                        }
                        break;
                    }
                }

            /*
             *  Otherwise, just insert an ordinary character mapping
             */
            default:
                map[ fch ] = tch;
            }
        } mapDone:;

        /*
         *  IF the from or to mapping strings were allocated,
         *  THEN deallocate it/them.  We don't need them any more.
         */
        if (pFr->valType == VT_ALLOC_STR)
            free( (void*)pFr->val.pz );

        if (pTo->valType == VT_ALLOC_STR)
            free( (void*)pTo->val.pz );

        /*
         *  IF the value string was allocated, we modify that one.
         *  Otherwise, we allocate a new one and modify that.
         */
        if (pStk->valType == VT_ALLOC_STR)
            pzTo = (unsigned char*)pStk->val.pz;
        else {
            pStk->valType = VT_ALLOC_STR;
            pStk->val.pz =
            (char*)(pzTo = (unsigned char*)strdup( pStk->val.pz ));
        }

        /*
         *  Now, convert the string.
         */
        while (*pzTo != NUL) {
            *pzTo = map[ *pzTo ];
            pzTo++;
        }
    }
    return level-2;
}


/*=evalexpr UP
 *
 *  descrip:
 *
 *  Replace the top of stack as an UPPER CASE string.
=*/
    STATIC int
evalExpr_UP( int         level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "UP" );
        return -1;
    }

    if (pStk->valType == VT_STRING) {
        AGDUPSTR( pStk->val.pz, pStk->val.pz );
        pStk->valType = VT_ALLOC_STR;
    }
    upcase( pStk->val.pz, CC_ALL_UP );

    return level;
}


/*=evalexpr VAL
 *
 *  descrip:
 *
 *  Replace TOS with the numeric value of the top of stack [atoi()].
=*/
    STATIC int
evalExpr_VAL( int        level,
             tValStack*  pStk,
             tDefEntry*  pCurDef )
{
    if ((level <= 0) || (pStk->valType <= VT_VALUE)) {
        fprintf( stderr, zNotString, "VAL" );
        return -1;
    }

    /*
     *  IF this is already a numeric value,
     *  THEN the function is a NOOP.
     */
    if (pStk->valType > VT_VALUE) {
        t_word value = (t_word)strtol( pStk->val.pz, (char**)NULL, 0 );

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

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

    return level;
}


/* end of agExpr.c */
/* end of agExpr.c */
