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

#ifdef WITH_INCLUDED_REGEX
#  include "compat/gnu-regex.h"
#else
#  include <regex.h>
#endif

#include "agfunc.h"
#include "proto.h"

typedef struct case_stack tCaseStack;
struct case_stack {
    tMacro*  pCase;
    tMacro*  pSelect;
};

STATIC tCaseStack  current_case;

/*
 *  See if the regular expression in the case element function
 *  matches the text of the case function.
 */
    STATIC tSuccess
matchExpr( tMacro* pM, char* pzComp )
{
    int     	advRes;
    regex_t 	re;
    regmatch_t  match[2];

    if (regcomp( &re, pM->pzText, REG_EXTENDED ) != 0) {
        tSCC zFmt[] =
            "Error:  _CASE element from line %d not a valid regexp:  %s\n";
        fprintf( stderr, zFmt, pM->lineNo, pM->pzText );
        longjmp( fileAbort, FAILURE );
    }

    advRes = regexec( &re, pzComp, (size_t)2, match, 0 );

    regfree( &re );

    /*
     *  Ensure we have a full match
     */
    if (  (advRes == REG_NOMATCH)
       || (match[0].rm_so != 0)
       || (match[0].rm_eo != strlen( pzComp )) )
        return PROBLEM;
    return SUCCESS;
}


/*=macfunc ESAC
 *
 *  what:   Terminate the @code{CASE} Template Block
 *  not_callable:
 *
 *  desc:
 *    This macro ends the @code{CASE} function template block.
 *    For a complete description, @xref{CASE}.
=*/
    STATIC tMacro*
mLoad_ESAC( tMacro* pM, char** ppzScan )
{
    tMacro* pCase = current_case.pCase;

    current_case.pSelect->pSibling = pM;

    for (;;) {
        pCase->pEnd = pM;
        if (pCase == current_case.pSelect)
            break;
        pCase = pCase->pSibling;
    }

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


/*=macfunc SELECT
 *
 *  not_callable:
 *  unnamed:
 *  what:    Selection block for _CASE function
=*/
    tMacro*
mLoad_SELECT( tMacro* pM, char** ppzScan )
{
    int    ct;
    char** pp;
    char*  pz = pM->pzText;
    char*  apzTkns[ MAX_TKN ];

    pz += sizeof( "_" )-1;
    while (isspace( *pz )) pz++;

    pM->pzText = pz;
    pz += strlen( pz );
    while ((pz > pM->pzText) && isspace( pz[-1] )) pz--;
    *pz = NUL;

    current_case.pSelect->pSibling = pM;
    current_case.pSelect = pM;

    return pM+1;
}


/*=macfunc CASE
 *
 *  what:   Select one of several template blocks
 *  load_func: this is private.
 *
 *  desc:
 *  The arguments are evaluated and converted to a string, if necessary.
 *  The scope of the macro is up to the matching @code{ESAC}.
 *  Within the scope of a CASE, this string is matched against
 *  case element macros named simply "_".  The entire argument string
 *  to the case element macro is treated as a regular expression
 *  and the "CASE" argument is matched against it.  It is successful
 *  only if there is a complete match.
 *
 *  @noindent
 *  For example:
 *
 *  @example
 *  [#_CASE text_mac _get#]
 *  this is a comment because there is no label
 *  [#_ [aA].*#]this is emitted if "text_mac" starts with an 'a'
 *  [#_ .*[zZ]#]this is emitted if it ends with a 'z'
 *  [#_ .*#]this is emitted if nothing else
 *  [#_ESAC#]
 *  @end example
=*/
    tMacro*
mFunc_Case( tMacro* pM, tDefEntry* pCurDef )
{
    tMacro*  pEnd = pM->pEnd;
    char*    pzVal;
    char     zValBuf[ 16 ];
    ag_bool  mustFree = AG_FALSE;

    switch ( eval( pM, pCurDef )) {
    default:
    case VT_NONE:
        return pEnd;

    case VT_VALUE:
        sprintf( zValBuf, "%d", (int)pM->evalRes );
        pzVal = zValBuf;
        break;

    case VT_ALLOC_STR:
        mustFree = AG_TRUE;

    case VT_STRING:
        pzVal = (char*)pM->evalRes;
        break;

    case VT_STACK:
        evalError( pM );
    }

    for (;;) {
        pM = pM->pSibling;
        if (pM == pEnd)
            break;

        if (SUCCEEDED( matchExpr( pM, pzVal ))) {
            generateBlock( pM+1, pM->pSibling, pCurDef );
            break;
        }
    }

    if (mustFree)
        AGFREE( (void*)pzVal );

    return pEnd;
}


/*
 *  mLoad_CASE
 *
 *  This function is called to set up (load) the macro
 *  when the template is first read in (before processing).
 */
    tMacro*
mLoad_CASE( tMacro* pM, char** ppzScan )
{
    char*          pzScan = pM->pzText;
    int            maxTkn;
    char**         ppzStack;
    tCaseStack     save_stack = current_case;

    /*
     *  Save the global macro loading mode
     */
    tpLoadProc*    papLP = papLoadProc;

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

    /*
     *  IF this is the first time here,
     *  THEN set up the "CASE" mode callout table.
     *  It is the standard table, except entries are inserted
     *  for functions that are enabled only while processing
     *  a CASE macro
     */
    if (apCaseLoad[0] == (tpLoadProc)NULL) {
        memcpy( (void*)apCaseLoad, apLoadProc, sizeof( apLoadProc ));
        apCaseLoad[ FTYP_SELECT ] = &mLoad_SELECT;
        apCaseLoad[ FTYP_ESAC   ] = &mLoad_ESAC;
    }

    /*
     *  Set the global macro loading mode
     */
    papLoadProc = apCaseLoad;

    /*
     *  Save global pointers to the current macro entry.
     *  We will need this to link the CASE, SELECT and ESAC
     *  functions together.
     */
    current_case.pCase = current_case.pSelect = pM;

    pzScan += sizeof( "_CASE" )-1;
    while (isspace( *pzScan )) pzScan++;

    /*
     *  Make sure we have more than enough space for all our tokens.
     */
    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 );

    /*
     *  Now release the memory we do not need for the token pointers.
     */
    pM->ppTkns =
        (char**)AGREALOC( (void*)ppzStack,
                          pM->tknCt * sizeof(char**) );
    pM->pzText = (char*)NULL;
    pM++;

    /*
     *  Continue parsing the template from this nested level
     */
    pM = parseTemplate( pM, ppzScan );
    if (*ppzScan == (char*)NULL) {
        fprintf( stderr, zTplErr, pzTemplFileName, pM->lineNo, "parse err" );
        LOAD_ABORT;
    }

    /*
     *  Restore any enclosing CASE function's context.
     */
    current_case = save_stack;

    /*
     *  Restore the global macro loading mode
     */
    papLoadProc  = papLP;

    /*
     *  Return the next available macro descriptor
     */
    return pM;
}
/* end of agFunCase.c */
