
/*
 *  $Id: agFunIf.c,v 2.5 1999/07/07 19:30:51 bkorb Exp $
 *
 *  This module implements the _IF 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"

tSCC zNoIfEnd[]   = "%s ERROR:  cannot find ENDIF\n\t'%s'\n";

typedef struct if_stack tIfStack;
struct if_stack {
    tMacro*  pIf;
    tMacro*  pElse;
};

STATIC tIfStack  current_if;


    STATIC ag_bool
evalExpr( tMacro*    pM,
          tDefEntry* pCurDef )
{
    switch ( eval( pM, pCurDef )) {
    default:
    case VT_NONE:
        return AG_FALSE;

    case VT_VALUE:
        return ((t_word)pM->evalRes != 0);

    case VT_STRING:
        return (*(char*)pM->evalRes != NUL);

    case VT_ALLOC_STR:
    {
        ag_bool  res = (*(char*)pM->evalRes != NUL);
        AGFREE( pM->evalRes );
        return res;
    }

    case VT_STACK:
        evalError( pM );
    }
}


/*=macfunc ELIF
 *
 *  what:   Alternate Conditional Template Block
 *  not_callable:
 *
 *  desc:
 *    This macro must only appear after an @code{IF} function,
 *    and before any associated @code{ELSE} or @code{ENDIF} functions.
 *    It denotes the start of an alternate template block for
 *    the @code{IF} function.  For a complete description @xref{IF}.
=*/
    STATIC tMacro*
mLoad_ELIF( tMacro* pM, char** ppzScan )
{
    char*  pzScan = pM->pzText;
    char*  apzTkns[ MAX_TKN ];
    int    ct;

    pzScan += sizeof( "_ELIF" )-1;
    while (isspace( *pzScan )) pzScan++;
    ct = tokenize( pzScan, MAX_TKN, apzTkns );

    /*
     *  Allocate for "<name>", and "_get" in addition
     *  to the number of arguments past the name
     */
    if (ct == 0) {
        fprintf( stderr, zTplErr, pzTemplFileName, templLineNo,
                 "ELIF has no expression" );
        LOAD_ABORT;
    }

    /*
     *  The struct gets the true count
     */
    {
        char**  pp = (char**)AGALOC( ct * sizeof( char* ));
        if (pp == (char**)NULL) {
            fprintf( stderr, zAllocErr, pzProg,
                     ct * sizeof(char**), zTokenList );
            LOAD_ABORT;
        }
        pM->ppTkns = pp;
        pM->pzText = (char*)NULL;
        memcpy( (void*)pp, (void*)apzTkns, ct * sizeof( char* ));
    }
    pM->tknCt  = ct;

    current_if.pElse->pSibling = pM;
    current_if.pElse = pM;
    return pM+1;
}


/*=macfunc ELSE
 *
 *  what:   Alternate Template Block
 *  not_callable:
 *
 *  desc:
 *    This macro must only appear after an @code{IF} function,
 *    and before the associated @code{ENDIF} function.
 *    It denotes the start of an alternate template block for
 *    the @code{IF} function.  For a complete description @xref{IF}.
=*/
    STATIC tMacro*
mLoad_ELSE( tMacro* pM, char** ppzScan )
{
    current_if.pElse->pSibling = pM;
    current_if.pElse = pM;

    return pM+1;
}


/*=macfunc ENDIF
 *
 *  what:   Terminate the @code{IF} Template Block
 *  not_callable:
 *
 *  desc:
 *    This macro ends the @code{IF} function template block.
 *    For a complete description @xref{IF}.
=*/
    STATIC tMacro*
mLoad_ENDIF( tMacro* pM, char** ppzScan )
{
    tMacro* pIf = current_if.pIf;

    current_if.pElse->pSibling = pM;

    for (;;) {
        pIf->pEnd = pM;
        if (pIf == current_if.pElse)
            break;
        pIf = pIf->pSibling;
    }

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


/*=macfunc IF
 *
 *  what:    Conditionally Emit a Template Block
 *  cindex:  conditional emit
 *  cindex:  if test
 *  load_func: private
 *
 *  desc:
 *  Conditional block.  Its arguments are evaluated (see _EVAL) and
 *  if the result is non-zero or a string with one or more bytes,
 *  then the condition is true and the text from that point
 *  until a matched "_ELIF", "_ELSE" or "_ENDIF" is emitted.
 *  "_ELIF" introduces a conditional alternative if the "_IF"
 *  clause evaluated FALSE and "_ELSE" introduces an unconditional
 *  alternative.
 *
 *  @example
 *  [#_IF [[condition]]#]
 *  emit things that are for the true condition[#
 *
 *  _ELIF [[maybe]] #]
 *  emit things that are true maybe[#
 *
 *  _ELSE "This may be a comment" #]
 *  emit this if all but else fails[#
 *
 *  _ENDIF "This may *also* be a comment" #]
 *  @end example
=*/
    tMacro*
mFunc_If( tMacro* pMac, tDefEntry* pCurDef )
{
    tMacro* pRet = pMac->pEnd;

    do  {
        /*
         *  'ELSE' is equivalent to 'ELIF true'
         */
        if (  (pMac->funcCode == FTYP_ELSE)
           || evalExpr( pMac, pCurDef )) {

            generateBlock( pMac+1, pMac->pSibling, pCurDef );
            break;
        }
        pMac = pMac->pSibling;
    } while (pMac != pRet);

    return pRet;
}

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

    /*
     *  While processing an "IF" macro,
     *  we have handler functions for 'ELIF', 'ELSE' and 'ENDIF'
     *  Otherwise, we do not.  Switch the callout function table.
     */
    static tpLoadProc apIfLoad[ FUNC_CT ] = { (tpLoadProc)NULL };

    if (apIfLoad[0] == (tpLoadProc)NULL) {
        memcpy( (void*)apIfLoad, apLoadProc, sizeof( apLoadProc ));
        apIfLoad[ FTYP_ELIF ]  = &mLoad_ELIF;
        apIfLoad[ FTYP_ELSE ]  = &mLoad_ELSE;
        apIfLoad[ FTYP_ENDIF ] = &mLoad_ENDIF;
    }

    papLoadProc = apIfLoad;

    /*
     *  We will need to chain together the 'IF', 'ELIF', and 'ELSE'
     *  macros.  The 'ENDIF' gets absorbed.
     */
    current_if.pIf = current_if.pElse = pM;

    /*
     *  Tokenize everything that follows the "_IF"
     */
    pzScan += sizeof( "_IF" )-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 );
    pM->ppTkns =
        (char**)AGREALOC( (void*)ppzStack,
                          pM->tknCt * sizeof(char**) );
    pM->pzText = (char*)NULL;

    /*
     *  Now, do a nested parse of the template.
     *  When the matching 'ENDIF' macro is encountered,
     *  the handler routine will cause 'parseTemplate()'
     *  to return with the text scanning pointer pointing
     *  to the remaining text.
     */
    pM++;
    pM = parseTemplate( pM, ppzScan );
    if (*ppzScan == (char*)NULL) {
        fprintf( stderr, zTplErr, pzTemplFileName, templLineNo, "parse err" );
        LOAD_ABORT;
    }

    /*
     *  Restore the context of any encompassing block macros
     */
    current_if  = save_stack;
    papLoadProc = papLP;
    return pM;
}
/* end of agFunIf.c */
