/*
 *  agGetDef.c
 *  $Id: agGetDef.c,v 2.16 1999/09/27 19:22:04 bkorb Exp $
 *  This module loads the definitions, calls yyparse to decipher them,
 *  and then makes a fixup pass to point all children definitions to
 *  their parent definition (except the fixed "rootEntry" entry).
 */

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

#include "autogen.h"


    STATIC int
compareIndex( const void* p1, const void* p2 )
{
    tDefEntry* pE1 = *((tDefEntry**)p1);
    tDefEntry* pE2 = *((tDefEntry**)p2);
    int  res = pE1->index - pE2->index;
    if (res == 0) {
        fprintf( stderr, "ERROR: two %s entries have index %ld\n",
                 pE1->pzName, pE1->index );
        LOAD_ABORT;
    }
    return res;
}


/*
 *  fixTwins
 *
 *  If the twins are unnumbered, then we assign numbers.
 *  Once they are all numbered, then we sort them.
 *  Once they are all sorted, then we link them together
 *  in that order.
 */
    STATIC void
fixTwins( tDefEntry** ppNode )
{
    long curMax = -1;
    int  twinCt = 0;
    void** p;
    int  idx;
    tDefEntry* pTw  = *ppNode;
    tDefEntry* pNxt = pTw->pNext;
    void*  ap[ 16 ];

    pTw->pNext = (tDefEntry*)NULL;

    /*
     *  Do the initial twin scan, assigning index numbers as needed
     */
    do  {
        if (pTw->index == NO_INDEX)
            pTw->index = ++curMax;

        else if (pTw->index > curMax)
            curMax = pTw->index;

        twinCt++;
        pTw = pTw->pTwin;
    } while (pTw != (tDefEntry*)NULL);

    /*
     *  Try to avoid allocating a pointer array,
     *  but do so if we have to to avoid arbitrary limits.
     */
    if (twinCt <= 16) {
        p = ap;
    } else {
        p = AGALOC( twinCt * sizeof( void* ));

        if (p == (void**)NULL) {
            fprintf( stderr, zAllocErr, pzProg,
                     twinCt * sizeof( void* ), "twin sort pointer array" );
            LOAD_ABORT;
        }
    }

    /*
     *  Sort all the twins by their index number.
     *  Reinsert the "next" pointer into the new first entry
     */
    for (pTw = *ppNode, idx = 0;
         pTw != (tDefEntry*)NULL;
         idx++, pTw = pTw->pTwin) {
        p[idx] = (void*)pTw;
    }

    qsort( (void*)p, twinCt, sizeof( void* ), compareIndex );
    ((tDefEntry*)(p[0]))->pNext = pNxt;

    /*
     *  Chain them all together in their sorted order,
     *  NULL terminating the list
     */
    for (idx = 0; idx < twinCt; idx++) {
        *ppNode = (tDefEntry*)p[idx];
        ppNode = &((tDefEntry*)(p[idx]))->pTwin;
    }
    *ppNode = (tDefEntry*)NULL;

    /*
     *  If we allocated the pointer array, dump it now
     */
    if (p != ap)
        AGFREE( (void*)p );
}


/*
 *  massageDefTree
 *
 *  The agReduce.c functions do not do everything.
 *  It stuffs all the definitins into the definition tree,
 *  but it is easier to come back at the end and link
 *  together the "twins" (definitions with the same name,
 *  but different indexes).  This routine does that.
 *  It is recursive, handling one level at a time.
 */
    STATIC void
massageDefTree( tDefEntry** ppNode, tDefEntry* pEldestUncle )
{
    static int lvl = 0;
    char zFmt[ 64 ];
    tDefEntry*  pNode;
    tDefEntry*  pEldestSib = *ppNode;

#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
    if (HAVE_OPT( SHOW_DEFS ))
        sprintf( zFmt, "%%%dd %%-%ds = ", 3 + (lvl * 2), 20 - (lvl * 2) );
#endif

    do  {
        tDefEntry** ppNextSib  = (tDefEntry**)NULL;

        pNode = *ppNode;

        /*
         *  IF this node has a twin (definition with same name)
         *  THEN ...
         */
        if (pNode->pTwin != (tDefEntry*)NULL) {
            fixTwins( ppNode );
            pNode = *ppNode;  /* new first entry */

            /*
             *  Now go through the twin list and put in the reverse
             *  pointers.  (We can go through _FOR loops in either
             *  direction.)
             */
            {
                tDefEntry* pNextTwin = pNode->pTwin;
                tDefEntry* pPrevTwin = pNode;
                do  {
                    pNextTwin->pPrevTwin = pPrevTwin;
                    pPrevTwin = pNextTwin;
                    pNextTwin = pNextTwin->pTwin;
                } while (pNextTwin != (tDefEntry*)NULL);

                /*
                 *  The eldest twin has a baby twin pointer
                 */
                pNode->pEndTwin = pPrevTwin;
            }

        } else if (pNode->index == NO_INDEX) {
            /*
             *  No twins and no index means an index of zero
             */
            pNode->index = 0;
        }

        ppNode = &(pNode->pNext);

    skipTwinFix:
#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
        if (HAVE_OPT(SHOW_DEFS))
            printf( zFmt, pNode->index, pNode->pzName );
#endif
        pNode->pDad = pEldestUncle;

        if (pNode->macType == MACTYP_BLOCK) {
#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
            if (HAVE_OPT(SHOW_DEFS))
                fputs( "{...}\n", stdout );
#endif
            /*
             *  Do this same function on all the children of this
             *  block definition.
             */
            lvl++;
            massageDefTree( (tDefEntry**)&(pNode->pzValue), pEldestSib );
            lvl--;
        }

#if defined( DEBUG ) && defined( VALUE_OPT_SHOW_DEFS )
        /*
         *  IF we are displaying definitions,
         *  THEN show what we can on a single line for this text definition
         */
        else if (HAVE_OPT(SHOW_DEFS)) {
            char* pz = pNode->pzValue;
            int   ct = 32;
            while (isspace( *pz )) pz++;
            for (;;) {
                char ch = *(pz++);
                if (ch == NUL) {
                    fputc( '\n', stdout );
                    break;
                }
                fputc( ch, stdout );
                if (ch == '\n')
                    break;
                if (--ct == 0) {
                    fputc( '\n', stdout );
                    break;
                }
            }
        }
#endif

        /*
         *  IF we have to deal with a twin,
         *  THEN remember who the next sibling is and handle the twin
         */
        if (pNode->pTwin != (tDefEntry*)NULL) {
            /*
             *  IF we do not already have a next sibling pointer,
             *  THEN save it now.  We will clear this pointer when
             *       we run out of twins.
             */
            if (ppNextSib == (tDefEntry**)NULL)
                ppNextSib = ppNode;

            pNode = pNode->pTwin;
            goto skipTwinFix;
        }

        /*
         *  IF we stashed a next sibling pointer in order to handle
         *  twins, THEN resume the scan from that point
         */
        if (ppNextSib != (tDefEntry**)NULL)
            ppNode = ppNextSib;

    } while (*ppNode != (tDefEntry*)NULL);
}


/*
 *  readDefines
 *
 *  Suck in the entire definitions file and parse it.
 */
    void
readDefines( const char* pzFname )
{
    char*    pzData;
    size_t   dataSize;
    size_t   sizeLeft;
    ag_bool  useStdin;
    FILE*    fp;

    /*
     *  Start our definitions tree with a magical root
     */
    memset( (void*)&rootEntry, 0, sizeof( rootEntry ));
    rootEntry.pzName  = "@@ROOT@@";
    rootEntry.macType = MACTYP_BLOCK;

    /*
     *  Check for stdin as the input file.  We use the current time
     *  as the modification time for stdin.  We also note it so we
     *  do not try to open it and we try to allocate more memory if
     *  the stdin input exceeds our initial allocation of 16K.
     */
    if (strcmp( pzFname, "-" ) == 0) {
        pzFname  = "stdin";
        outTime  = time( (time_t*)NULL );
        sizeLeft = dataSize = 0x4000 - (4+sizeof( *pBaseCtx ));
        useStdin = AG_TRUE;
    }

    /*
     *  This, then, must be a regular file.  Make sure of that and
     *  find out how big it was and when it was last modified.
     */
    else {
        struct stat stbf;
        if (stat( pzFname, &stbf ) != 0) {
            fprintf( stderr, "%s ERROR %d (%s) stat-ing %s\n",
                     pzProg, errno, strerror( errno ),
                     pzFname );
            LOAD_ABORT;
        }
        if (! S_ISREG( stbf.st_mode )) {
            fprintf( stderr, "%s ERROR:  %s is not a regular file\n",
                     pzProg, pzFname );
            LOAD_ABORT;
        }

        /*
         *  Our output file mod time will start as one second after
         *  the mod time on this file.  If any of the template files
         *  are more recent, then it will be adjusted.
         */
        sizeLeft = dataSize = stbf.st_size;
        outTime  = stbf.st_mtime + 1;
        useStdin = AG_FALSE;
    }

    /*
     *  Allocate the space we need for our definitions.
     */
    pBaseCtx = (tScanCtx*)AGALOC( dataSize+4+sizeof( *pBaseCtx ));
    if (pBaseCtx == (tScanCtx*)NULL) {
        fprintf( stderr, zAllocErr, pzProg,
                 dataSize+4+sizeof( *pBaseCtx ), "file buffer" );
        LOAD_ABORT;
    }

    /*
     *  Our base context will have its currency pointer set to this
     *  input.  It is also a scanning pointer, but since this buffer
     *  is never deallocated, we do not have to remember the initial
     *  value.  (It may get reallocated here in this routine, tho...)
     */
    pzData =
        pBaseCtx->pzScan =
        pBaseCtx->pzData = (char*)(pBaseCtx+1);
    pBaseCtx->pCtx = (tScanCtx*)NULL;

    /*
     *  Set the input file pointer, as needed
     */
    fp = useStdin ? stdin : fopen( pzFname, "r" FOPEN_TEXT_FLAG );
    if (fp == (FILE*)NULL) {
        fprintf( stderr, "%s ERROR %d (%s):  cannot open %s\n",
                 pzProg, errno, strerror( errno ),
                 pzFname );
        LOAD_ABORT;
    }

    /*
     *  Read until done...
     */
    for (;;) {
        size_t rdct = fread( (void*)pzData, 1, sizeLeft, fp );

        /*
         *  IF we are done,
         */
        if (rdct == 0) {
            /*
             *  IF it is because we are at EOF, then break out
             *  ELSE abend.
             */
            if (feof( fp ))
                break;

            fprintf( stderr, "%s ERROR %d (%s):  cannot read %d bytes "
                     "from %s\n", pzProg, errno,
                     strerror( errno ), dataSize, pzFname );
            LOAD_ABORT;
        }

        /*
         *  Advance input pointer, decrease remaining count
         */
        pzData   += rdct;
        sizeLeft -= rdct;

        /*
         *  See if there is any space left
         */
        if (sizeLeft == 0) {
            tScanCtx* p;

            /*
             *  IF it is a regular file, then we are done
             */
            if (! useStdin)
                break;

            /*
             *  We have more data and we are out of space.
             *  Try to reallocate our input buffer.
             */
            dataSize += (sizeLeft = 0x1000);
            p = (tScanCtx*)AGREALOC( (void*)pBaseCtx,
                                     dataSize+4+sizeof( *pBaseCtx ));
            if (p == (tScanCtx*)NULL) {
                fprintf( stderr, zAllocErr, pzProg,
                         dataSize+4+sizeof( *pBaseCtx ),
                         "expanded file buffer" );
                LOAD_ABORT;
            }

            /*
             *  The buffer may have moved.  Set the data pointer at an
             *  offset within the new buffer and make sure our base pointer
             *  has been corrected as well.
             */
            if (p != pBaseCtx) {
                p->pzScan =
                    p->pzData = (char*)(p+1);
                pzData = p->pzData + (pzData - pBaseCtx->pzData);
                pBaseCtx = p;
            }
        }
    }

    *pzData = NUL;
    AGDUPSTR( pBaseCtx->pzFileName, pzFname );
    pzDefineFileName = (char*)pzFname;

    /*
     *  Close the input file, parse the data
     *  and alphabetically sort the definition tree contents.
     */
    if (fp != stdin)
        fclose( fp );
    pCurCtx = pBaseCtx;
    {
        extern int yyparse( void );
        (void)yyparse();
    }
    {
        tDefEntry* pRoot = &rootEntry;
        massageDefTree( &pRoot, (tDefEntry*)NULL );
    }
}


    STATIC tDefEntry*
findEntryByIndex( tDefEntry* pE, char* pzScan )
{
    int  idx;

    /*
     *  '[]' means the first entry of whatever index number
     */
    if (*pzScan == ']')
        return pE;

    /*
     *  '[$]' means the last entry of whatever index number
     */
    if (*pzScan == '$') {
        while (isspace( *++pzScan )) ;
        if (*pzScan != ']')
            return (tDefEntry*)NULL;

        if (pE->pEndTwin != (tDefEntry*)NULL)
            return pE->pEndTwin;

        return pE;
    }

    /*
     *  '[nn]' means the specified index number
     */
    if (isdigit( *pzScan )) {
        char* pz;
        idx = strtol( pzScan, &pz, 0 );

        /*
         *  Skip over any trailing space and make sure we have a closer
         */
        while (isspace( *pz )) pz++;
        if (*pz != ']')
            return (tDefEntry*)NULL;
    }

    else {
        /*
         *  '[XX]' means get the index from our definitions
         */
        char* pzDef = pzScan;
        const char* pzVal;

        if (! isalpha( *pzScan ))
            return (tDefEntry*)NULL;

        while (isalnum( *pzScan ) || (*pzScan == '_')) pzScan++;

        /*
         *  Temporarily remove the character under *pzScan and
         *  find the corresponding defined value.
         */
        {
            char  svch = *pzScan;
            *pzScan = NUL;
            pzVal    = getDefine( pzDef );
            *pzScan = svch;
        }

        /*
         *  Skip over any trailing space and make sure we have a closer
         */
        while (isspace( *pzScan )) pzScan++;
        if (*pzScan != ']')
            return (tDefEntry*)NULL;

        /*
         *  make sure we found a defined value
         */
        if ((pzVal == (char*)NULL) || (*pzVal == NUL))
            return (tDefEntry*)NULL;

        idx = strtol( pzVal, &pzDef, 0 );

        /*
         *  Make sure we got a legal number
         */
        if (*pzDef != NUL)
            return (tDefEntry*)NULL;
    }

    /*
     *  Search for the entry with the specified index.
     */
    do  {
        if (pE->index > idx)
            return (tDefEntry*)NULL;
        if (pE->index == idx)
            break;
        pE = pE->pTwin;
    } while (pE != (tDefEntry*)NULL);

    return pE;
}

/*
 *  findDefEntry
 *
 *  Find the definition entry for the name passed in.
 *  It is okay to find block entries IFF they are found on the
 *  current level.  Once you start traversing up the tree,
 *  the macro must be a text macro.  Return an indicator saying if
 *  the element has been indexed (so the caller will not try
 *  to traverse the list of twins).
 */
    tDefEntry*
findDefEntry( char* pzName, tDefEntry* pDefs, ag_bool* pIsIndexed )
{
    char*      pcBrace;
    char       breakCh;
    tDefEntry* pE;
    char       zDefName[ MAXPATHLEN ];

    if (pIsIndexed != (ag_bool*)NULL)
      *pIsIndexed = AG_FALSE;

 findDefEntryRecurse:
    pcBrace  = pzName + strcspn( pzName, "[." );
    breakCh  = *pcBrace;
    *pcBrace = NUL;

    if (pIsIndexed != (ag_bool*)NULL)
      *pIsIndexed |= (breakCh == '[');

    strtransform( zDefName, pzName );

    for (;;) {
        pE = pDefs;

        do  {
            /*
             *  IF the name matches
             *  THEN break out of the double loop
             */
            if (strcmp( pE->pzName, zDefName ) == 0)
                goto findTwin;

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

        /*
         *  Advance and check for finish (not found in root defs)
         */
        pDefs = pDefs->pDad ;
        if (pDefs == (tDefEntry*)NULL) {
            *pcBrace = breakCh;
            return (tDefEntry*)NULL;
        }
    }

findTwin:
    *pcBrace = breakCh;
    switch (breakCh) {
    case NUL:
        return pE;

    case '[':
        /*
         *  We have to find a specific entry in a list.
         */
        while (isspace( *++pcBrace )) ;

        pE = findEntryByIndex( pE, pcBrace );
        if (pE == (tDefEntry*)NULL)
            return pE;

        /*
         *  We must find the closing brace, or there is an error
         */
        pcBrace = strchr( pcBrace, ']' );
        if (pcBrace == (char*)NULL)
            return (tDefEntry*)NULL;

        /*
         *  IF we are at the end of the definition,
         *  THEN return what we found
         */
        if (*++pcBrace != '.')
            return pE;

        /* FALLTHROUGH */

    case '.':
        /*
         *  It is a segmented value name.  Set the name pointer
         *  to the next segment and search starting from the newly
         *  available set of definitions.
         */
        pzName = pcBrace + 1;
        break;
    }

    /*
     *  We cannot find a member of a non-block type macro definition.
     */
    if (pE->macType != MACTYP_BLOCK)
        return (tDefEntry*)NULL;

    pDefs = (tDefEntry*)pE->pzValue;
    goto findDefEntryRecurse;
}
/* end of agGetDef.c */
