/*
* $Id: loadPseudo.c,v 4.13 2007/07/07 01:04:11 bkorb Exp $
*
* Time-stamp: "2007-07-06 12:15:42 bkorb"
* Last Committed: $Date: 2007/07/07 01:04:11 $
*
* This module processes the "pseudo" 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 .
*/
/*
* loadPseudoMacro
*
* Find the start and end macro markers. In btween we must find the
* "autogen" and "template" keywords, followed by any suffix specs.
*/
#define DEFINE_FSM
#include "pseudo-fsm.h"
tSCC zAgName[] = "autogen5";
tSCC zTpName[] = "template";
/* = = = START-STATIC-FORWARD = = = */
/* static forward declarations maintained by :mkfwd */
static tCC*
doSchemeExpr( tCC* pzData, tCC* pzFileName );
static te_pm_event
findTokenType( tCC** ppzData, te_pm_state fsm_state );
static tCC*
copyMarker( tCC* pzData, char* pzMark, size_t * pCt );
/* = = = END-STATIC-FORWARD = = = */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* doSchemeExpr
*
* Process a scheme specification
*/
static tCC*
doSchemeExpr( tCC* pzData, tCC* pzFileName )
{
char* pzEnd = (char*)pzData + strlen( pzData );
char ch;
tMacro* pCM = pCurMacro;
tMacro mac = { (teFuncType)~0, 0, 0, 0, 0, 0, 0, NULL };
mac.lineNo = templLineNo;
pzEnd = (char*)skipScheme( pzData, pzEnd );
ch = *pzEnd;
*pzEnd = NUL;
pCurMacro = &mac;
ag_scm_c_eval_string_from_file_line(
(char*)pzData, pzFileName, templLineNo );
pCurMacro = pCM;
*pzEnd = ch;
while (pzData < pzEnd)
if (*(pzData++) == '\n')
templLineNo++;
return (tCC*)pzEnd;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* skipSuffixSpec
*
* Process a suffix specification
*/
LOCAL tCC*
doSuffixSpec( tCC* pzData, tCC* pzFileName, int lineNo )
{
/*
* The following is the complete list of POSIX required-to-be-legal
* file name characters. These are the only characters we allow to
* appear in a suffix. We do, however, add '=' and '%' because we
* also allow a format specification to follow the suffix,
* separated by an '=' character.
*/
tSCC zSuffixSpecChars[] = "=%" "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "-_./";
tSCC zEmptySpec[] = "Empty suffix format";
tOutSpec* pOS;
tCC* pzSfxFmt;
tCC* pzResult;
static tOutSpec** ppOSList = &pOutSpecList;
/*
* Skip over the suffix construct
*/
size_t spn = strspn(pzData, zSuffixSpecChars+2);
if (pzData[spn] != '=') {
pzSfxFmt = NULL;
} else {
pzSfxFmt = pzData + spn; // "spn" includes format spec.
if (pzSfxFmt[1] == '(') {
tCC *pe = pzSfxFmt + 1 + strlen( pzSfxFmt + 1 );
tCC *ptr = skipScheme( pzSfxFmt+1, pe );
spn = ptr - pzData;
} else {
spn += strspn( pzSfxFmt, zSuffixSpecChars );
if (pzSfxFmt == pzData + spn)
AG_ABEND(zEmptySpec);
}
}
pzResult = pzData + spn;
/*
* If pzFileName is NULL, then we are called by --select-suffix.
* Otherwise, the suffix construct is saved only for the main template,
* and only when the --select-suffix option was not specified.
*/
if ( (pzFileName != NULL)
&& ( (procState != PROC_STATE_LOAD_TPL)
|| HAVE_OPT( SELECT_SUFFIX )))
return pzResult;
/*
* Allocate Output Spec and link into the global list. Copy all the
* "spanned" text, including any '=' character, scheme expression or
* file name format string.
*/
pOS = AGALOC(sizeof( *pOS ) + (size_t)spn + 1, "Output Specification");
*ppOSList = pOS;
ppOSList = &pOS->pNext;
pOS->pNext = NULL;
memcpy( pOS->zSuffix, pzData, spn );
pOS->zSuffix[ spn ] = NUL;
/*
* IF the suffix contains its own formatting construct,
* THEN split it off from the suffix and set the formatting ptr.
* ELSE supply a default.
*/
if (pzSfxFmt != NULL) {
size_t sfx_len = pzSfxFmt - pzData;
pOS->zSuffix[sfx_len++] = NUL;
pOS->pzFileFmt = pOS->zSuffix + sfx_len;
if (*pOS->pzFileFmt == '(') {
SCM str =
ag_scm_c_eval_string_from_file_line(
pOS->pzFileFmt, pzFileName, lineNo );
size_t str_size;
pzSfxFmt = resolveSCM( str );
str_size = strlen(pzSfxFmt);
if (str_size == 0)
AG_ABEND(zEmptySpec);
if (strspn(pzSfxFmt, zSuffixSpecChars) != str_size)
AG_ABEND(aprf("invalid file name chars in suffix: %s",
pzSfxFmt));
/*
* IF the scheme replacement text fits in the space, don't
* mess with allocating another string.
*/
if (str_size < spn - sfx_len)
strcpy(pOS->zSuffix + sfx_len, pzSfxFmt);
else
AGDUPSTR(pOS->pzFileFmt, pzSfxFmt, "suffix format");
}
} else {
/*
* IF the suffix does not start with punctuation,
* THEN we will insert a '.' of our own.
*/
if (isalnum( pOS->zSuffix[0] ))
pOS->pzFileFmt = zFileFormat + 5;
else pOS->pzFileFmt = zFileFormat;
}
return pzResult;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* findTokenType
*
* Skiping leading white space, figure out what sort of token is under
* the scan pointer (pzData).
*/
static te_pm_event
findTokenType( tCC** ppzData, te_pm_state fsm_state )
{
tCC* pzData = *ppzData;
/*
* At the start of processing in this function, we can never be at
* the "start of a line". A '#' type comment before the initial
* start macro marker is illegal. Otherwise, our scan pointer is
* after some valid token, which won't be the start of a line, either.
*/
ag_bool line_start = AG_FALSE;
skipWhiteSpace:
while (isspace( *pzData )) {
if (*(pzData++) == '\n') {
line_start = AG_TRUE;
templLineNo++;
/*
* IF we are done with the macro markers,
* THEN we skip white space only thru the first new line.
*/
if (fsm_state == PM_ST_END_MARK) {
*ppzData = pzData;
return PM_EV_END_PSEUDO;
}
}
}
if (line_start && (*pzData == '#')) {
pzData = strchr( pzData, '\n' );
if (pzData == NULL)
AG_ABEND( "Invalid template file" );
goto skipWhiteSpace;
}
*ppzData = pzData; /* in case we return */
/*
* After the end marker has been found,
* anything else is really the start of the data.
*/
if (fsm_state == PM_ST_END_MARK)
return PM_EV_END_PSEUDO;
/*
* IF the token starts with an alphanumeric,
* THEN it must be "autogen5" or "template" or a suffix specification
*/
if (isalnum( *pzData )) {
if (strneqvcmp( pzData, zAgName, (int)sizeof(zAgName)-1 ) == 0) {
if (isspace( pzData[ sizeof(zAgName)-1 ] )) {
*ppzData = pzData + sizeof(zAgName);
return PM_EV_AUTOGEN;
}
return PM_EV_SUFFIX;
}
if (strneqvcmp( pzData, zTpName, (int)sizeof(zTpName)-1 ) == 0) {
if (isspace( pzData[ sizeof(zTpName)-1 ] )) {
*ppzData = pzData + sizeof(zTpName)-1;
return PM_EV_TEMPLATE;
}
return PM_EV_SUFFIX;
}
return PM_EV_SUFFIX;
}
/*
* Several transition tokens are enabled once
* the "template" keyword has been processed.
*/
if (fsm_state == PM_ST_TEMPL) {
switch (*pzData) {
case '-':
if ((pzData[1] == '*') && (pzData[2] == '-'))
return PM_EV_ED_MODE;
/* FALLTHROUGH */
case '.':
case '_':
return PM_EV_SUFFIX;
case '(':
return PM_EV_SCHEME;
}
}
/*
* IF the next sequence is "-*-"
* THEN we found an edit mode marker
*/
if (strncmp(pzData, "-*-", (size_t)3) == 0)
return PM_EV_ED_MODE;
/*
* IF it is some other punctuation,
* THEN it must be a start/end marker.
*/
if (ispunct( *pzData ))
return PM_EV_MARKER;
/*
* Otherwise, it is just junk.
*/
AG_ABEND( "Invalid template file" );
/* NOTREACHED */
return PM_EV_INVALID;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* copyMarker
*
* Some sort of marker is under the scan pointer. Copy it for as long
* as we find punctuation characters.
*/
static tCC*
copyMarker( tCC* pzData, char* pzMark, size_t * pCt )
{
int ct = 0;
for (;;) {
char ch = *pzData;
if (! ispunct( ch ))
break;
*(pzMark++) = ch;
if (++ct >= sizeof( zStartMac ))
return NULL;
pzData++;
}
*pCt = ct;
*pzMark = NUL;
return pzData;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* loadPseudoMacro
*
* Using a finite state machine, scan over the tokens that make up the
* "pseudo macro" at the start of every template.
*/
LOCAL tCC*
loadPseudoMacro( tCC* pzData, tCC* pzFileName )
{
tSCC zMarkErr[] = "start/end macro mark too long";
tSCC zBadMark[] = "bad template marker in %s on line %d:\n\t%s";
tCC* pzBadness;
# define BAD_MARKER( t ) { pzBadness = t; goto abort_load; }
te_pm_state fsm_state = PM_ST_INIT;
templLineNo = 1;
while (fsm_state != PM_ST_DONE) {
te_pm_event fsm_tkn = findTokenType( &pzData, fsm_state );
te_pm_state nxt_state;
te_pm_trans trans;
nxt_state = pm_trans_table[ fsm_state ][ fsm_tkn ].next_state;
trans = pm_trans_table[ fsm_state ][ fsm_tkn ].transition;
/*
* There are only so many "PM_TR__"
* transitions that are legal. See which one we got.
* It is legal to alter "nxt_state" while processing these.
*/
switch (trans) {
case PM_TR_SKIP_ED_MODE:
{
char* pzEnd = strstr( pzData + 3, "-*-" );
char* pzNL = strchr( pzData + 3, '\n' );
if ((pzEnd == NULL) || (pzNL < pzEnd))
BAD_MARKER( "invalid edit mode marker" );
pzData = pzEnd + 3;
break;
}
case PM_TR_INIT_MARKER:
pzData = copyMarker( pzData, zStartMac, &startMacLen );
if (pzData == NULL)
BAD_MARKER( zMarkErr );
break;
case PM_TR_TEMPL_MARKER:
pzData = copyMarker( pzData, zEndMac, &endMacLen );
if (pzData == NULL)
BAD_MARKER( zMarkErr );
/*
* IF the end macro seems to end with the start macro and
* it is exactly twice as long as the start macro, then
* presume that someone ran the two markers together.
*/
if ( (endMacLen == 2 * startMacLen)
&& (strcmp( zStartMac, zEndMac + startMacLen ) == 0)) {
pzData -= startMacLen;
zEndMac[ startMacLen ] = NUL;
endMacLen = startMacLen;
}
if (strstr( zEndMac, zStartMac ) != NULL)
BAD_MARKER( "start marker contained in end marker" );
if (strstr( zStartMac, zEndMac ) != NULL)
BAD_MARKER( "end marker contained in start marker" );
break;
case PM_TR_TEMPL_SUFFIX:
pzData = doSuffixSpec( pzData, pzFileName, templLineNo );
break;
case PM_TR_TEMPL_SCHEME:
pzData = doSchemeExpr( pzData, pzFileName );
break;
case PM_TR_INVALID:
pm_invalid_transition( fsm_state, fsm_tkn );
switch (fsm_state) {
case PM_ST_INIT: BAD_MARKER( "need start marker" );
case PM_ST_ST_MARK: BAD_MARKER( "need autogen5 marker" );
case PM_ST_AGEN: BAD_MARKER( "need template marker" );
case PM_ST_TEMPL: BAD_MARKER( "need end marker" );
case PM_ST_END_MARK: BAD_MARKER( "need end of line" );
default: BAD_MARKER( "BROKEN FSM STATE" );
}
case PM_TR_NOOP:
break;
default:
BAD_MARKER( "broken pseudo-macro FSM" );
}
fsm_state = nxt_state;
}
/*
* It is possible that the template writer specified a shell to use.
* If the server shell has not already started, we'll catch it later.
* If it has started, then check for a shell change & shut it down
* if it has been changed.
*/
if (serverArgs[0] != NULL) {
char* pz = getenv( zShellEnv );
if ((pz != NULL) && (strcmp( pz, serverArgs[0] ) != 0)) {
fprintf( pfTrace, "Changing server shell from %s to %s\n",
serverArgs[0], pz );
closeServer();
serverArgs[0] = pz;
pzShellProgram = pz;
}
}
return pzData;
abort_load:
AG_ABEND( aprf( zBadMark, pzFileName, templLineNo, pzBadness ));
# undef BAD_MARKER
return NULL;
}
/*
* Local Variables:
* mode: C
* c-file-style: "stroustrup"
* indent-tabs-mode: nil
* End:
* end of agen5/loadPseudo.c */