/* * $Id: funcFor.c,v 4.18 2007/07/04 20:51:12 bkorb Exp $ * * Time-stamp: "2007-07-04 11:26:24 bkorb" * Last Committed: $Date: 2007/07/04 20:51:12 $ * * This module implements the FOR text macro. * * 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 . */ #define ENTRY_END INT_MAX static tForState* pFS; /* Current "FOR" information (state) */ tSCC zNoEnd[] = "%s ERROR: FOR loop `%s' does not end\n"; /* = = = START-STATIC-FORWARD = = = */ /* static forward declarations maintained by :mkfwd */ static ag_bool nextDefinition( ag_bool invert, tDefEntry** ppList ); static int doForByStep( tTemplate* pT, tMacro* pMac, tDefEntry* pFoundDef ); static int doForEach( tTemplate* pT, tMacro* pMac, tDefEntry* pFoundDef ); static void load_ForIn( tCC* pzSrc, size_t srcLen, tTemplate* pT, tMacro* pMac ); /* = = = END-STATIC-FORWARD = = = */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Operational Functions */ /*=gfunc first_for_p * * what: detect first iteration * exparg: for_var, which for loop, opt * doc: Returns SCM_BOOL_T if the named FOR loop (or, if not named, the * current innermost loop) is on the first pass through the data. * Outside of any FOR loop, it returns SCM_UNDEFINED. * @xref{FOR}. =*/ SCM ag_scm_first_for_p( SCM which ) { if (forInfo.fi_depth <= 0) return SCM_UNDEFINED; if (! AG_SCM_STRING_P( which )) return (pFS->for_firstFor) ? SCM_BOOL_T : SCM_BOOL_F; { tForState* p = forInfo.fi_data + (forInfo.fi_depth - 1); char* pz = ag_scm2zchars( which, "which for" ); SCM res = SCM_UNDEFINED; int ct = forInfo.fi_depth; do { if (strcmp( p->for_pzName, pz ) == 0) { res = (p->for_firstFor ? SCM_BOOL_T : SCM_BOOL_F); break; } p--; } while (--ct > 0); return res; } } /*=gfunc last_for_p * * what: detect last iteration * exparg: for_var, which for loop, opt * doc: Returns SCM_BOOL_T if the named FOR loop (or, if not named, the * current innermost loop) is on the last pass through the data. * Outside of any FOR loop, it returns SCM_UNDEFINED. * @xref{FOR}. =*/ SCM ag_scm_last_for_p( SCM which ) { if (forInfo.fi_depth <= 0) return SCM_UNDEFINED; if (! AG_SCM_STRING_P( which )) return (pFS->for_lastFor ? SCM_BOOL_T : SCM_BOOL_F); { tForState* p = forInfo.fi_data + (forInfo.fi_depth - 1); char* pz = ag_scm2zchars( which, "which for" ); SCM res = SCM_UNDEFINED; int ct = forInfo.fi_depth; do { if (strcmp( p->for_pzName, pz ) == 0) { res = (p->for_lastFor ? SCM_BOOL_T : SCM_BOOL_F); break; } p--; } while (--ct > 0); return res; } } /*=gfunc for_index * * what: get current loop index * exparg: for_var, which for loop, opt * doc: Returns the current index for the named FOR loop. * If not named, then the index for the innermost loop. * Outside of any FOR loop, it returns SCM_UNDEFINED. * @xref{FOR}. =*/ SCM ag_scm_for_index( SCM which ) { if (forInfo.fi_depth <= 0) return SCM_UNDEFINED; if (! AG_SCM_STRING_P( which )) return AG_SCM_INT2SCM( pFS->for_index ); { tForState* p = forInfo.fi_data + (forInfo.fi_depth - 1); char* pz = ag_scm2zchars( which, "which for" ); SCM res = SCM_UNDEFINED; int ct = forInfo.fi_depth; do { if (strcmp( p->for_pzName, pz ) == 0) { res = AG_SCM_INT2SCM( p->for_index ); break; } p--; } while (--ct > 0); return res; } } /*=gfunc for_from * * what: set initial index * exparg: from, the initial index for the AutoGen FOR macro * * doc: This function records the initial index information * for an AutoGen FOR function. * Outside of the FOR macro itself, this function will emit an error. * @xref{FOR}. =*/ SCM ag_scm_for_from( SCM from ) { if ((! pFS->for_loading) || (! AG_SCM_NUM_P( from ))) return SCM_UNDEFINED; pFS->for_from = AG_SCM_SCM2INT( from ); return SCM_BOOL_T; } /*=gfunc for_to * * what: set ending index * exparg: to, the final index for the AutoGen FOR macro * * doc: This function records the terminating value information * for an AutoGen FOR function. * Outside of the FOR macro itself, this function will emit an error. * @xref{FOR}. =*/ SCM ag_scm_for_to( SCM to ) { if ((! pFS->for_loading) || (! AG_SCM_NUM_P( to ))) return SCM_UNDEFINED; pFS->for_to = AG_SCM_SCM2INT( to ); return SCM_BOOL_T; } /*=gfunc for_by * * what: set iteration step * exparg: by, the iteration increment for the AutoGen FOR macro * * doc: This function records the "step by" information * for an AutoGen FOR function. * Outside of the FOR macro itself, this function will emit an error. * @xref{FOR}. =*/ SCM ag_scm_for_by( SCM by ) { if ((! pFS->for_loading) || (! AG_SCM_NUM_P( by ))) return SCM_UNDEFINED; pFS->for_by = AG_SCM_SCM2INT( by ); return SCM_BOOL_T; } /*=gfunc for_sep * * what: set loop separation string * exparg: separator, the text to insert between the output of * each FOR iteration * * doc: This function records the separation string that is to be inserted * between each iteration of an AutoGen FOR function. This is often * nothing more than a comma. * Outside of the FOR macro itself, this function will emit an error. =*/ SCM ag_scm_for_sep( SCM obj ) { if (! pFS->for_loading) return SCM_UNDEFINED; AGDUPSTR( pFS->for_pzSep, ag_scm2zchars( obj, "sep str" ), "sep str" ); return SCM_BOOL_T; } static ag_bool nextDefinition( ag_bool invert, tDefEntry** ppList ) { ag_bool haveMatch = AG_FALSE; tDefEntry* pList = *ppList; while (pList != 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 == pFS->for_index) { 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 < pFS->for_index) : (pList->index > pFS->for_index)) { /* * When the "by" step is zero, force syncronization. */ if (pFS->for_by == 0) { pFS->for_index = pList->index; haveMatch = AG_TRUE; } break; } /* * The current index (pFS->for_index) 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; } static int doForByStep( tTemplate* pT, tMacro* pMac, tDefEntry* pFoundDef ) { int loopCt = 0; tDefEntry textDef; ag_bool invert = (pFS->for_by < 0) ? AG_TRUE : AG_FALSE; t_word loopLimit = OPT_VALUE_LOOP_LIMIT; tDefCtx ctx = currDefCtx; if (pFS->for_pzSep == NULL) pFS->for_pzSep = (char*)zNil; /* * IF the for-from and for-to values have not been set, * THEN we set them from the indices of the first and last * entries of the twin set. */ { tDefEntry* pLast = (pFoundDef->pEndTwin != NULL) ? pFoundDef->pEndTwin : pFoundDef; if (pFS->for_from == 0x7BAD0BAD) pFS->for_from = (invert) ? pLast->index : pFoundDef->index; if (pFS->for_to == 0x7BAD0BAD) pFS->for_to = (invert) ? pFoundDef->index : pLast->index; /* * "loopLimit" is intended to catch runaway ending conditions. * However, if you really have a gazillion entries, who am I * to stop you? */ if (loopLimit < pLast->index - pFoundDef->index) loopLimit = (pLast->index - pFoundDef->index) + 1; } /* * Make sure we have some work to do before we start. */ if (invert) { if (pFS->for_from < pFS->for_to) return 0; } else { if (pFS->for_from > pFS->for_to) return 0; } pFS->for_index = pFS->for_from; /* * FROM `from' THROUGH `to' BY `by', * DO... */ for (;;) { int nextIdx; ag_bool gotNewDef = nextDefinition( invert, &pFoundDef ); if (loopLimit-- < 0) { fprintf( pfTrace, "too many FOR iterations in %s line %d\n", pT->pzTplName, pMac->lineNo ); fprintf( pfTrace, "\texiting FOR %s from %d to %d by %d:\n" "\tmore than %d iterations\n", pT->pzTemplText + pMac->ozText, pFS->for_from, pFS->for_to, pFS->for_by, (int)OPT_VALUE_LOOP_LIMIT ); break; } if (pFS->for_by != 0) { nextIdx = pFS->for_index + pFS->for_by; } else if (invert) { nextIdx = (pFoundDef->pPrevTwin == NULL) ? pFS->for_to - 1 /* last iteration !! */ : pFoundDef->pPrevTwin->index; } else { nextIdx = (pFoundDef->pTwin == NULL) ? pFS->for_to + 1 /* last iteration !! */ : pFoundDef->pTwin->index; } /* * IF we have a non-base definition, use the old def context */ if (! gotNewDef) currDefCtx = ctx; /* * ELSE IF this macro is a text type * THEN create an un-twinned version of it to be found first */ else if (pFoundDef->valType == VALTYP_TEXT) { textDef = *pFoundDef; textDef.pNext = textDef.pTwin = NULL; currDefCtx.pDefs = &textDef; currDefCtx.pPrev = &ctx; } /* * ELSE the current definitions are based on the block * macro's values */ else { currDefCtx.pDefs = pFoundDef->val.pDefEntry; currDefCtx.pPrev = &ctx; } pFS->for_lastFor = (invert) ? ((nextIdx < pFS->for_to) ? AG_TRUE : AG_FALSE) : ((nextIdx > pFS->for_to) ? AG_TRUE : AG_FALSE); generateBlock( pT, pMac+1, pT->aMacros + pMac->endIndex ); loopCt++; pFS = forInfo.fi_data + (forInfo.fi_depth - 1); if (pFS->for_lastFor) break; fputs( pFS->for_pzSep, pCurFp->pFile ); fflush( pCurFp->pFile ); pFS->for_firstFor = AG_FALSE; pFS->for_index = nextIdx; } currDefCtx = ctx; /* Restore the def context */ return loopCt; } static int doForEach( tTemplate* pT, tMacro* pMac, tDefEntry* pFoundDef ) { int loopCt = 0; tDefCtx ctx = currDefCtx; currDefCtx.pPrev = &ctx; for (;;) { tDefEntry textDef; /* * 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 (pFoundDef->valType == VALTYP_TEXT) { textDef = *pFoundDef; textDef.pNext = textDef.pTwin = NULL; currDefCtx.pDefs = &textDef; } else { currDefCtx.pDefs = pFoundDef->val.pDefEntry; } /* * Set the global current index */ pFS->for_index = pFoundDef->index; /* * Advance to the next twin */ pFoundDef = pFoundDef->pTwin; if (pFoundDef == NULL) pFS->for_lastFor = AG_TRUE; generateBlock( pT, pMac+1, pT->aMacros + pMac->endIndex ); loopCt++; pFS = forInfo.fi_data + (forInfo.fi_depth - 1); if (pFoundDef == NULL) break; pFS->for_firstFor = AG_FALSE; /* * Emit the iteration separation */ fputs( pFS->for_pzSep, pCurFp->pFile ); fflush( pCurFp->pFile ); } currDefCtx = ctx; /* Restore the def context */ return loopCt; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static void load_ForIn( tCC* pzSrc, size_t srcLen, tTemplate* pT, tMacro* pMac ) { char* pzName = pT->pzTemplText + pMac->ozName; int ix = 0; char* pz; tDefEntry* pPrev = NULL; /* * Find the first text value */ pzSrc += 2; srcLen -= 3; while (isspace( *++pzSrc )) srcLen--; if (*pzSrc == NUL) AG_ABEND_IN( pT, pMac, "FOR x IN ... has no list" ); { size_t nmlen = strlen( pzName ); pz = AGALOC( srcLen + 2 + nmlen, "copy of FOR x IN ... text" ); strcpy( pz, pzName ); pzName = pz; manageAllocatedData(pz); pz += nmlen + 1; } memcpy( pz, pzSrc, srcLen ); pz[ srcLen ] = NUL; do { tDefEntry* pDef = getEntry(); pDef->pzDefName = pzName; pDef->index = ix++; pDef->valType = VALTYP_TEXT; pDef->val.pzText = pz; switch (*pz) { case '\'': case '"': pz = spanQuote( pz ); /* * Clean up trailing commas */ while (isspace( *pz )) pz++; if (*pz == ',') pz++; break; default: for (;;) { char ch = *(pz++); switch (ch) { case ' ': case '\t': case '\f': case '\v': case '\n': pz[-1] = NUL; if (*pz != ',') break; pz++; /* FALLTHROUGH */ case ',': pz[-1] = NUL; break; case NUL: pz--; break; default: continue; } break; } break; } /* * Clean up trailing white space */ while (isspace( *pz )) pz++; /* * IF there is a previous entry, link its twin to this one. * OTHERWISE, it is the head of the twin list. * Link to funcPrivate. */ if (pPrev != NULL) pPrev->pTwin = pDef; else pMac->funcPrivate = pDef; pPrev = pDef; } while (*pz != NUL); pMac->ozText = 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*=macfunc FOR * * what: Emit a template block multiple times * cindex: looping, for * cindex: for loop * handler_proc: * load_proc: * * desc: * This macro has a slight variation on the standard syntax: * @example * FOR [ ] * * FOR (...Scheme expression list) * * FOR IN "string" [ ... ] * @end example * * Other than for the last form, the first macro argument must be the name of * an AutoGen value. If there is no value associated with the name, the * @code{FOR} template block is skipped entirely. The scope of the @code{FOR} * macro extends to the corresponding @code{ENDFOR} macro. The last form will * create an array of string values named @code{} that only exists * within the context of this @code{FOR} loop. With this form, in order to * use a @code{separator-string}, you must code it into the end of the * template block using the @code{(last-for?)} predicate function * (@pxref{SCM last-for?}). * * If there are any arguments after the @code{value-name}, the initial * characters are used to determine the form. If the first character is * either a semi-colon (@code{;}) or an opening parenthesis (@code{(}), then * it is presumed to be a Scheme expression containing the FOR macro specific * functions @code{for-from}, @code{for-by}, @code{for-to}, and/or * @code{for-sep}. @xref{AutoGen Functions}. If it consists of an '@code{i}' * an '@code{n}' and separated by white space from more text, then the * @code{FOR x IN} form is processed. Otherwise, the remaining text is * presumed to be a string for inserting between each iteration of the loop. * This string will be emitted one time less than the number of iterations of * the loop. That is, it is emitted after each loop, excepting for the last * iteration. * * If the from/by/to functions are invoked, they will specify which copies of * the named value are to be processed. If there is no copy of the named * value associated with a particular index, the @code{FOR} template block * will be instantiated anyway. The template must use methods for detecting * missing definitions and emitting default text. In this fashion, you can * insert entries from a sparse or non-zero based array into a dense, zero * based array. * * @strong{NB:} the @code{for-from}, @code{for-to}, @code{for-by} and * @code{for-sep} functions are disabled outside of the context of the * @code{FOR} macro. Likewise, the @code{first-for}, @code{last-for} * and @code{for-index} functions are disabled outside of the range * of a @code{FOR} block. * * @strong{Also:} the @code{} must be a single level name, * not a compound name (@pxref{naming values}). * * @example * [+FOR var (for-from 0) (for-to ) (for-sep ",") +] * ... text with @code{var}ious substitutions ...[+ * ENDFOR var+] * @end example * * @noindent * this will repeat the @code{... text with @code{var}ious * substitutions ...} +1 times. Each repetition, * except for the last, will have a comma @code{,} after it. * * @example * [+FOR var ",\n" +] * ... text with @code{var}ious substitutions ...[+ * ENDFOR var +] * @end example * * @noindent * This will do the same thing, but only for the index * values of @code{var} that have actually been defined. =*/ /*=macfunc ENDFOR * * what: Terminates the @code{FOR} function template block * in-context: * * desc: * This macro ends the @code{FOR} function template block. * For a complete description @xref{FOR}. =*/ tMacro* mFunc_For( tTemplate* pT, tMacro* pMac ) { tMacro* pMRet = pT->aMacros + pMac->endIndex; ag_bool isIndexed; tDefEntry* pDef; int loopCt; if (pMac->funcPrivate != NULL) pDef = pMac->funcPrivate; else { pDef = findDefEntry( pT->pzTemplText + pMac->ozName, &isIndexed ); if (pDef == NULL) { if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) { fprintf( pfTrace, "FOR loop skipped - no definition for `%s'\n", pT->pzTemplText + pMac->ozName ); if (OPT_VALUE_TRACE < TRACE_EVERYTHING) fprintf( pfTrace, zFileLine, pT->pzTplFile, pMac->lineNo ); } return pMRet; } } if (++(forInfo.fi_depth) > forInfo.fi_alloc) { forInfo.fi_alloc += 5; if (forInfo.fi_data == NULL) forInfo.fi_data = (tForState*) AGALOC( 5 * sizeof( tForState ), "Initial FOR sate"); else forInfo.fi_data = (tForState*) AGREALOC( (void*)forInfo.fi_data, forInfo.fi_alloc * sizeof( tForState ), "Expansion of FOR state" ); } pFS = forInfo.fi_data + (forInfo.fi_depth - 1); memset( (void*)pFS, 0, sizeof( tForState )); pFS->for_firstFor = AG_TRUE; pFS->for_pzName = pT->pzTemplText + pMac->ozName; if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) fprintf( pfTrace, "FOR %s loop in %s on line %d begins:\n", pT->pzTemplText + pMac->ozName, pT->pzTplFile, pMac->lineNo ); if (pT->pzTemplText[ pMac->ozText ] == '(') { pFS->for_from = \ pFS->for_to = 0x7BAD0BAD; pFS->for_loading = AG_TRUE; (void) eval( pT->pzTemplText + pMac->ozText ); pFS->for_loading = AG_FALSE; loopCt = doForByStep( pT, pMac, pDef ); } else { pFS->for_pzSep = pT->pzTemplText + pMac->ozText; loopCt = doForEach( pT, pMac, pDef ); } forInfo.fi_depth--; if (OPT_VALUE_TRACE >= TRACE_BLOCK_MACROS) { fprintf( pfTrace, "FOR %s repeated %d times\n", pT->pzTemplText + pMac->ozName, loopCt ); if (OPT_VALUE_TRACE < TRACE_EVERYTHING) fprintf( pfTrace, zFileLine, pT->pzTplFile, pMac->lineNo ); } return pMRet; } tMacro* mLoad_For( tTemplate* pT, tMacro* pMac, tCC** ppzScan ) { char* pzCopy = pT->pNext; /* next text dest */ tCC* pzSrc = (char const*)pMac->ozText; /* macro text */ size_t srcLen = (size_t)pMac->res; /* macro len */ tMacro* pEndMac; /* * Save the global macro loading mode */ tpLoadProc* papLP = papLoadProc; static tpLoadProc apForLoad[ FUNC_CT ] = { NULL }; papLoadProc = apForLoad; if (srcLen == 0) AG_ABEND_IN( pT, pMac, "FOR macro requires iterator name" ); /* * IF this is the first time here, * THEN set up the "FOR" mode callout table. * It is the standard table, except entries are inserted * for functions that are enabled only while processing * a FOR macro */ if (apForLoad[0] == NULL) { memcpy( (void*)apForLoad, apLoadProc, sizeof( apLoadProc )); apForLoad[ FTYP_ENDFOR ] = &mLoad_Ending; } /* * pzSrc points to the name of the iteration "variable" * Special hack: if the name is preceeded by a `.', * then the lookup is local-only and we will accept it. */ pMac->ozName = pT->pNext - pT->pzTemplText; if (*pzSrc == '.') { *(pzCopy++) = *(pzSrc++); if (! ISNAMECHAR( *pzSrc )) pzCopy--; /* force an error */ } while (ISNAMECHAR( *pzSrc )) *(pzCopy++) = *(pzSrc++); *(pzCopy++) = NUL; if (pT->pzTemplText[ pMac->ozName ] == NUL) AG_ABEND_IN( pT, pMac, "invalid FOR loop variable" ); /* * Skip space to the start of the text following the iterator name */ while (isspace( *pzSrc )) pzSrc++; srcLen -= pzSrc - (char*)pMac->ozText; /* * * * * * * No source -> zero offset to text */ if ((ssize_t)srcLen <= 0) { pMac->ozText = 0; } /* * * * * * * FOR foo IN ... -> no text, but we create an array of text values */ else if ( (strneqvcmp( pzSrc, "in", 2 ) == 0) && isspace( pzSrc[2] )) { load_ForIn( pzSrc, srcLen, pT, pMac ); } /* * * * * * * *EITHER* a: FOR foo "<>" * *OR* FOR foo (scheme ...) ... */ else { char* pzCopied = pzCopy; pMac->ozText = pzCopy - pT->pzTemplText; do { *(pzCopy++) = *(pzSrc++); } while (--srcLen > 0); *(pzCopy++) = NUL; if ((*pzCopied == '"') || (*pzCopied == '\'')) spanQuote( pzCopied ); } /* * * * * */ pT->pNext = pzCopy; pEndMac = parseTemplate( pMac + 1, ppzScan ); if (*ppzScan == NULL) AG_ABEND_IN( pT, pMac, "ENDFOR not found" ); pMac->endIndex = pMac->sibIndex = pEndMac - pT->aMacros; papLoadProc = papLP; return pEndMac; } /* * Local Variables: * mode: C * c-file-style: "stroustrup" * indent-tabs-mode: nil * End: * end of agen5/funcFor.c */