
/*
 *  $Id: agFunc.c,v 2.9 1999/07/07 19:30:51 bkorb Exp $
 *
 *  This module implements text 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.
 */

#include <time.h>
#include <utime.h>

#define AGFUNC_PRIVATE
#include "autogen.h"

#include <streqv.h>

/*
 *  Return the enumerated function type corresponding
 *  to a name pointed to by the input argument.
 */
    teFuncType
whichFunc( char* pzFuncName )
{
    int           typeIdx;
    tNameType*    pNT;

    switch (*pzFuncName) {
    case '/':
        return FTYP_ENDFOR;

    case '#':
        return FTYP_COMMENT;

    case '=':
        return FTYP_SELECT;

    default:
        if (! isalpha( *pzFuncName ))
             return FTYP_BOGUS;
        return FTYP_COND_EVAL;

    case '_':
        if (! isalpha( *++pzFuncName ))
            return FTYP_SELECT;
    }

    for (typeIdx = NAMED_FUNC_CT, pNT = nameTypeTable;
         --typeIdx >= 0;
         pNT++ ) {
        /*
         *  IF the test name matches for its entire length
         *     AND that is a full match on our name in question
         *  THEN return the type
         */
        if (  (strneqvcmp( pNT->pName, pzFuncName, pNT->cmpLen) == 0)
           && (  isspace( pzFuncName[pNT->cmpLen] )
              || (pzFuncName[pNT->cmpLen] == zEndMac[0])
              || (pzFuncName[pNT->cmpLen] == NUL) ))
            return pNT->fType;
    }

    return FTYP_BOGUS;
}


/*
 *  mLoad_Macro
 */
    tMacro*
mLoad_Macro( tMacro* pM, char** ppzScan )
{
    int     maxTkn;
    char**  ppzStack;
    char*   pz = pM->pzText;

    /*
     *  Skip over the function identifier and following white space
     */
    while ((*pz != NUL) && (! isspace( *pz ))) pz++;
    while (isspace( *pz )) pz++;

    /*
     *  IF there is any text left, tokenize and attach to the struct.
     */
    if (*pz != NUL) {
        maxTkn = 1 + (strlen( pz ) / 2);

        ppzStack   = (char**)AGALOC( maxTkn * sizeof(char**) );
        if (ppzStack == (char**)NULL) {
            fprintf( stderr, zAllocErr, pzProg,
                     maxTkn * sizeof(char**), zTokenList );
            LOAD_ABORT;
        }

        pM->tknCt  = tokenize( pz, maxTkn, ppzStack );
        pM->ppTkns =
            (char**)AGREALOC( (void*)ppzStack,
                              pM->tknCt * sizeof(char**) );
    }

    /*
     *  The tokens should now be used instead of the text pointer
     */
    pM->pzText = (char*)NULL;
    return pM + 1;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *   Exported function procedures
 */
/*=macfunc ERROR
 *
 *  what:    Cease processing
 *  cindex:  error handling
 *  load_func: special
 *
 *  desc:
 *  The arguments are evaluated and printed to the stderr,
 *  the current output file is removed and AutoGen exits
 *  with the EXIT_FAILURE error code.  IF, however, the argument
 *  is the number 0 (zero), then the output file is silently
 *  removed and processing continues with the next suffix.
 *
 *  @noindent
 *  For example, the following will print the quoted error
 *  message and AutoGen will exit:
 *
 *  @example
 *  [#_ERROR "You did not have enough values to continue"#]
 *  @end example
=*/
    tMacro*
mFunc_Error( tMacro* pM, tDefEntry* pCurDef )
{
    tSCC    zErr[] = "DEFINITIONS ERROR for %s:  %s\n";
    char    zVal[ 16 ];
    ag_bool deallocate = AG_FALSE;

    if (pM->tknCt > 1)
      switch ( eval( pM, pCurDef )) {
      default:
      case VT_NONE:
          pM->evalRes = (void*)"** EMPTY ERROR MESSAGE **";
          break;

      case VT_VALUE:
          if (pM->evalRes == (void*)0 )
              longjmp( fileAbort, PROBLEM );

          sprintf( zVal, "%d", (t_word)pM->evalRes );
          pM->evalRes = (void*)zVal;
          break;

      case VT_STRING:
          break;

      case VT_ALLOC_STR:
          deallocate = AG_TRUE;
          break;
      }

    if (  isdigit( *((char*)pM->evalRes) )
       && (strtol(  ((char*)pM->evalRes), (char**)NULL, 0 ) == 0)) {
        if (deallocate)
            AGFREE( pM->evalRes );

        longjmp( fileAbort, PROBLEM );
    }

    fprintf( stderr, zErr, pCurFp->pzName, (char*)pM->evalRes );

    longjmp( fileAbort, FAILURE );
    return (tMacro*)NULL;
}



    tMacro*
mLoad_ERROR( tMacro* pM, char** ppzScan )
{
    return mLoad_INCLUDE( pM, ppzScan );
}


/*=macfunc INCLUDE
 *
 *  what:   Read in and emit a template block
 *  load_func: special
 *
 *  desc:
 *
 *  The entire contents of the named file is inserted at this point.
 *  The contents of the file are processed for macro expansion.  The
 *  arguments are eval-ed, so you may compute the name of the file to
 *  be included.  The included file must not contain any incomplete
 *  function blocks.  Function blocks are template text beginning with
 *  any of the macro functions @samp{_IF}, @samp{_FOR} and
 *  @samp{_CASE} and extending through the respective terminating
 *  macro functions.
=*/
    tMacro*
mFunc_Include( tMacro* pM, tDefEntry* pCurDef )
{
    void*    pToFree    = (void*)NULL;
    char*    pzSaveName = pzTemplFileName;

    /*
     *  IF there is more than one token,
     *  THEN do an expression evaluation
     */
    if (pM->tknCt > 1)
      switch ( eval( pM, pCurDef )) {
      default:
      case VT_NONE:
      case VT_VALUE:
          return pM+1;

      case VT_ALLOC_STR:
          pToFree = pM->evalRes;
          break;

      case VT_STRING:
          break;
      }

    {
        tTemplFile* pTF = loadTemplate( (char*)pM->evalRes );
        tMacro*     pLastM = pTF->pMacros + (pTF->macCt - 1);

        /*
         *  IF the last entry is text,
         *  THEN strip the trailing white space
         *       the including file needs to control spacing.
         */
        if (pLastM->funcCode == FTYP_TEXT) {
            char*  pz = pLastM->pzText;
            pz += strlen( pz );
            while ((pz > pLastM->pzText) && isspace( *pz ))  pz--;
            *pz = NUL;
        }

        generateBlock( pTF->pMacros, pLastM+1, pCurDef );
        unloadTemplate( pTF );
    }

    pzTemplFileName = pzSaveName;

    if (pToFree != (void*)NULL)
        AGFREE( pToFree );

    return pM+1;
}



    tMacro*
mLoad_INCLUDE( tMacro* pM, char** ppzScan )
{
    int     maxTkn;
    char**  ppzStack;
    char*   pz = pM->pzText;

    /*
     *  Skip over the function identifier and following white space
     */
    while (isalpha( *++pz ))  ;
    while (isspace( *pz )) pz++;

    if (*pz == NUL) {
        fprintf( stderr, zTplErr, pzTemplFileName, templLineNo,
                 "no text specified" );
        LOAD_ABORT;
    }

    /*
     *  IF there is any text left, tokenize and attach to the struct.
     */
    maxTkn = 1 + (strlen( pz ) / 2);

    ppzStack   = (char**)AGALOC( maxTkn * sizeof(char**) );
    if (ppzStack == (char**)NULL) {
        fprintf( stderr, zAllocErr, pzProg,
                 maxTkn * sizeof(char**), zTokenList );
        LOAD_ABORT;
    }
    pM->tknCt  = tokenize( pz, maxTkn, ppzStack );

    /*
     *  IF there is only one token, it will not be eval-ed.
     *  Put it directly into the eval result, which will never change.
     *  Otherwise, finish setting up the token list.
     */
    if (pM->tknCt == 1) {
        pM->evalRes = (void*)pz;
        AGFREE( (void*)ppzStack );
    } else {
        pM->ppTkns = (char**)AGREALOC( (void*)ppzStack,
                                       pM->tknCt * sizeof(char**) );
    }

    /*
     *  The tokens should now be used instead of the text pointer
     */
    pM->pzText = (char*)NULL;
    return pM + 1;
}



/*=macfunc SETENV
 *
 *  what:   Compute and Save an Expression
 *
 *  desc:
 *  The arguments are "_EVAL"-ed.  When done, there should be two values
 *  left.  The first is the name of the environment variable and the second
 *  its value.  This value can be used later in expression evaluations using
 *  the @code{_env} expression function.  No data are written to the output.
 *
 *  @noindent
 *  For example:
 *
 *  @example
 *  [#_SETENV SAVE_ name _get _up + text _get#]
 *  @end example
 *
 *  @noindent
 *  This will:
 *
 *  @enumerate
 *  @item
 *  Push @code{SAVE_} onto stack
 *
 *  @item
 *  Push @code{name} onto stack
 *
 *  @item
 *  Replace @code{name} with value of variable @code{name}.
 *
 *  @item
 *  Replace that value with an upper case version of that value
 *
 *  @item
 *  Concatenate this value to the end of @code{SAVE_} and pop
 *  it off the stack.
 *
 *  @item
 *  Push @code{text} onto stack
 *
 *  @item
 *  Replace @code{text} with value of variable @code{text}.
 *  @end enumerate
 *
 *  @noindent
 *  At this point, the expression evaluation is done and there are two
 *  strings remaining on the stack.  @code{SETENV} will now create an
 *  environment variable with the first stack element as the variable name
 *  and the second stack element as its value.
=*/
    tMacro*
mFunc_Setenv( tMacro* pM, tDefEntry* pCurDef )
{
    tSCC zNotString[] = "setenv name must be string";

    ag_bool  dealloc = AG_FALSE;

    char zBuf[ 4096 ];

    switch ( eval( pM, pCurDef )) {
    default:
    case VT_NONE:
    case VT_VALUE:
        fprintf( stderr, zTplErr, pzTemplFileName, pM->lineNo, zNotString );
        longjmp( fileAbort, FAILURE );

    case VT_ALLOC_STR:
        sprintf( zBuf, "%s=", (char*)pM->evalRes );
        AGFREE( pM->evalRes );
        break;

    case VT_STRING:
        sprintf( zBuf, "%s=", (char*)pM->evalRes );
        break;

    case VT_STACK:
    {
        char* pzName;
        char* pzVal;

        tValStack*  pStk = (tValStack*)pM->evalRes;

        if (  (pStk[2].valType != VT_NONE)
           || (  (pStk->valType != VT_STRING)
              && (pStk->valType != VT_ALLOC_STR)) )  {
            fprintf( stderr, zTplErr, pzTemplFileName, pM->lineNo,
                     zNotString );
            longjmp( fileAbort, FAILURE );
        }

        switch (pStk[1].valType) {
        case VT_VALUE:
            sprintf( zBuf, "%s=%d", pStk[0].val.pz, pStk[1].val.val );
            break;

        case VT_ALLOC_STR:
            sprintf( zBuf, "%s=%s", pStk[0].val.pz, pStk[1].val.pz );
            AGFREE( (void*)pStk[1].val.pz );
            break;

        case VT_STRING:
            sprintf( zBuf, "%s=%s", pStk[0].val.pz, pStk[1].val.pz );
            break;
        }

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

        AGFREE( pM->evalRes );
    }
    }

    {
        char* pz;
        AGDUPSTR( pz, zBuf );
        putenv( pz );
    }
    return pM+1;
}


/*=macfunc Cond_Eval
 *
 *  what:  Eval Expression Conditionally
 *  unnamed:
 *  load_func: special
 *
 *  desc:
 *     If the macro text begins with an alphabetic character, it
 *     is assumed to be a text macro name.  If it is, the name is
 *     replaced with the associated value and the resulting expression
 *     is @code{_eval}-ed and printed to the output.  If the name
 *     is unknown or not a text macro name, then the expression is
 *     ignored and no output is produced.
 *
 *     @code{[#var_name ...#]} is thus equivalent to the following:
 *
 *     @example
 *     [#_IF var_name _exist#][#
 *          _EVAL var_name _get ... #][#
 *     _ENDIF#]
 *     @end example
=*/
    tMacro*
mFunc_Cond_Eval( tMacro* pM, tDefEntry* pCurDef )
{
    tDefEntry*  pDef = findDefEntry( pM->ppTkns[0], pCurDef,
                                     (ag_bool*)NULL );

    if (   (pDef != (tDefEntry*)NULL)
        && (pDef->macType == MACTYP_TEXT))
        mFunc_Eval( pM, pCurDef );

    return pM + 1;
}

    tMacro*
mLoad_Cond_Eval( tMacro* pM, char** ppzScan )
{
    static char zGet[] = "_GET";
    char*  apzTkns[ MAX_TKN ];

    /*
     *  "ct" is the number of arguments *past* the name
     */
    int    ct = tokenize( pM->pzText, MAX_TKN, apzTkns )-1;

    /*
     *  Allocate for "<name>", and "_get" in addition
     *  to the number of arguments past the name
     */
    char** pp = (char**)AGALOC( (ct+2) * sizeof( char* ));
    if (pp == (char**)NULL) {
        fprintf( stderr, zAllocErr, pzProg,
                 (ct+2) * sizeof(char**), zTokenList );
        LOAD_ABORT;
    }

    /*
     *  The struct gets the true count
     */
    pM->tknCt  = ct+2;
    pM->ppTkns = pp;

    /*
     *  IF there were any args past the name, move them in...
     */
    if (ct > 0)
        memcpy( (void*)(pp+2), (void*)&(apzTkns[1]), ct * sizeof( char* ));

    pp[0] = apzTkns[0];
    pp[1] = zGet;

    /*
     *  The tokens should now be used instead of the text pointer
     */
    pM->pzText = (char*)NULL;
    return pM+1;
}


/*=macfunc Bogus
 *
 *  what:  Out-of-context or unknown function
 *  unnamed:
 *  not_callable:
=*/
    tMacro*
mFunc_Bogus( tMacro* pM, tDefEntry* pCurDef )
{
    fprintf( stderr, zTplErr, pzTemplFileName, pM->lineNo,
             "Unknown macro function" );
    longjmp( fileAbort, FAILURE );
    return pM;
}


    tMacro*
mLoad_Context( tMacro* pM, char** ppzScan )
{
    char z[ 128 ];
    sprintf( z, "%s function (%d) out of context",
             apzFuncNames[ pM->funcCode ], pM->funcCode );
    fprintf( stderr, zTplErr, pzTemplFileName, pM->lineNo, z );
    LOAD_ABORT;
    return (tMacro*)NULL;
}



/*=macfunc Text
 *
 *  what:  A block of text to be emitted
 *  unnamed:
=*/
    tMacro*
mFunc_Text( tMacro* pM, tDefEntry* pCurDef )
{
    fputs( pM->pzText, pCurFp->pFile );
    return pM + 1;
}



/*=macfunc Comment
 *
 *  what:  A block of comment to be ignored
 *  unnamed:
 *  load_func: special
 *  not_callable:
=*/
    tMacro*
mLoad_Comment( tMacro* pM, char** ppzScan )
{
    return pM;
}
/* end of agFunc.c */
