/*
* $Id: funcIf.c,v 4.13 2007/07/04 20:51:12 bkorb Exp $
*
* Time-stamp: "2007-07-04 11:26:32 bkorb"
* Last Committed: $Date: 2007/07/04 20:51:12 $
*
* This module implements the _IF text function.
*
* This file is part of AutoGen.
* AutoGen copyright (c) 1992-2007 by Bruce Korb - all rights reserved
*
* AutoGen is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, 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 this program. If not, see .
*/
tSCC zNoIfEnd[] = "%s ERROR: cannot find ENDIF\n\t'%s'\n";
tSCC zNoIfExpr[] = "expressionless IF";
typedef struct if_stack tIfStack;
struct if_stack {
tMacro* pIf;
tMacro* pElse;
};
static tIfStack current_if;
static tLoadProc mLoad_Elif, mLoad_Else;
/* = = = START-STATIC-FORWARD = = = */
/* static forward declarations maintained by :mkfwd */
static ag_bool
eval_true( void );
static tMacro*
mLoad_Elif( tTemplate* pT, tMacro* pMac, tCC** ppzScan );
static tMacro*
mLoad_Else( tTemplate* pT, tMacro* pMac, tCC** ppzScan );
/* = = = END-STATIC-FORWARD = = = */
/*
* eval_true - should a string be interpreted as TRUE?
*
* It is always true unless:
*
* 1. it is the empty string
* 2. it starts with a digit and the number evaluates to zero
* 3. it starts with either "#f" or "#F"
* 4. For its length or its first five characters (whichever is less)
* it matches the string "false"
*/
static ag_bool
eval_true( void )
{
ag_bool needFree;
ag_bool res = AG_TRUE;
tCC* pz = evalExpression( &needFree );
if (isdigit( *pz ))
res = (atoi(pz) == 0) ? AG_FALSE : AG_TRUE;
else switch (*pz) {
case NUL:
res = AG_FALSE;
break;
case '#':
if ((pz[1] == 'f') || (pz[1] == 'F'))
res = AG_FALSE;
break;
case 'f':
case 'F':
{
int len = strlen( pz );
if (len > 5)
len = 5;
if (strneqvcmp( "false", pz, len ) == 0)
res = AG_FALSE;
break;
}
}
if (needFree)
AGFREE( (void*)pz );
return res;
}
/*=macfunc IF
*
* what: Conditionally Emit a Template Block
* cindex: conditional emit
* cindex: if test
* handler_proc:
* load_proc:
*
* desc:
* Conditional block. Its arguments are evaluated (@pxref{EXPR}) 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 @code{ELIF}, @code{ELSE} or @code{ENDIF} is emitted.
* @code{ELIF} introduces a conditional alternative if the @code{IF}
* clause evaluated FALSE and @code{ELSE} introduces an unconditional
* alternative.
*
* @example
* [+IF +]
* emit things that are for the true condition[+
*
* ELIF +]
* 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
*
* @noindent
* @code{} may be any expression described in the
* @code{EXPR} expression function, including the use of apply-codes
* and value-names. If the expression yields an empty string, it
* is interpreted as @i{false}.
=*/
/*=macfunc ENDIF
*
* what: Terminate the @code{IF} Template Block
* in-context:
*
* desc:
* This macro ends the @code{IF} function template block.
* For a complete description @xref{IF}.
=*/
tMacro*
mFunc_If( tTemplate* pT, tMacro* pMac )
{
tMacro* pRet = pT->aMacros + pMac->endIndex;
tMacro* pIf = pMac;
tSCC zIfFmt[] = "IF expression `%s' on line %d yielded true\n";
do {
/*
* The current macro becomes the 'ELIF' or 'ELSE' macro
*/
pCurMacro = pMac;
/*
* 'ELSE' is equivalent to 'ELIF true'
*/
if ( (pMac->funcCode == FTYP_ELSE)
|| eval_true()) {
if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) {
fprintf( pfTrace, zIfFmt, (pMac->funcCode == FTYP_ELSE)
? "ELSE clause" : pT->pzTemplText + pMac->ozText,
pMac->lineNo );
if (OPT_VALUE_TRACE < TRACE_EVERYTHING)
fprintf( pfTrace, zFileLine, pCurTemplate->pzTplFile,
pIf->lineNo );
}
generateBlock( pT, pMac+1, pT->aMacros + pMac->sibIndex );
break;
}
pMac = pT->aMacros + pMac->sibIndex;
} while (pMac < pRet);
if ((OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) && (pMac >= pRet)) {
fprintf( pfTrace, "IF `%s' macro selected no clause\n",
pCurTemplate->pzTemplText + pCurMacro->ozText );
if (OPT_VALUE_TRACE < TRACE_EVERYTHING)
fprintf( pfTrace, zFileLine, pCurTemplate->pzTplFile,
pIf->lineNo );
}
return pRet;
}
/*=macfunc WHILE
*
* what: Conditionally loop over a Template Block
* cindex: conditional emit
* cindex: while test
* handler_proc:
* load_proc:
*
* desc:
* Conditionally repeated block. Its arguments are evaluated (@pxref{EXPR})
* and as long as 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 @code{ENDWHILE} is emitted.
*
* @example
* [+WHILE +]
* emit things that are for the true condition[+
*
* ENDWHILE +]
* @end example
*
* @noindent
* @code{} may be any expression described in the
* @code{EXPR} expression function, including the use of apply-codes
* and value-names. If the expression yields an empty string, it
* is interpreted as @i{false}.
=*/
/*=macfunc ENDWHILE
*
* what: Terminate the @code{WHILE} Template Block
* in-context:
*
* desc:
* This macro ends the @code{WHILE} function template block.
* For a complete description @xref{WHILE}.
=*/
tMacro*
mFunc_While( tTemplate* pT, tMacro* pMac )
{
tMacro* pRet = pT->aMacros + pMac->endIndex;
int ct = 0;
if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS)
fprintf( pfTrace, "WHILE `%s' loop in %s on line %d begins:\n",
pCurTemplate->pzTemplText + pCurMacro->ozText,
pT->pzTplFile, pMac->lineNo );
for (;;) {
pCurTemplate = pT;
pCurMacro = pMac;
if (! eval_true())
break;
ct++;
generateBlock( pT, pMac+1, pT->aMacros + pMac->sibIndex );
}
if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) {
fprintf( pfTrace, "WHILE macro repeated %d times\n", ct );
if (OPT_VALUE_TRACE < TRACE_EVERYTHING)
fprintf( pfTrace, zFileLine, pT->pzTplFile, pMac->lineNo );
}
return pRet;
}
/*=macfunc ELIF
*
* what: Alternate Conditional Template Block
* in-context:
*
* 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. Its expression argument is evaluated as are
* the arguments to @code{IF}. For a complete description @xref{IF}.
=*/
static tMacro*
mLoad_Elif( tTemplate* pT, tMacro* pMac, tCC** ppzScan )
{
if ((int)pMac->res == 0)
AG_ABEND_IN( pT, pMac, zNoIfExpr );
/*
* Load the expression
*/
(void)mLoad_Expr( pT, pMac, ppzScan );
current_if.pElse->sibIndex = pMac - pT->aMacros;
current_if.pElse = pMac;
return pMac + 1;
}
/*=macfunc ELSE
*
* what: Alternate Template Block
* in-context:
*
* 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( tTemplate* pT, tMacro* pMac, tCC** ppzScan )
{
/*
* After processing an "ELSE" macro,
* we have a special handler function for 'ENDIF' only.
*/
static tpLoadProc apElseLoad[ FUNC_CT ] = { NULL };
if (apElseLoad[0] == NULL) {
memcpy( (void*)apElseLoad, apLoadProc, sizeof( apLoadProc ));
apElseLoad[ FTYP_ENDIF ] = &mLoad_Ending;
}
papLoadProc = apElseLoad;
current_if.pElse->sibIndex = pMac - pT->aMacros;
current_if.pElse = pMac;
pMac->ozText = 0;
return pMac+1;
}
/*
* mLoad_Ending is the common block termination function.
* By returning NULL, it tells the macro parsing loop to return.
*/
tMacro*
mLoad_Ending( tTemplate* pT, tMacro* pMac, tCC** ppzScan )
{
memset( (void*)pMac, 0, sizeof( *pMac ));
return NULL;
}
tMacro*
mLoad_If( tTemplate* pT, tMacro* pMac, tCC** ppzScan )
{
size_t srcLen = (size_t)pMac->res; /* macro len */
tIfStack save_stack = current_if;
tpLoadProc* papLP = papLoadProc;
tMacro* pEndifMac;
/*
* 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 ] = { NULL };
/*
* IF there is no associated text expression
* THEN woops! what are we to case on?
*/
if (srcLen == 0)
AG_ABEND_IN( pT, pMac, zNoIfExpr );
if (apIfLoad[0] == NULL) {
memcpy( (void*)apIfLoad, apLoadProc, sizeof( apLoadProc ));
apIfLoad[ FTYP_ELIF ] = &mLoad_Elif;
apIfLoad[ FTYP_ELSE ] = &mLoad_Else;
apIfLoad[ FTYP_ENDIF ] = &mLoad_Ending;
}
papLoadProc = apIfLoad;
/*
* We will need to chain together the 'IF', 'ELIF', and 'ELSE'
* macros. The 'ENDIF' gets absorbed.
*/
current_if.pIf = current_if.pElse = pMac;
/*
* Load the expression
*/
(void)mLoad_Expr( pT, pMac, ppzScan );
/*
* 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.
*/
pEndifMac = parseTemplate( pMac+1, ppzScan );
if (*ppzScan == NULL)
AG_ABEND_IN( pT, pMac, "ENDIF not found" );
current_if.pIf->endIndex = \
current_if.pElse->sibIndex = pEndifMac - pT->aMacros;
/*
* Restore the context of any encompassing block macros
*/
current_if = save_stack;
papLoadProc = papLP;
return pEndifMac;
}
tMacro*
mLoad_While( tTemplate* pT, tMacro* pMac, tCC** ppzScan )
{
size_t srcLen = (size_t)pMac->res; /* macro len */
tpLoadProc* papLP = papLoadProc;
tMacro* pEndMac;
/*
* 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 apWhileLoad[ FUNC_CT ] = { NULL };
/*
* IF there is no associated text expression
* THEN woops! what are we to case on?
*/
if (srcLen == 0)
AG_ABEND_IN( pT, pMac, "expressionless WHILE" );
if (apWhileLoad[0] == NULL) {
memcpy( (void*)apWhileLoad, apLoadProc, sizeof( apLoadProc ));
apWhileLoad[ FTYP_ENDWHILE ] = &mLoad_Ending;
}
papLoadProc = apWhileLoad;
/*
* Load the expression
*/
(void)mLoad_Expr( pT, pMac, ppzScan );
/*
* Now, do a nested parse of the template. When the matching 'ENDWHILE'
* macro is encountered, the handler routine will cause 'parseTemplate()'
* to return with the text scanning pointer pointing to the remaining
* text.
*/
pEndMac = parseTemplate( pMac+1, ppzScan );
if (*ppzScan == NULL)
AG_ABEND_IN( pT, pMac, "ENDWHILE not found" );
pMac->sibIndex = pMac->endIndex = pEndMac - pT->aMacros;
/*
* Restore the context of any encompassing block macros
*/
papLoadProc = papLP;
return pEndMac;
}
/*=gfunc set_writable
*
* what: Make the output file be writable
*
* exparg: + set? + boolean arg, false to make output non-writable + opt
*
* doc: This function will set the current output file to be writable
* (or not). This is only effective if neither the @code{--writable}
* nor @code{--not-writable} have been specified. This state
* is reset when the current suffix's output is complete.
=*/
SCM
ag_scm_set_writable( SCM set )
{
tSCC zWarn[] =
"Warning: (set-writable) function in %s on line %d:\n"
"\toverridden by invocation option\n";
switch (STATE_OPT( WRITABLE )) {
case OPTST_DEFINED:
case OPTST_PRESET:
fprintf( pfTrace, zWarn, pCurTemplate->pzTplFile, pCurMacro->lineNo );
break;
default:
if (AG_SCM_BOOL_P( set ) && (set == SCM_BOOL_F))
CLEAR_OPT( WRITABLE );
else
SET_OPT_WRITABLE;
}
return SCM_UNDEFINED;
}
/*
* Local Variables:
* mode: C
* c-file-style: "stroustrup"
* indent-tabs-mode: nil
* End:
* end of agen5/funcIf.c */