
/*
 *  agTempl.c
 *  $Id: agTempl.c,v 2.11 1999/09/27 19:22:04 bkorb Exp $
 *  Parse and process the template data descriptions
 */

/*
 *  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 <autoopts.h>
#include "autogen.h"
#include <streqv.h>

/*
 *  The template file pointer is only messed with in this file.
 */
tSCC zNotTemplate[] = "not a template file";
tSCC zUnexEOF[]     = "Unexpected EOF";

static ag_bool haveAG  = AG_FALSE;
static ag_bool haveTPL = AG_FALSE;
static ag_bool haveMod = AG_FALSE;


static char      zStartMac[  16 ]  = "";

static int       startMacLen       = 0;
static char*     pzTemplData       = (char*)NULL;

STATIC void openOutFile( tOutSpec* pOutSpec, tFpStack* );

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 */

    STATIC char*
processDeclLine( char* pzLoad, char* pzFile )
{
    tSCC    zAG[]   = "autogen";
    tSCC    zTPL[]  = "template";
    tSCC    zMode[] = "-*-";

    static tOutSpec**  ppOSList = &pOutSpecList;

    do  {
        /*
         *  Check for editing mode: two "-*-" on a single line
         *  with stuff in between.  Ignore it all.
         */
        if (  (pzLoad[0] == '-')
           && (pzLoad[1] == '*')
           && (pzLoad[2] == '-')  )  {

            char* pz = strstr( pzLoad+3, zMode );
            if (pz != (char*)NULL) {
                pzLoad  = pz + STRSIZE(zMode);
                haveMod = AG_TRUE;
                goto advance;
            }
        }

        /*
         *  We must get the start macro marker before anything else
         */
        if (zStartMac[0] == NUL) {
            char* pz = zStartMac;
            if (! ispunct( *pzLoad )) {
                fprintf( stderr, zTplErr, pzFile, templLineNo,
                         "cannot find start macro mark" );
                LOAD_ABORT;
            }
            do  {
                startMacLen++;
                *(pz++) = *(pzLoad++);
                if ((int)(pz - zStartMac) >= STRSIZE( zStartMac )) {
                    fprintf( stderr, zTplErr, pzFile, templLineNo,
                             "start macro mark too long" );
                    LOAD_ABORT;
                }
            } while (ispunct( *pzLoad ));
            if ((pz - zStartMac) <= 1) {
                fprintf( stderr, zTplErr, pzFile, templLineNo,
                         "start macro mark too small (minimum 2)" );
                LOAD_ABORT;
            }
            *pz = NUL;
            goto advance;
        }

        /*
         *  IF we have not found the 'autogen' mark yet,
         *  THEN we must find it now
         */
        if (! haveAG) {
            if (  (strneqvcmp( pzLoad, zAG, STRSIZE( zAG )) != 0)
               || (! isspace( pzLoad[ STRSIZE( zAG ) ]))  ) {
                fprintf( stderr, zTplErr, pzFile, templLineNo,
                         "not an autogen file" );
                LOAD_ABORT;
            }
            pzLoad += STRSIZE( zAG ) + 1;
            haveAG  = AG_TRUE;
            goto advance;
        }

        /*
         *  IF we have not found the 'template' mark yet,
         *  THEN we must find it now
         */
        if (! haveTPL) {
            if (  (strneqvcmp( pzLoad, zTPL, STRSIZE( zTPL )) != 0)
               || (! isspace( pzLoad[ STRSIZE( zTPL ) ]))  ) {

                fprintf( stderr, zTplErr, pzFile, templLineNo,
                         zNotTemplate );
                LOAD_ABORT;
            }
            pzLoad += STRSIZE( zTPL ) + 1;
            haveTPL  = AG_TRUE;
            goto advance;
        }

        /*
         *  IF the next character is alphanumeric, a period,
         *      a hyphen or an underscore,
         *  THEN process the new suffix...
         */
        if (  isalnum( *pzLoad )
           || (*pzLoad == '.')
           || (*pzLoad == '-')
           || (*pzLoad == '_') ) {

            /*
             *  The following is the complete list of POSIX
             *  required-to-be-legal file name characters.
             *  These are the only characters we allow to
             *  appear in a suffix.  We do, however, add
             *  '=' and '%' because we also allow a format
             *  specification to follow the suffix, separated
             *  by an '=' character.
             */
            tSCC       zFilChars[] = "abcdefghijklmnopqrstuvwxyz"
                                     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                                     "0123456789" "-_./" "=%";
            tOutSpec*  pOS;
            char*      pz;

            /*
             *  Skip over the suffix construct
             */
            int spn = strspn( pzLoad, zFilChars );

            if (procState != PROC_STATE_LOAD_TPL) {
                pzLoad += spn;
                goto advance;
            }

            /*
             *  Allocate the suffix structure
             */
            pOS = (tOutSpec*)AGALOC( sizeof( *pOS ) + (size_t)spn + 1);
            if (pOS == (tOutSpec*)NULL) {
                tSCC zOutSpec[] = "Output Specification";

                fprintf( stderr, zAllocErr, pzProg,
                         sizeof( *pOS ) + (size_t)spn+1, zOutSpec );
                LOAD_ABORT;
            }

            /*
             *  Link it into the global list, maybe
             */
            *ppOSList  = pOS;
            ppOSList   = &pOS->pNext;
            pOS->pNext = (tOutSpec*)NULL;

            /*
             *  Copy the data into the suffix field from our input buffer.
             *  IF the suffix contains its own formatting construct,
             *  THEN split it off from the suffix and set the formatting ptr.
             *  ELSE supply a default.
             */
            strncpy( pOS->zSuffix, pzLoad, spn );
            pOS->zSuffix[ spn ] = NUL;
            pzLoad += spn;

            pz = strchr( pOS->zSuffix, '=' );

            if (pz != (char*)NULL) {
                tSCC zFileFmt3[] = "%s";
                *pz++ = NUL;
                if (*pz == NUL)
                     pOS->pzFileFmt = zFileFmt3;
                else pOS->pzFileFmt = pz;

            } else {
                tSCC zFileFmt1[] = "%s.%s";
                tSCC zFileFmt2[] = "%s%s";

                if (isalnum( pOS->zSuffix[0] ))
                     pOS->pzFileFmt = zFileFmt1;
                else pOS->pzFileFmt = zFileFmt2;
            }
            goto advance;
        }

        /*
         *  IF we still have data (it will not be a suffix),
         *     AND we still need an end macro marker,
         *  THEN we must be pointing at it now.
         */
        if ((*pzLoad != NUL) && (zEndMac[0] == NUL)) {
            char* pz = zEndMac;
            do  {
                endMacLen++;
                *(pz++) = *(pzLoad++);
                if ((int)(pz - zEndMac) >= STRSIZE( zEndMac )) {
                    fprintf( stderr, zTplErr, pzTemplFileName, templLineNo,
                             "end macro mark too long" );
                    LOAD_ABORT;
                }
            } while (ispunct( *pzLoad ));
            *pz = NUL;

            /*
             *  Check for a start macro contained within the end macro.
             *  Someday, we may wish to start our template there.
             *  In any event, we will crop off the start macro and
             *  make sure there is still something left.
             */
            pz = strstr( zEndMac, zStartMac );
            if (pz != (char*)NULL) {
                *pz = NUL;
                if (zEndMac[0] == NUL) {
                    fprintf( stderr, zTplErr, pzTemplFileName, templLineNo,
                             "end macro mark starts with start mark" );
                    LOAD_ABORT;
                }
                pzLoad -= 1 + strlen( pz+1 );
            } else {
                while (isspace( *pzLoad )) pzLoad++;
            }
            return pzLoad;
        }

    advance:
        /*
         *  Skip over trailing space and see if we need a new line.
         */
        while (isspace(*pzLoad)) pzLoad++;
    } while (*pzLoad != NUL);
    return (char*)NULL;
}


    STATIC void
loadTemplText( tTemplFile*  pTF, size_t fsize )
{
    FILE* fp = fopen( pTF->zName, "r" FOPEN_TEXT_FLAG );
    char* pzData = pTF->pzText;

    if (fp == (FILE*)NULL) {
        tSCC zOpen[] = "open";
        fprintf( stderr, zCannot, pzProg, errno, zOpen,
                 pTF->zName, strerror( errno ));
        LOAD_ABORT;
    }

    haveAG       = AG_FALSE;
    haveTPL      = AG_FALSE;
    haveMod      = AG_FALSE;
    zStartMac[0] = NUL;
    startMacLen  = 0;
    endMacLen    = 0;
    zEndMac[0]   = NUL;
    templLineNo  = 1;

    /*
     *  Keep reading lines from the template until we have found
     *  the end macro marker.
     */
    for (;;templLineNo++) {
        char* pzLoad = fgets( pzData, fsize, fp );
        if (pzLoad == (char*)NULL) {
            fprintf( stderr, zTplErr, pTF->zName, templLineNo,
                     zNotTemplate );
            LOAD_ABORT;
        }
        fsize -= strlen( pzLoad );

        /*
         *  Ignore blank lines and comments (lines starting with '#')
         */
        {
            char*  pz = pzLoad;
            while (isspace(*pz)) pz++;
            if ((*pz == NUL) || (*pz == '#'))
                continue;

            /*
             *  Skip the leading white space
             */
            pzLoad = pz;
        }

        pzLoad = processDeclLine( pzLoad, pTF->zName );

        if (pzLoad != (char*)NULL) {
            long backCt = strlen( pzLoad );

            /*
             *  Either regurgitate the last new line or increment the count
             */
            if (backCt > 0) {
                fseek( fp, -1L * backCt, SEEK_CUR );
                fsize += backCt;
            } else {
                templLineNo++;
            }

            break;
        }
    }

    /*
     *  Make a global note of the file pointer and remember where
     *  we are in the template file.  We start right after the
     *  newline that terminated the last input.
     */
    do  {
        size_t  readCt = fread( (void*)pzData, 1, fsize, fp );

        if (readCt <= 0) {
            if (ferror( fp )) {
                tSCC zRead[] = "read";
                fprintf( stderr, zCannot, pzProg,
                         errno, zRead, pTF->zName, strerror( errno ));
                LOAD_ABORT;
            }
            break;
        }

        pzData += readCt;
        fsize  -= readCt;
    } while (fsize > 0);

    *pzData = NUL;
    fclose( fp );
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *  Starting with the current directory, search the directory
 *  list trying to find the base template file name.
 */
    tSuccess
findTemplateFile( const char* pzTempl, char* pzFullName )
{
    char**  ppzDir;
    int     ct;
    char*   pzRoot;
    char*   pzSfx;

    /*
     *  Expand leading environment variables.
     *  We will not mess with embedded ones.
     */
    if (*pzTempl == '$') do {
        char* pzDef = (char*)(pzTempl+1);
        char* pzEnd = strchr( pzDef, DIR_SEP_CHAR );

        /*
         *  IF the string contains a directory separator,
         *  THEN everything up to that is the define name.
         */
        if (pzEnd != (char*)NULL)
            *(pzEnd++) = NUL;

        pzDef = (char*)getDefine( pzDef );

        /*
         *  IF we cannot find a define name,
         *  THEN print a warning and continue as normal.
         */
        if (pzDef == (char*)NULL) {
            if (pzEnd != (char*)NULL)
                pzEnd[-1] = DIR_SEP_CHAR;

            fprintf( stderr, "NOTE: cannot expand %s\n", pzTempl );
            break;
        }

        /*
         *  Add stuff after the expanded name IFF there is stuff
         */
        if (pzEnd != (char*)NULL) {
            sprintf( pzFullName, "%s%c%s", pzDef, DIR_SEP_CHAR, pzEnd );
            /*
             *  FIXME:  this is a memory leak
             */
            AGDUPSTR( pzTempl, pzFullName );
            pzEnd[-1] = DIR_SEP_CHAR;
        } else {
            pzTempl = pzDef;
        }
    } while (AG_FALSE);

    /*
     *  Check for a complete file name
     */
    if (access( pzTempl, R_OK ) == 0) {
        strcpy( pzFullName, pzTempl );
        return SUCCESS;
    }

    /*
     *  Not a complete file name.  If there is not already
     *  a suffix for the file name, then append ".tpl".
     *  Check for immediate access once again.
     */
    pzRoot = strrchr( pzTempl, DIR_SEP_CHAR );
    pzSfx  = (pzRoot != (char*)NULL)
             ? strchr( ++pzRoot, '.' )
             : strchr( pzTempl, '.' );

    if (pzSfx == (char*)NULL) {
        sprintf( pzFullName, "%s.tpl", pzTempl );
        if (access( pzFullName, R_OK ) == 0)
            return SUCCESS;
    }

    /*
     *  Search each directory in our directory search list
     *  for the file.
     */
    if (HAVE_OPT( TEMPL_DIRS )) {
        ct     = STACKCT_OPT(  TEMPL_DIRS );
        ppzDir = STACKLST_OPT( TEMPL_DIRS ) + ct - 1;

        do  {
            tSCC zDirFmt[] = "%s%c%s";
            char*   pzDir  = *(ppzDir--);
            sprintf( pzFullName, zDirFmt, pzDir, DIR_SEP_CHAR, pzTempl );
            if (access( pzFullName, R_OK ) == 0)
                return SUCCESS;
            if (pzSfx == (char*)NULL) {
                strcat( pzFullName, ".tpl" );
                if (access( pzFullName, R_OK ) == 0)
                    return SUCCESS;
            }
        } while (--ct > 0);
    }

    /*
     *  Now we try to find this file using our PATH environment variable.
     */
    do  {
        tSCC   zPath[] = "PATH";
        char*  pzPath  = getenv( zPath );

        /*
         *  IF we cannot find a PATH variable, bail
         */
        if (pzPath == (char*)NULL)
            return FAILURE;

        sprintf( pzFullName, "%s.tpl", pzTempl );
        pzPath = pathfind( pzPath, pzFullName, "rs" );
        if (pzPath == (char*)NULL)
            return FAILURE;

        strcpy( pzFullName, pzPath );
    } while (AG_FALSE);

    return SUCCESS;
}


/*
 *  countMacros
 *
 *  Figure out how many macros there are in the template
 *  so that we can allocate the right number of pointers.
 */
    STATIC size_t
countMacros( char* pz )
{
    size_t  ct = 2;
    for (;;) {
        pz = strstr( pz, zStartMac );
        if (pz == (char*)NULL)
            break;
        ct += 2;
        pz += startMacLen;
    }
    return ct;
}



    tMacro*
parseTemplate( tMacro* pM, char** ppzText )
{
    char* pzScan = *ppzText;
#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
    tSCC zTDef[] = "%-10s (%d) line %d end=0x%X - tkns %d";
    static int level = 0;
    level++;
#endif

    for (;;) {
        char* pzMark = strstr( pzScan, zStartMac );

        /*
         *  IF there is any text, then make a text macro entry
         */
        if (pzMark != pzScan) {
            pM->pzText    = pzScan;
            pM->funcCode  = FTYP_TEXT;
            pM->lineNo    = templLineNo;
#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
            if (HAVE_OPT( SHOW_DEFS )) {
                int ct = level;
                do { fputs( "  ", stdout ); } while (--ct > 0);
                ct = pM->tknCt;
                printf( zTDef, apzFuncNames[ pM->funcCode ],
                        pM->funcCode, pM->lineNo, pM->pEnd, ct );
                fputc( '\n', stdout );
            }
#endif

            pM++;
        }

        /*
         *  IF no more macro marks are found,
         *  THEN we are done...
         */
        if (pzMark == (char*)NULL)
            break;

        *pzMark = NUL;

        /*
         *  Count the lines in the text block
         */
        for (;;) {
            char* pz = strchr( pzScan, '\n' );
            if (pz == (char*)NULL)
                break;
            templLineNo++;
            pzScan = pz+1;
        }

        /*
         *  Set our pointers to the start of the macro text
         */
        pzMark += startMacLen;
        while (isspace( *pzMark )) {
            if (*pzMark == '\n')
                templLineNo++;
            pzMark++;
        }

        pM->pzText   = pzMark;
        pM->funcCode = whichFunc( pzMark );
        pM->lineNo   = templLineNo;

        /*
         *  Find the end of the macro invocation
         */
        pzScan = strstr( pzMark, zEndMac );
        if (pzScan == (char*)NULL) {
            fprintf( stderr, zTplErr, pzTemplFileName, pM->lineNo,
                     "macro has no end" );
            LOAD_ABORT;
        }

        /*
         *  Count the lines in the macro text and advance the
         *  text pointer to after the marker.
         */
        {
            char*  pzMacEnd = pzScan;
            char*  pz = pzMark;

            for (;;pz++) {
                pz = strchr( pz, '\n' );
                if ((pz == (char*)NULL) || (pz > pzMacEnd))
                    break;
                templLineNo++;
            }

            /*
             *  Strip trailing white space from the macro
             */
            while ((pzMacEnd > pzMark) && isspace( pzMacEnd[-1] )) pzMacEnd--;
            *pzMacEnd = NUL;
        }

        pzScan += endMacLen;

        /*
         *  IF the called function returns a NULL next macro pointer,
         *  THEN some block has completed.  The returned scanning pointer
         *       will be non-NULL.
         */
        {
            tMacro* pNM = (*(papLoadProc[ pM->funcCode ]))( pM, &pzScan );
#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
            if (HAVE_OPT( SHOW_DEFS )) {
                int ct = level;
                do { fputs( "  ", stdout ); } while (--ct > 0);
                ct = pM->tknCt;
                printf( zTDef, apzFuncNames[ pM->funcCode ],
                        pM->funcCode, pM->lineNo, pM->pEnd, ct );

                if (ct > 5)
                    fputs( "\n\t", stdout );

                if (pM->ppTkns != (char**)NULL)
                  while (ct > 0)
                    printf( " `%s'", pM->ppTkns[ pM->tknCt - (ct--)] );
                fputc( '\n', stdout );
            }
#endif

            if (pNM == (tMacro*)NULL) {
                *ppzText = pzScan;
#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
                level--;
#endif
                return pM;
            }
            pM = pNM;
        }
    }

#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
    level--;
#endif

    /*
     *  We reached the end of the input string.
     *  Return a NULL scanning pointer and a pointer to the end.
     */
    *ppzText = (char*)NULL;
    return pM;
}



    tTemplFile*
loadTemplate( char* pzTemplName )
{
    size_t       fsize;
    char*        pzData;
    tTemplFile*  pTF;

    static char zBaseTemplFile[ MAXPATHLEN ];

    /*
     *  Find the template file somewhere
     */
    if (! SUCCESSFUL( findTemplateFile( pzTemplName, zBaseTemplFile ))) {
        tSCC zFindTemplFile[] = "find template file";
        fprintf( stderr, zCannot, pzProg, ENOENT,
                 zFindTemplFile, pzTemplName, strerror( ENOENT ));
        LOAD_ABORT;
    }

    /*
     *  Find out how big the template is
     */
    {
        struct stat  stBuf;
        if (stat( zBaseTemplFile, &stBuf ) != 0) {
            tSCC zStat[] = "stat";
            fprintf( stderr, zCannot, pzProg, errno,
                     zStat, zBaseTemplFile, strerror( errno ));
            LOAD_ABORT;
        }

        if (! S_ISREG( stBuf.st_mode )) {
            fprintf( stderr, "ERROR:  `%s' is not a regular file\n",
                     zBaseTemplFile );
            LOAD_ABORT;
        }

        if (outTime <= stBuf.st_mtime)
            outTime = stBuf.st_mtime + 1;
        fsize = stBuf.st_size;
    }

    /*
     *  Allocate space for the template text, template name
     *  and all the associated text.  Copy the name into that space.
     */
    {
        size_t nameLen = strlen( zBaseTemplFile );

        pTF = (tTemplFile*)AGALOC( fsize + sizeof( *pTF ) + 2 + nameLen );
        if (pTF == (tTemplFile*)NULL) {
            tSCC zWhat[] = "Template File Description";
            fprintf( stderr, zAllocErr, pzProg,
                     fsize + 1, zWhat );
            LOAD_ABORT;
        }

        strcpy( pTF->zName, zBaseTemplFile );
        pzData = pTF->pzText = pTF->zName + nameLen + 1;
    }

    /*
     *  Set the global file name pointer
     */
    pzTemplFileName = pTF->zName;

    /*
     *  Load the text from the file, and
     *  figure out about how many macros there will be
     *  and allocate pointers for them.
     */
    loadTemplText( pTF, fsize );
    pTF->macCt = countMacros( pTF->pzText )+1;
    fsize = sizeof( tMacro ) * pTF->macCt;
    pTF->pMacros = (tMacro*)AGALOC( fsize );
    if (pTF->pMacros == (tMacro*)NULL) {
        tSCC zWhat[] = "Template Macros";
        fprintf( stderr, zAllocErr, pzProg,
                 fsize + 1, zWhat );
        LOAD_ABORT;
    }
    memset( (void*)pTF->pMacros, 0, fsize );

    {
        tMacro* pMacEnd = parseTemplate( pTF->pMacros, &pzData );
        pTF->macCt = pMacEnd - pTF->pMacros;
        /*
         *  We cannot reallocate a smaller array because
         *  the entries are all linked together and
         *  realloc-ing it may cause it to move.
         */
#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
    if (HAVE_OPT( SHOW_DEFS ))
        printf( "loaded %d macros from %s\n\n\n", pTF->macCt, pTF->zName );
#endif
    }

    /*
     *  Make sure all of the input string was scanned.
     */
    if (pzData != (char*)NULL)  {
        fprintf( stderr, zTplErr, pTF->zName, templLineNo,
                 "parse ended unexpectedly" );
        LOAD_ABORT;
    }

    return pTF;
}


    void
unloadTemplate( tTemplFile* pTF )
{
    tMacro* pMac = pTF->pMacros;
    tMacro* pEnd = pTF->pMacros + pTF->macCt;

    while (pMac < pEnd) {
        if (pMac->ppTkns != (char**)NULL)
            AGFREE( (void*)pMac->ppTkns );
        pMac++;
    }
    AGFREE( (void*)pTF->pMacros );
    AGFREE( (void*)pTF );
}


    void
closeOutput( ag_bool purge )
{
    removeWriteAccess( fileno( pCurFp->pFile ));
    fclose( pCurFp->pFile );

    if (purge)
        unlink( pCurFp->pzName );

    else {
        struct utimbuf tbuf;

        tbuf.actime  = time( (time_t*)NULL );
        tbuf.modtime = outTime;

        utime( pCurFp->pzName, &tbuf );
    }

    /*
     *  Do not deallocate stuff for the root file.
     *  It is not allocated!!
     */
    if (pCurFp->pPrev != (tFpStack*)NULL) {
        tFpStack* p = pCurFp;
        pCurFp = p->pPrev;
        AGFREE( (void*)p->pzName );
        AGFREE( (void*)p );
    }
}


/*
 *  Generate all the text within a block.  The caller must
 *  know the exact bounds of the block.  "pEnd" actually
 *  must point to the first entry that is *not* to be emitted.
 */
    void
generateBlock( tMacro*      pMac,
               tMacro*      pEnd,
               tDefEntry*   pList )
{
    while ((pMac != (tMacro*)NULL) && (pMac < pEnd)) {

#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
        if (HAVE_OPT( SHOW_DEFS )) {
            tSCC zFmt[] = "%-10s (%2d) at 0x%08X  tknct = %d\n";
            printf( zFmt, apzFuncNames[ pMac->funcCode ], pMac->funcCode,
                    pMac, pMac->tknCt );
        }
#endif

        pMac = (*(apHdlrProc[ pMac->funcCode ]))( pMac, pList );
    }
}


    void
processTemplate( tTemplFile* pTF )
{
    tFpStack fpRoot = { (tFpStack*)NULL, (FILE*)NULL, (char*)NULL };

    /*
     *  IF the template file does not specify any output suffixes,
     *  THEN we will generate to standard out with the suffix set to zNone.
     */
    if (pOutSpecList == (tOutSpec*)NULL) {
        tSCC zNone[]   = "* NONE *";
        tSCC zStdout[] = "stdout";
        int  jumpCd  = setjmp( fileAbort );
        if (jumpCd == SUCCESS) {
            fpRoot.pFile  = stdout;
            fpRoot.pzName = (char*)zStdout;
            pzCurSfx      = zNone;
            forLoopDepth  = 0;
            pCurFp        = &fpRoot;
            generateBlock( pTF->pMacros, pTF->pMacros + pTF->macCt,
                           (tDefEntry*)rootEntry.pzValue );
        }
        fclose( stdout );
        if (FAILED(jumpCd))
            exit( EXIT_FAILURE );
        return;
    }

    for (;;) {
        char       zOut[ MAXPATHLEN ];
        tOutSpec*  pOS    = pOutSpecList;
        int        jumpCd = setjmp( fileAbort );

        /*
         *  HOW was that we got here?
         */
        switch (jumpCd) {
        case SUCCESS:
            /*
             *  Set the output file name buffer.
             *  It may get switched inside openOutFile.
             */
            fpRoot.pzName = zOut;
            openOutFile( pOS, &fpRoot );

            pzCurSfx      = pOS->zSuffix;
            forLoopDepth  = 0;
            generateBlock( pTF->pMacros, pTF->pMacros + pTF->macCt,
                           (tDefEntry*)rootEntry.pzValue );

            do  {
                closeOutput( AG_FALSE );
            } while (pCurFp->pPrev != (tFpStack*)NULL);
            break;

        case PROBLEM:
        case FAILURE:
            /*
             *  We got here by a long jump.  Close/purge the open files.
             */
            do  {
                closeOutput( AG_TRUE );
            } while (pCurFp->pPrev != (tFpStack*)NULL);

            /*
             *  On failure, we quit the program, too.
             */
            if (FAILED( jumpCd ))
                exit( EXIT_FAILURE );
        }

        /*
         *  Advance to the next output specification
         *  and free the old output spec.
         */
        pOutSpecList = pOS->pNext;
        AGFREE( (void*)pOS );

        if (pOutSpecList == (tOutSpec*)NULL)
            break;
    }
}



    STATIC void
openOutFile( tOutSpec* pOutSpec, tFpStack* pStk )
{
    char*  pzDefFile;

    /*
     *  Figure out what to use as the base name of the output file.
     *  If an argument is not provided, we use the base name of
     *  the definitions file.
     */
    if ( ISSEL_OPT( BASE_NAME ))
         pzDefFile = OPT_ARG( BASE_NAME );
    else pzDefFile = pzDefineFileName;

    {
        char*  p = strrchr( pzDefFile, DIR_SEP_CHAR );
        if (p != (char*)NULL)
            pzDefFile = p + 1;

        p = strchr( pzDefFile, '.' );
        if (p != (char*)NULL)
            *p = NUL;
        /*
         *  Now formulate the output file name in the buffer
         *  porvided as the input argument.
         */
        sprintf( pStk->pzName, pOutSpec->pzFileFmt, pzDefFile,
                 pOutSpec->zSuffix );
        if (p != (char*)NULL)
            *p = '.';
    }

    pCurFp = pStk;

    /*
     *  IF we are to skip the current suffix,
     *  we will redirect the output to /dev/null and
     *  perform all the work.  There may be side effects.
     */
    if (HAVE_OPT( SKIP_SUFFIX )) {
        int     ct  = STACKCT_OPT(  SKIP_SUFFIX );
        char**  ppz = STACKLST_OPT( SKIP_SUFFIX );

        while (--ct >= 0) {
            if (strcmp( pOutSpec->zSuffix, *ppz++ ) == 0) {
                /*
                 *  Make the output a no-op, but perform the operations.
                 */
                tSCC zDevNull[] = "/dev/null";
                pStk->pzName = (char*)zDevNull;
                pStk->pFile  = fopen( zDevNull, "w" FOPEN_TEXT_FLAG );
                if (pStk->pFile != (FILE*)NULL)
                    return;

                goto openError;
            }
        }
    }

    unlink( pStk->pzName );
    pStk->pFile = fopen( pStk->pzName, "w" FOPEN_TEXT_FLAG );

    if (pStk->pFile == (FILE*)NULL) {
    openError:
        fprintf( stderr, zCannot, pzProg, errno,
                 "create", pStk->pzName, strerror( errno ));
        LOAD_ABORT;
    }
}
/* end of agTempl.c */
