
/*
 *  $Id: agFunFor.c,v 2.8 1999/08/03 23:11:53 bkorb Exp $
 *
 *  This module implements the _FOR text function.
 */

/*
 *  AutoGen copyright 1992-1999 Bruce Korb
 *
 *  AutoGen is free software.
 *  You may redistribute it and/or modify it under the terms of the
 *  GNU General Public License, as published by the Free Software
 *  Foundation; either version 2, or (at your option) any later version.
 *
 *  AutoGen is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with AutoGen.  See the file "COPYING".  If not,
 *  write to:  The Free Software Foundation, Inc.,
 *             59 Temple Place - Suite 330,
 *             Boston,  MA  02111-1307, USA.
 */

#include "autogen.h"
#include "proto.h"

#define ENTRY_END  INT_MAX

tSCC zNoEnd[] = "%s ERROR:  _FOR loop `%s' does not end\n";

STATIC tMacro*  current_for;

/*
 *  If one of the loop controls is parenthesized, then the contained
 *  tokens are eval-ed.  The first and last expression tokens may
 *  or may not be attached to the opening and closing parenthesis.
 */
    STATIC int
evalParenExpr( int*       pVal,
               int        tknCt,
               char**     tknList,
               tDefEntry* pCurDef )
{
    int      soloParen = 0;
    int      tknIdx    = 0;
    char*    pcClose   = (char*)NULL;
    tMacro   mcro;

    if ((*tknList)[1] != NUL)
         (*tknList)++;
    else {
        tknCt--;
        tknList++;
        soloParen = 1;
    }

    /*
     *  Search until we find a token that ends with a ')'.
     *  If it is also the first character of the token,
     *  then it will not be included in the evaluation,
     *  but must, nonetheless, be included in the return count.
     */
    for (;;) {
        char*  pz = tknList[ tknIdx ];
        pz += strlen( pz )-1;

        if (*pz == ')') {
            /*
             *  IF the closing paren follows data,
             *  THEN we must include it in our list
             */
            if (pz != tknList[ tknIdx ]) {
                *pz = NUL;
                pcClose = pz;
                tknIdx++;
            } else
                soloParen++;

            break;
        }

        tknIdx++;
        if (tknIdx >= tknCt)
            break;
    }

    mcro.tknCt  = tknIdx;
    mcro.ppTkns = tknList;

    /*
     *  Evaluate the expression.  Convert any non-numeric
     *  result into a number (zero or the result of a strtoul() call)
     */
    switch ( eval( &mcro, pCurDef )) {
    default:
    case VT_NONE:
        *pVal = 0;
        break;

    case VT_VALUE:
#if SIZEOF_CHARP > SIZEOF_INT
        *pVal = (long)mcro.evalRes;
#else /* SIZEOF_CHARP <= SIZEOF_INT */      
        *pVal = (int)mcro.evalRes;
#endif
        break;

    case VT_STRING:
        *pVal = strtoul( mcro.evalRes, (char**)NULL, 0 );
        break;

    case VT_ALLOC_STR:
        *pVal = strtoul( mcro.evalRes, (char**)NULL, 0 );
        AGFREE( (void*)*tknList );
        break;

    case VT_STACK:
        evalError( &mcro );
    }

    if (! soloParen)
        (*tknList)--;
    if (pcClose != (char*)NULL)
        *pcClose = ')';

    return tknIdx + soloParen;
}


    STATIC void
evalForArgs( int*       pFrom,
             int*       pTo,
             int*       pBy,
             char**     ppzSep,
             tMacro*    pM,
             tDefEntry* pCurDef )
{
    tSCC zFrom[] = "FROM";
    tSCC zTo[]   = "TO";
    tSCC zBy[]   = "BY";
    tSCC zSep[]  = "SEP";

    int     tknCt   = pM->tknCt  - 1;
    char**  tknList = pM->ppTkns + 1;

    do  {
        char*  pzKey = *(tknList++);
        char*  pzVal = *(tknList++);

        tknCt -= 2;
        if (tknCt < 0)
            break;

        upcase( pzKey, CC_ALL_UP );

        if (strcmp( pzKey, zFrom ) == 0) {
            if (*pzVal != '(')
                *pFrom = strtol( pzVal, (char**)NULL, 0 );
            else {
                int   ct = evalParenExpr( pFrom, tknCt+1, tknList-1, pCurDef );
                tknCt   -= --ct;
                tknList += ct;
            }

        } else if (strcmp( pzKey, zTo ) == 0) {
            if (*pzVal != '(')
                *pTo = strtol( pzVal, (char**)NULL, 0 );
            else {
                int   ct = evalParenExpr( pTo, tknCt+1, tknList-1, pCurDef );
                tknCt   -= --ct;
                tknList += ct;
            }


        } else if (strcmp( pzKey, zBy ) == 0) {
            if (*pzVal != '(')
                *pBy = strtol( pzVal, (char**)NULL, 0 );
            else {
                int   ct = evalParenExpr( pBy, tknCt+1, tknList-1, pCurDef );
                tknCt   -= --ct;
                tknList += ct;
            }


        } else if (strcmp( pzKey, zSep ) == 0) {
            AGDUPSTR( *ppzSep, pzVal );

        } else
            break;

    } while (tknCt >= 0);
}


    STATIC void
genEachBlock( tMacro*    pForMac,
              tDefEntry* pDef,
              tDefEntry* pCurDef,
              char*      pzSep )
{
    char*  pzSaveStart  = pzCurStart;
    off_t  saveStartOff = curStartOff;

    /*
     *  Save our current offset and set a global to the start of the
     *  current fragment.  This makes error messages more interesting.
     */
    firstForCycle = AG_TRUE;
    lastForCycle  = AG_FALSE;

    for (;;) {
        tDefEntry textDef;
        tDefEntry* pEnt;

        /*
         *  IF this loops over a text macro,
         *  THEN create a definition that will be found *before*
         *       the repeated (twinned) copy.  That way, when it
         *       is found as a macro invocation, the current value
         *       will be extracted, instead of the value list.
         */
        if (pDef->macType == MACTYP_TEXT) {
            textDef = *pDef;
            textDef.pNext    =
            textDef.pTwin    =
            textDef.pEndTwin = (tDefEntry*)NULL;
            textDef.pDad     = pCurDef;
            pEnt = &textDef;
        } else {
            pEnt = (tDefEntry*)(void*)pDef->pzValue;
        }

        /*
         *  Set the global current index
         */
        curIndex = pDef->index;

        /*
         *  Advance to the next twin
         */
        pDef = pDef->pTwin;
        if (pDef == (tDefEntry*)NULL)
            lastForCycle = AG_TRUE;

        generateBlock( pForMac+1, pForMac->pEnd, pEnt );

        if (pDef == (tDefEntry*)NULL)
            break;
        firstForCycle = AG_FALSE;

        /*
         *  Emit the iteration separation
         */
        fputs( pzSep, pCurFp->pFile );
    }

    curStartOff = saveStartOff;
    pzCurStart  = pzSaveStart;
}


    STATIC ag_bool
nextDefinition( ag_bool invert, tDefEntry** ppList )
{
    ag_bool     haveMatch = AG_FALSE;
    tDefEntry*  pList     = *ppList;

    while (pList != (tDefEntry*)NULL) {
        /*
         *  Loop until we find or pass the current index value
         *
         *  IF we found an entry for the current index,
         *  THEN break out and use it
         */
        if (pList->index == curIndex) {
            haveMatch = AG_TRUE;
            break;
        }

        /*
         *  IF the next definition is beyond our current index,
         *       (that is, the current index is inside of a gap),
         *  THEN we have no current definition and will use
         *       only the set passed in.
         */
        if ((invert)
            ? (pList->index < curIndex)
            : (pList->index > curIndex))

            break;

        /*
         *  The current index (curIndex) is past the current value
         *  (pB->index), so advance to the next entry and test again.
         */
        pList = (invert) ? pList->pPrevTwin : pList->pTwin;
    }

    /*
     *  Save our restart point and return the find indication
     */
    *ppList = pList;
    return haveMatch;
}


/*
 *  Generate the output text for a positive count ("by" value).
 *  There are two termination conditions used.  If a "to" value was
 *  supplied, the block is output until "curIndex" passes the "to"
 *  value.  If a "to" value was *NOT* supplied, then "curIndex" is
 *  stepped until the last indexVal is passed.
 */
    STATIC void
genCount( tMacro*      pForMac,
          tDefEntry*   pList,
          tDefEntry*   pBaseDef,
          char*        pzSep,
          int          from,
          int          to,
          int          by )
{
    ag_bool     invert   = (by < 0);
    tDefEntry   textDef;
    tDefEntry*  pPassDef;
    t_word      loopLimit;

    /*
     *  IF no "to" value was supplied,
     *  THEN set it to the last (largest/smallest) entry
     */
    if (to == ENTRY_END)
        to = (( (!invert) && pList->pEndTwin)
              ? pList->pEndTwin : pList )->index;

    /*
     *  Record the start point and set the scanning pointer
     */
    if (invert && pList->pEndTwin)
        pList = pList->pEndTwin;

    /*
     *  IF no "from" value was supplied,
     *  THEN set it to the first (smallest/largest) entry
     */
    if (from == ENTRY_END)
        from = pList->index;

    /*
     *  Make sure we have some work to do before we start.
     */
    if (invert) {
        if (from < to)
            return;
    } else {
        if (from > to)
            return;
    }

    firstForCycle = AG_TRUE;
    lastForCycle  = AG_FALSE;
    loopLimit     = OPT_VALUE_LOOP_LIMIT;

    /*
     *  FROM `from' THROUGH `to' BY `by',
     *  DO...
     */
    for ( curIndex = from; ; )  {

        int     nextIdx   = curIndex + by;
        ag_bool gotNewDef = nextDefinition( invert, &pList );

        if (--loopLimit < 0) {
            fprintf( stderr, zTplErr, pzTemplFileName, pForMac->lineNo,
                     "Too many _FOR iterations" );
            fprintf( stderr, "\texiting _FOR %s from %d to %d "
                     "by %d:\n\tmore than %d iterations\n", pForMac->ppTkns[0],
                     from, to, by, OPT_VALUE_LOOP_LIMIT );
            break;
        }

        /*
         *  IF we have a non-base definition, ...
         */
        if (! gotNewDef)
            pPassDef = pBaseDef;

        /*
         *  ELSE IF this macro is a text type
         *  THEN create an un-twinned version of it to be found first
         */
        else if (pList->macType == MACTYP_TEXT) {
            textDef = *pList;
            textDef.pNext = textDef.pTwin = (tDefEntry*)NULL;
            textDef.pDad  = pBaseDef;
            pPassDef = &textDef;
        }

        /*
         *  ELSE the current definitions are based on the block
         *       macro's values
         */
        else
            pPassDef = (tDefEntry*)(void*)pList->pzValue;

        lastForCycle = (invert) ? (nextIdx < to) : (nextIdx > to);
        generateBlock( pForMac+1, pForMac->pEnd, pPassDef );

        if (lastForCycle)
            break;

        fputs( pzSep, pCurFp->pFile );
        firstForCycle = AG_FALSE;
        curIndex      = nextIdx;
    }
}


/*=macfunc ENDFOR
 *
 *  not_callable:
 *  unnamed:
 *  what:   Terminates the _FOR function template block
=*/
    STATIC tMacro*
mLoad_ENDFOR( tMacro* pM, char** ppzScan )
{
    current_for->pEnd = current_for->pSibling = pM;

    memset( (void*)pM, 0, sizeof( *pM ));
    return (tMacro*)NULL;
}


/*=macfunc FOR
 *
 *  what:    Emit a template block multiple times
 *  cindex:  looping, for
 *  cindex:  for loop
 *  load_func: this is private.
 *
 *  desc:
 *  The first argument must be the name of the block.
 *  The scope of the @code{FOR} function extends to a macro marker
 *  that contains a slash @code{/} and this block name, rather
 *  than any other macro function.
 *
 *  If there are any further arguments, a single argument is interpreted as
 *  an iteration separator (text to be emitted between emitted copies of the
 *  text block).  If there are more arguments, they must be named arguments:
 *  @code{FROM}, @code{TO}, @code{BY} or @code{SEP}.
 *  @code{FROM}, @code{TO} and @code{BY} must be followed by a simple
 *  number or a single pair of parentheses containing an expression
 *  to be evaluated into a number.
 *
 *  Each copy of a text block macro has an associated index number.  If the
 *  definition file specified the index values, the indexes may have gaps in
 *  the sequence.  @code{FROM}, @code{TO} and @code{BY} may be used to
 *  select specific sets of entries.  If @code{TO} is omitted, the default
 *  is to terminate the loop when the index goes outside of the valid range
 *  of indexes.  If @code{BY} is omitted, only extant index values will be
 *  iterated.  If @code{BY} is specified on a sparse block, the block will
 *  be emitted with the block values undefined, but @code{[#_eval _index#]}
 *  still showing the correct value.  If @code{FROM} is omitted, the first
 *  or last defined index value will be used (depending on the sign of
 *  @code{BY}).
 *
 *  @example
 *  [#_FOR var FROM 0 TO (LIMIT _env) SEP "," #]
 *  ... text with @code{var}ious substitutions ...[#
 *  /var#]
 *  @end example
 *
 *  @noindent
 *  this will repeat the @code{\n... text with @code{var}ious
 *  substitutions ...} several times, depending on @code{LIMIT},
 *  a value which is gotten from the environment.  Each repetition,
 *  except for the last, will have a comma @code{,} after it.
 *
 *  @example
 *  [#_FOR var "," #]
 *  ... text with @code{var}ious substitutions ...[#
 *  /var#]
 *  @end example
 *
 *  @noindent
 *  This will do the same thing, but only for the index
 *  values of @code{var} that have actually been defined.
=*/
    tMacro*
mFunc_For( tMacro* pM, tDefEntry* pCurDef )
{
    tMacro*     pMRet = pM->pEnd;
    ag_bool     isIndexed;
    tDefEntry*  pDef  = findDefEntry( pM->ppTkns[0], pCurDef, &isIndexed );

    if (pDef == (tDefEntry*)NULL)
        return pMRet;

    forLoopDepth++;

    {
        int       saveIdx     = curIndex;
        int       idxFrom     = ENTRY_END;
        int       idxTo       = ENTRY_END;
        int       idxBy       = ENTRY_END;
        char*     pzSep       = "";
        ag_bool   iterating   = AG_FALSE;
        ag_bool   svFirst, svLast;

        svFirst = firstForCycle;
        svLast  = lastForCycle;

        if (isIndexed) {
            /*
             *  The "to" and "from" values are whatever.
             *  The "by" count is always 1.
             *  The separator will never be used, set to the empty string.
             */
            genCount(  pM, pDef, pCurDef, "",
                       pDef->index, pDef->index, 1 );

        } else {
            /*
             *  IF there is only one argument, it *must* be
             *  the text to insert between emitted entries.
             */
            if (pM->tknCt == 2)
                pzSep = pM->ppTkns[1];

            else if (pM->tknCt > 2) {
                evalForArgs( &idxFrom, &idxTo, &idxBy, &pzSep,
                             pM, pCurDef );

                iterating = (  (idxFrom != ENTRY_END)
                            || (idxTo   != ENTRY_END)
                            || (idxBy   != ENTRY_END));
                if (idxBy == ENTRY_END)
                    idxBy = 1;
            }

            if (! iterating)
                 genEachBlock( pM, pDef, pCurDef, pzSep );

            else genCount(  pM, pDef, pCurDef, pzSep,
                            idxFrom, idxTo, idxBy );
        }

        forLoopDepth--;
        firstForCycle = svFirst;
        lastForCycle  = svLast;
        curIndex      = saveIdx;
    }

    return pMRet;
}

    tMacro*
mLoad_FOR( tMacro* pM, char** ppzScan )
{
    char*          pzScan = pM->pzText;
    int            maxTkn;
    char**         ppzStack;
    tMacro*        save_stack = current_for;
    tpLoadProc*    papLP = papLoadProc;

    static tpLoadProc apForLoad[ FUNC_CT ] = { (tpLoadProc)NULL };

    if (apForLoad[0] == (tpLoadProc)NULL) {
        memcpy( (void*)apForLoad, apLoadProc, sizeof( apLoadProc ));
        apForLoad[ FTYP_ENDFOR ]  = &mLoad_ENDFOR;
    }

    papLoadProc = apForLoad;
    current_for = pM;

    while (isspace( *pzScan )) pzScan++;
    pzScan += sizeof( "_FOR" )-1;
    while (isspace( *pzScan )) pzScan++;
    maxTkn = strlen( pM->pzText ) / 2;

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

    pM->tknCt = tokenize( pzScan, maxTkn, ppzStack );
    if (pM->tknCt == 0) {
        fprintf( stderr, zTplErr, pzTemplFileName, pM->lineNo,
                 "missing index name" );
        LOAD_ABORT;
    }

    pM->ppTkns =
        (char**)AGREALOC( (void*)ppzStack,
                          pM->tknCt * sizeof(char**) );
    pM->pzText = (char*)NULL;
    pM++;

    pM = parseTemplate( pM, ppzScan );
    if (*ppzScan == (char*)NULL) {
        fprintf( stderr, zTplErr, pzTemplFileName, pM->lineNo,
                 "parse error" );
        LOAD_ABORT;
    }

    current_for = save_stack;
    papLoadProc = papLP;
    return pM;
}
/* end of agFunFor.c */
