/*=gfunc extract * * what: extract text from another file * general_use: * exparg: file-name, name of file with text * exparg: marker-fmt, format for marker text * exparg: caveat, warn about changing marker, opt * exparg: default, default initial text, opt * * doc: * * This function is used to help construct output files that may contain * text that is carried from one version of the output to the next. * * The first two arguments are required, the second are optional: * * @itemize @bullet * @item * The @code{file-name} argument is used to name the file that * contains the demarcated text. * @item * The @code{marker-fmt} is a formatting string that is used to construct * the starting and ending demarcation strings. The sprintf function is * given the @code{marker-fmt} with two arguments. The first is either * "START" or "END". The second is either "DO NOT CHANGE THIS COMMENT" * or the optional @code{caveat} argument. * @item * @code{caveat} is presumed to be absent if it is the empty string * (@code{""}). If absent, ``DO NOT CHANGE THIS COMMENT'' is used * as the second string argument to the @code{marker-fmt}. * @item * When a @code{default} argument is supplied and no pre-existing text * is found, then this text will be inserted between the START and END * markers. * @end itemize * * @noindent * The resulting strings are presumed to be unique within * the subject file. As a simplified example: * * @example * [+ (extract "fname" "// %s - SOMETHING - %s" "" * "example default") +] * @end example * @noindent * will result in the following text being inserted into the output: * * @example * // START - SOMETHING - DO NOT CHANGE THIS COMMENT * example default * // END - SOMETHING - DO NOT CHANGE THIS COMMENT * @end example * * @noindent * The ``@code{example default}'' string can then be carried forward to * the next generation of the output, @strong{@i{provided}} the output * is not named "@code{fname}" @i{and} the old output is renamed to * "@code{fname}" before AutoGen-eration begins. * * @table @strong * @item NB: * You can set aside previously generated source files inside the pseudo * macro with a Guile/scheme function, extract the text you want to keep * with this extract function. Just remember you should delete it at the * end, too. Here is an example from my Finite State Machine generator: * * @example * [+ AutoGen5 Template -*- Mode: text -*- * h=%s-fsm.h c=%s-fsm.c * (shellf * "test -f %1$s-fsm.h && mv -f %1$s-fsm.h .fsm.head * test -f %1$s-fsm.c && mv -f %1$s-fsm.c .fsm.code" (base-name)) * +] * @end example * * This code will move the two previously produced output files to files * named ".fsm.head" and ".fsm.code". At the end of the 'c' output * processing, I delete them. * * @item also NB: * This function presumes that the output file ought to be editable so * that the code between the @code{START} and @code{END} marks can be edited * by the template user. Consequently, when the @code{(extract ...)} function * is invoked, if the @code{writable} option has not been specified, then * it will be set at that point. If this is not the desired behavior, the * @code{--not-writable} command line option will override this. * Also, you may use the guile function @code{(chmod "file" mode-value)} * to override whatever AutoGen is using for the result mode. * @end table =*/ SCM ag_scm_extract(SCM file, SCM marker, SCM caveat, SCM def) { char const * pzStart; char const * pzEnd; char const * pzText; if (! AG_SCM_STRING_P(file) || ! AG_SCM_STRING_P(marker)) return SCM_UNDEFINED; pzText = load_extract_file(ag_scm2zchars(file, "extr file")); { char const * pzMarker = ag_scm2zchars(marker, "marker"); char const * pzCaveat = EXTRACT_CAVEAT; if (AG_SCM_STRING_P(caveat) && (AG_SCM_STRLEN(caveat) > 0)) pzCaveat = ag_scm2zchars(caveat, "caveat"); pzStart = aprf(pzMarker, EXTRACT_START, pzCaveat); pzEnd = aprf(pzMarker, EXTRACT_END, pzCaveat); } { SCM res; if (pzText == NULL) res = mk_empty_text(pzStart, pzEnd, def); else res = get_text(pzText, pzStart, pzEnd, def); AGFREE((void*)pzStart); AGFREE((void*)pzEnd); return res; } }
LOCAL void yyerror(char* s) { static char const yyerr_fmt[] = "%s: in %s on line %d\n" "\ttoken in error: %s\n" "\t[[...<error-text>]] %s\n\n" "Likely causes: a mismatched quote, a value that needs " "quoting,\n\t\tor a missing semi-colon\n"; static char const zErrTkn[] = "%s: ``%s''\n"; static char const zDf[] = "`%s'\n"; char* pz; if (strlen(pCurCtx->pzScan) > 64 ) pCurCtx->pzScan[64] = NUL; switch (lastToken) { case DP_EV_VAR_NAME: case DP_EV_OTHER_NAME: case DP_EV_STRING: case DP_EV_NUMBER: if (strlen(pz_token) > 64 ) pz_token[64] = NUL; pz = aprf(zErrTkn, DP_EVT_NAME(lastToken), pz_token); break; default: pz = aprf(zDf, DP_EVT_NAME(lastToken)); } AG_ABEND(aprf(yyerr_fmt, s, pCurCtx->pzCtxFname, pCurCtx->lineNo, pz, pCurCtx->pzScan)); }
/** * Print out an invalid transition message and return EXIT_FAILURE */ static int cgi_invalid_transition( te_cgi_state st, te_cgi_event evt ) { /* START == INVALID TRANS MSG == DO NOT CHANGE THIS COMMENT */ char * pz = aprf( zCgiStrings + CgiFsmErr_off, st, CGI_STATE_NAME( st ), evt, CGI_EVT_NAME( evt )); AG_ABEND( aprf(CGI_PARSE_ERR_FMT, pz )); /* END == INVALID TRANS MSG == DO NOT CHANGE THIS COMMENT */ return EXIT_FAILURE; }
/** * Add a system name to the environment. The input name is up-cased and * made to conform to environment variable names. If not already in the * environment, it is added with the string value "1". * * @param env_name in/out: system name to export */ static void add_sys_env(char * env_name) { int i = 2; for (;;) { if (IS_UPPER_CASE_CHAR(env_name[i])) env_name[i] = (char)tolower(env_name[i]); else if (! IS_ALPHANUMERIC_CHAR(env_name[i])) env_name[i] = '_'; if (env_name[ ++i ] == NUL) break; } /* * If the user already has something in the environment, do not * override it. */ if (getenv(env_name) == NULL) { char * pz; if (OPT_VALUE_TRACE > TRACE_DEBUG_MESSAGE) fprintf(trace_fp, TRACE_ADD_TO_ENV_FMT, env_name); pz = aprf(ADD_SYS_ENV_VAL_FMT, env_name); putenv(pz); } }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Support routines for the directives * * skipToEndif * * Skip through the text to a matching "#endif". We do this when we * have processed the allowable text (found an "#else" after * accepting the preceeding text) or when encountering a "#if*def" * while skipping a block of text due to a failed test. */ static char* skipToEndif(char* pzStart) { char* pzScan = pzStart; char* pzRet; for (;;) { /* * 'pzScan' is pointing to the first character on a line. * Check for a directive on the current line before scanning * later lines. */ if (*pzScan == '#') pzScan++; else { char* pz = strstr(pzScan, zCheckList); if (pz == NULL) AG_ABEND(aprf(zNoEndif, pCurCtx->pzCtxFname, pCurCtx->lineNo)); pzScan = pz + STRSIZE(zCheckList); } while (IS_WHITESPACE_CHAR(*pzScan)) pzScan++; switch (findDirective(pzScan)) { case DIR_ENDIF: { /* * We found the endif we are interested in */ char* pz = strchr(pzScan, NL); if (pz != NULL) pzRet = pz+1; else pzRet = pzScan + strlen(pzScan); goto leave_func; } case DIR_IFDEF: case DIR_IFNDEF: /* * We found a nested ifdef/ifndef */ pzScan = skipToEndif(pzScan); break; default: /* * We do not care what we found */ break; /* ignore it */ } /* switch (findDirective(pzScan)) */ } leave_func: while (pzStart < pzRet) { if (*(pzStart++) == NL) pCurCtx->lineNo++; } return pzRet; }
/** * Some functions are known to AutoGen, but invalid out of context. * For example, ELIF, ELSE and ENDIF are all known to AutoGen. * However, the load function pointer for those functions points * here, until an "IF" function is encountered. */ tMacro* mLoad_Bogus(tTemplate* pT, tMacro* pMac, char const ** ppzScan) { char const * pzSrc = (char const*)pMac->ozText; /* macro text */ char const * pzMac; char z[ 64 ]; if (pzSrc != NULL) { z[0] = ':'; z[1] = z[2] = ' '; strncpy(z+3, pzSrc, (size_t)60); z[63] = NUL; pzSrc = z; } else pzSrc = zNil; { int ix = pMac->funcCode; if ((unsigned)ix >= FUNC_CT) ix = 0; pzMac = apzFuncNames[ ix ]; } pzSrc = aprf(LD_BOGUS_UNKNOWN, pT->pzTplFile, pMac->lineNo, pzMac, pzSrc); AG_ABEND_IN(pT, pMac, pzSrc); /* NOTREACHED */ return NULL; }
static void check_assert_str(char const * pz, char const * arg) { pz = SPN_WHITESPACE_CHARS(pz); if (IS_FALSE_TYPE_CHAR(*pz)) AG_ABEND(aprf(DIRECT_ASSERT_FMT, pz, arg)); }
/*=directive error * * arg: [ <descriptive text> ] * * text: * This directive will cause AutoGen to stop processing * and exit with a status of EXIT_FAILURE. =*/ static char* doDir_error(char* pzArg, char* pzScan) { AG_ABEND(aprf("#error directive -- in %s on line %d\n\t%s\n", pCurCtx->pzCtxFname, pCurCtx->lineNo, pzArg)); /* NOTREACHED */ return NULL; }
/** * Starting with the current directory, search the directory * list trying to find the base template file name. */ LOCAL tTemplate * loadTemplate(char const * pzFileName, char const * referrer) { static tmap_info_t mapInfo; static char zRealFile[ AG_PATH_MAX ]; /* * Find the template file somewhere */ { static char const * const apzSfx[] = { "tpl", "agl", NULL }; if (! SUCCESSFUL(findFile(pzFileName, zRealFile, apzSfx, referrer))) { errno = ENOENT; AG_CANT("map data file", pzFileName); } } /* * Make sure the specified file is a regular file. * Make sure the output time stamp is at least as recent. */ { struct stat stbf; if (stat(zRealFile, &stbf) != 0) AG_CANT("stat file", pzFileName); if (! S_ISREG(stbf.st_mode)) { errno = EINVAL; AG_CANT("not regular file", pzFileName); } if (outTime <= stbf.st_mtime) outTime = stbf.st_mtime + 1; } text_mmap(zRealFile, PROT_READ|PROT_WRITE, MAP_PRIVATE, &mapInfo); if (TEXT_MMAP_FAILED_ADDR(mapInfo.txt_data)) AG_ABEND(aprf("Could not open template '%s'", zRealFile)); if (pfDepends != NULL) append_source_name(zRealFile); /* * Process the leading pseudo-macro. The template proper * starts immediately after it. */ { tMacro * pSaveMac = pCurMacro; tTemplate * pRes; pCurMacro = NULL; pRes = digest_pseudo_macro(&mapInfo, zRealFile); pCurMacro = pSaveMac; text_munmap(&mapInfo); return pRes; } }
/*=directive elif * * text: * This must follow an @code{#if} * otherwise it will generate an error. * It will be ignored. =*/ static char* doDir_elif(char* pzArg, char* pzScan) { static char const z[] = "`#elif' directive encountered out of context\n\tin %s on line %d\n"; AG_ABEND(aprf(z, pCurCtx->pzCtxFname, pCurCtx->lineNo)); /* NOTREACHED */ return NULL; }
/** * This directive will cause AutoGen to stop processing * and exit with a status of EXIT_FAILURE. */ char * doDir_error(directive_enum_t id, char const * arg, char * scan_next) { arg = SPN_WHITESPACE_CHARS(arg); AG_ABEND(aprf(DIRECT_ERROR_FMT, cctx->scx_fname, cctx->scx_line, arg)); /* NOTREACHED */ (void)scan_next; (void)id; return NULL; }
/*=directive assert * * arg: `shell-script` | (scheme-expr) | <anything else> * * text: * If the @code{shell-script} or @code{scheme-expr} do not yield @code{true} * valued results, autogen will be aborted. If @code{<anything else>} or * nothing at all is provided, then this directive is ignored. * * When writing the shell script, remember this is on a preprocessing * line. Multiple lines must be backslash continued and the result is a * single long line. Separate multiple commands with semi-colons. * * The result is @code{false} (and fails) if the result is empty, the * number zero, or a string that starts with the letters 'n' or 'f' ("no" * or "false"). =*/ static void check_assert_str(char const* pz, char const* pzArg) { static char const fmt[] = "#assert yielded \"%s\":\n\t`%s`"; while (IS_WHITESPACE_CHAR(*pz)) pz++; if (IS_FALSE_TYPE_CHAR(*pz)) AG_ABEND(aprf(fmt, pz, pzArg)); }
/** * @code{#endshell}, @code{#elif} and @code{#endmac} directives, when found * via a non-nested scan, are always in error. They should only be found * during the handling of @code{#shell}, @code{#if} and @code{#macdef} * directives. @code{#else} and @code{#endif} are in error if not matched * with a previous @code{#ifdef} or @code{#ifndef} directive. */ static char * bad_dirv_ctx(directive_enum_t id, char const * arg, char * scan_next) { AG_ABEND(aprf(DIRECT_BAD_CTX_FMT, directive_name(id), cctx->scx_fname, cctx->scx_line)); (void)arg; (void)id; /* NOTREACHED */ return scan_next; }
/*=macfunc BOGUS * * what: Out-of-context or unknown functions are bogus. * handler_proc: * load_proc: * unnamed: =*/ tMacro* mFunc_Bogus(tTemplate* pT, tMacro* pMac) { char * pz = aprf(FN_BOGUS_FMT, pMac->funcCode, (pMac->funcCode < FUNC_CT) ? apzFuncNames[ pMac->funcCode ] : FN_BOGUS_HUH); AG_ABEND_IN(pT, pMac, pz); /* NOTREACHED */ return pMac; }
static void init_scm(void) { last_scm_cmd = SCHEME_INIT_TEXT; { SCM ini_res = ag_scm_c_eval_string_from_file_line( SCHEME_INIT_TEXT, AG_TEXT_STRTABLE_FILE, SCHEME_INIT_TEXT_LINENO); AGDUPSTR(libguile_ver, scm2display(ini_res), "ini res"); } { unsigned int maj, min, mic; switch (sscanf(libguile_ver, "%u.%u.%u", &maj, &min, &mic)) { case 2: case 3: break; default: AG_ABEND(aprf(GUILE_VERSION_BAD, libguile_ver)); /* NOT_REACHED */ } maj = min + (100 * maj); if ((GUILE_VERSION / 1000) != maj) AG_ABEND(aprf(GUILE_VERSION_WRONG, libguile_ver, MK_STR(GUILE_VERSION))); } { # if GUILE_VERSION >= 200000 # define SCHEME_INIT_DEBUG SCHEME_INIT_DEBUG_2_0 # else # define SCHEME_INIT_DEBUG SCHEME_INIT_DEBUG_1_6 # endif char * p = aprf(INIT_SCM_ERRS_FMT, SCHEME_INIT_DEBUG); # undef SCHEME_INIT_DEBUG last_scm_cmd = p; ag_scm_c_eval_string_from_file_line(p, __FILE__, __LINE__); AGFREE(p); } }
static void loadScheme(void) { char* pzText = pCurCtx->pzScan; char* pzEnd = (char*)skipScheme(pzText, pzText + strlen(pzText)); char endCh = *pzEnd; int schemeLen = (pzEnd - pzText); int next_ln; SCM res; /* * NUL terminate the Scheme expression, run it, then restore * the NUL-ed character. */ if (*pzEnd == NUL) AG_ABEND(aprf(zErrMsg, pzProg, "end of Guile/scheme expression not found", pCurCtx->pzCtxFname, pCurCtx->lineNo)); *pzEnd = NUL; next_ln = pCurCtx->lineNo + count_nl(pzText); procState = PROC_STATE_GUILE_PRELOAD; res = ag_scm_c_eval_string_from_file_line( pzText, pCurCtx->pzCtxFname, pCurCtx->lineNo ); procState = PROC_STATE_LOAD_DEFS; *pzEnd = endCh; pCurCtx->pzScan = pzEnd; pzEnd = (char*)resolveSCM(res); /* ignore const-ness */ pCurCtx->lineNo = next_ln; if (strlen(pzEnd) >= schemeLen) { AGDUPSTR(pzEnd, pzEnd, "SCM Result"); pz_token = pzEnd; manageAllocatedData(pz_token); } else { /* * We know the result is smaller than the source. Copy in place. */ strcpy(pzText, pzEnd); pz_token = pzText; } lastToken = DP_EV_STRING; }
static char * next_directive(char * scan) { if (*scan == '#') scan++; else { char * pz = strstr(scan, DIRECT_CK_LIST_MARK); if (pz == NULL) AG_ABEND(aprf(DIRECT_NOENDIF_FMT, cctx->scx_fname, cctx->scx_line)); scan = pz + 2; } return SPN_WHITESPACE_CHARS(scan); }
static char* skipToEndmac(char* pzStart) { char* pzScan = pzStart; char* pzRet; for (;;) { /* * 'pzScan' is pointing to the first character on a line. * Check for a directive on the current line before scanning * later lines. */ if (*pzScan == '#') pzScan++; else { char* pz = strstr(pzScan, zCheckList); if (pz == NULL) AG_ABEND(aprf(zNoEndif, pCurCtx->pzCtxFname, pCurCtx->lineNo)); pzScan = pz + STRSIZE(zCheckList); } while (IS_WHITESPACE_CHAR(*pzScan)) pzScan++; if (findDirective(pzScan) == DIR_ENDMAC) { /* * We found the endmac we are interested in */ char* pz = strchr(pzScan, NL); if (pz != NULL) pzRet = pz+1; else pzRet = pzScan + strlen(pzScan); break; } } while (pzStart < pzRet) { if (*(pzStart++) == NL) pCurCtx->lineNo++; } return pzRet; }
/*=directive shell * * text: * Invokes @code{$SHELL} or @file{/bin/sh} on a script that should * generate AutoGen definitions. It does this using the same server * process that handles the back-quoted @code{`} text. * @strong{CAUTION}@: let not your @code{$SHELL} be @code{csh}. =*/ static char* doDir_shell(char* pzArg, char* pzScan) { static char const zShellText[] = "Computed Definitions"; static char const zEndShell[] = "\n#endshell"; tScanCtx* pCtx; char* pzText = pzScan; /* * The output time will always be the current time. * The dynamic content is always current :) */ outTime = time(NULL); /* * IF there are no data after the '#shell' directive, * THEN we won't write any data * ELSE we have to find the end of the data. */ if (strncmp(pzText, zEndShell+1, STRSIZE(zEndShell)-1) == 0) return pzScan; { static char const noend[] = "Missing #endshell after '#shell' in %s on line %d\n"; char* pz = strstr(pzScan, zEndShell); if (pz == NULL) AG_ABEND(aprf(noend, pCurCtx->pzCtxFname, pCurCtx->lineNo)); while (pzScan < pz) { if (*(pzScan++) == NL) pCurCtx->lineNo++; } *pzScan = NUL; } /* * Advance the scan pointer to the next line after '#endshell' * IF there is no such line, * THEN the scan will resume on a zero-length string. */ pzScan = strchr(pzScan + STRSIZE(zEndShell), NL); if (pzScan == NULL) pzScan = (void*)zNil; /* * Save the scan pointer into the current context */ pCurCtx->pzScan = pzScan; /* * Run the shell command. The output text becomes the * "file text" that is used for more definitions. */ pzText = runShell(pzText); if (pzText == NULL) return pzScan; if (*pzText == NUL) { AGFREE(pzText); return pzScan; } /* * Get the space for the output data and for context overhead. * This is an extra allocation and copy, but easier than rewriting * 'loadData()' for this special context. */ pCtx = (tScanCtx*)AGALOC(sizeof(tScanCtx) + strlen(pzText) + 4, "shell output"); /* * Link the new scan data into the context stack */ pCtx->pCtx = pCurCtx; pCurCtx = pCtx; /* * Set up the rest of the context structure */ AGDUPSTR(pCtx->pzCtxFname, zShellText, "shell text"); pCtx->pzScan = pCtx->pzData = (char*)(pCtx+1); pCtx->lineNo = 0; strcpy(pCtx->pzScan, pzText); AGFREE(pzText); return pCtx->pzScan; }
/** * Figure out what to use as the base name of the output file. * If an argument is not provided, we use the base name of * the definitions file. */ static void open_output(tOutSpec * spec) { static char const write_mode[] = "w" FOPEN_BINARY_FLAG "+"; char const * out_file = NULL; if (strcmp(spec->zSuffix, OPEN_OUTPUT_NULL) == 0) { static int const flags = FPF_NOUNLINK | FPF_NOCHMOD | FPF_TEMPFILE; null_open: open_output_file(DEV_NULL, DEV_NULL_LEN, write_mode, flags); return; } /* * IF we are to skip the current suffix, * we will redirect the output to /dev/null and * perform all the work. There may be side effects. */ if (HAVE_OPT(SKIP_SUFFIX)) { int ct = STACKCT_OPT(SKIP_SUFFIX); const char ** ppz = STACKLST_OPT(SKIP_SUFFIX); while (--ct >= 0) { if (strcmp(spec->zSuffix, *ppz++) == 0) goto null_open; } } /* * Remove any suffixes in the last file name */ { char const * def_file = OPT_ARG(BASE_NAME); char z[AG_PATH_MAX]; const char * pst = strrchr(def_file, '/'); char * end; pst = (pst == NULL) ? def_file : (pst + 1); /* * We allow users to specify a suffix with '-' and '_', but when * stripping a suffix from the "base name", we do not recognize 'em. */ end = strchr(pst, '.'); if (end != NULL) { size_t len = (unsigned)(end - pst); if (len >= sizeof(z)) AG_ABEND("--base-name name is too long"); memcpy(z, pst, len); z[ end - pst ] = NUL; pst = z; } /* * Now formulate the output file name in the buffer * provided as the input argument. */ out_file = aprf(spec->pzFileFmt, pst, spec->zSuffix); if (out_file == NULL) AG_ABEND(aprf(OPEN_OUTPUT_BAD_FMT, spec->pzFileFmt, pst, spec->zSuffix)); } open_output_file(out_file, strlen(out_file), write_mode, 0); free((void *)out_file); }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * A quoted string has been found. * Find the end of it and compress any escape sequences. */ static char* assembleHereString(char* pzScan) { static char const endless[] = "Unterminated HereString"; ag_bool trimTabs = AG_FALSE; char zMark[ MAX_HEREMARK_LEN ]; size_t markLen = 0; char* pzDest; int here_string_line_no; /* * See if we are to strip leading tab chars */ if (*pzScan == '-') { trimTabs = AG_TRUE; pzScan++; } /* * Skip white space up to the marker or EOL */ while (IS_WHITESPACE_CHAR(*pzScan)) { if (*pzScan++ == NL) AG_ABEND(aprf(zErrMsg, pzProg, "HereString missing the mark", pCurCtx->pzCtxFname, pCurCtx->lineNo)); } /* * Copy the marker, noting its length */ { char* pz = zMark; while (IS_VARIABLE_NAME_CHAR(*pzScan)) { if (++markLen >= sizeof(zMark)) AG_ABEND(aprf(zErrMsg, pzProg, "HereString mark " STR(MAX_HEREMARK_LEN) " or more chars", pCurCtx->pzCtxFname, pCurCtx->lineNo)); *(pz++) = *(pzScan++); } if (markLen == 0) AG_ABEND(aprf(zErrMsg, pzProg, "HereString missing the mark", pCurCtx->pzCtxFname, pCurCtx->lineNo)); *pz = NUL; } pzDest = pzScan; pz_token = pzDest; /* * Skip forward to the EOL after the marker. */ pzScan = strchr(pzScan, NL); if (pzScan == NULL) AG_ABEND(aprf(zErrMsg, pzProg, endless, pCurCtx->pzCtxFname, pCurCtx->lineNo)); /* * And skip the first new line + conditionally skip tabs */ here_string_line_no = pCurCtx->lineNo++; pzScan++; if (trimTabs) while (*pzScan == TAB) ++pzScan; /* * FOR as long as the text does not match the mark * OR it matches but is a substring * DO copy characters */ while ( (strncmp(pzScan, zMark, markLen) != 0) || IS_VARIABLE_NAME_CHAR(pzScan[markLen]) ) { for (;;) { switch (*(pzDest++) = *(pzScan++)) { case NL: pCurCtx->lineNo++; goto lineDone; case NUL: AG_ABEND(aprf(zErrMsg, pzProg, endless, pCurCtx->pzCtxFname, here_string_line_no)); } } lineDone:; if (trimTabs) while (*pzScan == TAB) ++pzScan; } /* while strncmp ... */ /* * pzDest may still equal pz_token, if no data were copied */ if (pzDest > (char*)pz_token) pzDest[-1] = NUL; else pzDest[0] = NUL; return pzScan + markLen; }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * It may be a number, a name, a keyword or garbage. * Figure out which. */ static char* assembleName(char * pzScan, te_dp_event * pRetVal) { /* * Check for a number. * Scan it in and advance "pzScan". */ if ( IS_DEC_DIGIT_CHAR(*pzScan) || ( (*pzScan == '-') && IS_DEC_DIGIT_CHAR(pzScan[1]) ) ) { pz_token = pzScan; (void)strtol(pzScan, &pzScan, 0); *pRetVal = DP_EV_NUMBER; return pzScan; } if (! IS_UNQUOTABLE_CHAR(*pzScan)) AG_ABEND(aprf("%s Error: Invalid input char '%c' in %s on line %d\n", pzProg, *pzScan, pCurCtx->pzCtxFname, pCurCtx->lineNo)); { unsigned char* pz = (unsigned char*)pzScan; while (IS_VALUE_NAME_CHAR(*pz)) pz++; if (IS_UNQUOTABLE_CHAR(*pz)) { *pRetVal = DP_EV_OTHER_NAME; while (IS_UNQUOTABLE_CHAR(*++pz)) ; } else *pRetVal = DP_EV_VAR_NAME; /* * Return a NAME token, maybe. * If the name is actually a keyword, * we will return that token code instead. */ pz_token = pzScan; pzScan = (char*)pz; } /* * Now scan the keyword table. */ if (*pRetVal == DP_EV_VAR_NAME) { char sv_ch = *pzScan; /* preserve the following character */ int kw_ix = 0; *pzScan = NUL; /* NUL terminate the name */ do { if (streqvcmp(apzKeywords[ kw_ix ], (char*)pz_token) == 0) { /* * Return the keyword token code instead of DP_EV_NAME */ *pRetVal = aKeywordTkn[ kw_ix ]; break; } } while (++kw_ix < KEYWORD_CT); *pzScan = sv_ch; /* restore the following character */ } return pzScan; }
/** * Invokes @code{$SHELL} or @file{/bin/sh} on a script that should * generate AutoGen definitions. It does this using the same server * process that handles the back-quoted @code{`} text. * The block of text handed to the shell is terminated with * the #endshell directive. * * @strong{CAUTION}@: let not your @code{$SHELL} be @code{csh}. */ char * doDir_shell(directive_enum_t id, char const * arg, char * scan_next) { static size_t const endshell_len = sizeof("\n#endshell") - 1; scan_ctx_t * pCtx; char * pzText = scan_next; (void)arg; (void)id; /* * The output time will always be the current time. * The dynamic content is always current :) */ maxfile_time = outfile_time = time(NULL); /* * IF there are no data after the '#shell' directive, * THEN we won't write any data * ELSE we have to find the end of the data. */ if (strncmp(pzText, DIRECT_SHELL_END_SHELL+1, endshell_len-1) == 0) return scan_next; { char * pz = strstr(scan_next, DIRECT_SHELL_END_SHELL); if (pz == NULL) AG_ABEND(aprf(DIRECT_SHELL_NOEND, cctx->scx_fname, cctx->scx_line)); while (scan_next < pz) { if (*(scan_next++) == NL) cctx->scx_line++; } *scan_next = NUL; } /* * Advance the scan pointer to the next line after '#endshell' * IF there is no such line, * THEN the scan will resume on a zero-length string. */ scan_next = strchr(scan_next + endshell_len, NL); if (scan_next == NULL) scan_next = VOIDP(zNil); /* * Save the scan pointer into the current context */ cctx->scx_scan = scan_next; /* * Run the shell command. The output text becomes the * "file text" that is used for more definitions. */ pzText = shell_cmd(pzText); if (pzText == NULL) return scan_next; if (*pzText == NUL) { AGFREE(pzText); return scan_next; } /* * Get the space for the output data and for context overhead. * This is an extra allocation and copy, but easier than rewriting * 'loadData()' for this special context. */ pCtx = (scan_ctx_t *)AGALOC(sizeof(scan_ctx_t) + strlen(pzText) + 4, "shell output"); /* * Link the new scan data into the context stack */ pCtx->scx_next = cctx; cctx = pCtx; /* * Set up the rest of the context structure */ AGDUPSTR(pCtx->scx_fname, DIRECT_SHELL_COMP_DEFS, DIRECT_SHELL_COMP_DEFS); pCtx->scx_scan = pCtx->scx_data = (char *)(pCtx + 1); pCtx->scx_line = 0; strcpy(pCtx->scx_scan, pzText); AGFREE(pzText); return pCtx->scx_scan; }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LEXICAL SCANNER */ LOCAL te_dp_event yylex(void) { lastToken = DP_EV_INVALID; scanAgain: /* * Start the process of locating a token. * We branch here after skipping over a comment * or processing a directive (which may change our context). */ if (IS_WHITESPACE_CHAR(*pCurCtx->pzScan)) trim_whitespace(); switch (*pCurCtx->pzScan) { case NUL: /* * IF we are not inside an include context, * THEN go finish. */ if (pCurCtx->pCtx == NULL) goto lex_done; pop_context(); goto scanAgain; case '#': { extern char * processDirective(char*); char * pz = processDirective(pCurCtx->pzScan+1); /* * Ensure that the compiler doesn't try to save a copy of * "pCurCtx" in a register. It must be reloaded from memory. */ pCurCtx->pzScan = pz; goto scanAgain; } case '{': SET_LIT_TKN(O_BRACE); break; case '=': SET_LIT_TKN(EQ); break; case '}': SET_LIT_TKN(C_BRACE); break; case '[': SET_LIT_TKN(OPEN_BKT); break; case ']': SET_LIT_TKN(CLOSE_BKT); break; case ';': SET_LIT_TKN(SEMI); break; case ',': SET_LIT_TKN(COMMA); break; case '\'': case '"': { char* pz = ao_string_cook(pCurCtx->pzScan, &(pCurCtx->lineNo)); if (pz == NULL) goto NUL_error; pz_token = pCurCtx->pzScan; lastToken = DP_EV_STRING; pCurCtx->pzScan = pz; break; } case '<': switch (lex_here_string()) { case SUCCESS: break; case FAILURE: goto BrokenToken; case PROBLEM: return DP_EV_INVALID; } break; case '(': loadScheme(); break; case '\\': if (strncmp(pCurCtx->pzScan+1, "'(", (size_t)2) == 0) { alist_to_autogen_def(); goto scanAgain; } lex_escaped_char(); break; case '`': switch (lex_backquote()) { case FAILURE: goto NUL_error; case PROBLEM: goto scanAgain; case SUCCESS: break; } break; case '/': switch (lex_comment()) { case SUCCESS: goto scanAgain; default: break; } /* FALLTHROUGH */ /* to Invalid input char */ default: BrokenToken: pCurCtx->pzScan = assembleName(pCurCtx->pzScan, &lastToken); break; } /* switch (*pCurCtx->pzScan) */ return lastToken; NUL_error: AG_ABEND(aprf(zErrMsg, pzProg, "unterminated quote in definition", pCurCtx->pzCtxFname, pCurCtx->lineNo)); return DP_EV_INVALID; lex_done: /* * First time through, return the DP_EV_END token. * Second time through, we really finish. */ if (pCurCtx->pzScan == zNil) { pCurCtx->pCtx = pDoneCtx; pDoneCtx = pCurCtx; return DP_EV_INVALID; } pCurCtx->pzScan = (char*)zNil; return DP_EV_END; }
static ssize_t safePrintf(char** ppzBuf, char const * pzFmt, void** argV) { #if ! defined(DEBUG_ENABLED) /* * In normal operation (or during AutoGen testing), seg faults during the * printf operation are caused by bad input data. During AutoGen * development, we do not supply bad printf arguments, so we want to * capture any segfaults when they happen with the correct stack trace. * Therefore, during AutoGen development, we do not protect against seg * faults. */ struct sigaction saSave1; struct sigaction saSave2; { struct sigaction sa; sa.sa_handler = printFault; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(SIGBUS, &sa, &saSave1); sigaction(SIGSEGV, &sa, &saSave2); } { static char const zBadArgs[] = "Bad args to sprintf"; static char const zBadFmt[] = "%s ERROR: %s processing printf format:\n\t%s\n"; /* * IF the sprintfv call below is going to address fault, * THEN ... */ if (sigsetjmp(printJumpEnv, 0) != 0) { #ifndef HAVE_STRSIGNAL extern char* strsignal(int signo); #endif /* * IF the fprintf command in the then clause has not failed yet, * THEN perform that fprintf */ if (sigsetjmp(printJumpEnv, 0) == 0) fprintf(pfTrace, zBadFmt, pzProg, strsignal(printJumpSignal), pzFmt); /* * The "sprintfv" command below faulted, so we exit */ AG_ABEND(zBadArgs); } } #endif /* ! defined(DEBUG_ENABLED) */ { int printSize = asprintfv(ppzBuf, pzFmt, (snv_constpointer*)argV); if ((printSize & ~0xFFFFFU) != 0) /* 1MB max */ AG_ABEND(aprf("asprintfv returned 0x%08X\n", printSize)); #if ! defined(DEBUG_ENABLED) sigaction(SIGBUS, &saSave1, NULL); sigaction(SIGSEGV, &saSave2, NULL); #endif return printSize; } }
/* * process a single scheme expression, yielding text that gets processed * into AutoGen definitions. */ static void alist_to_autogen_def(void) { static char const zSchemeText[] = "Scheme Computed Definitions"; static char const zWrap[] = "(alist->autogen-def %s)"; char* pzText = ++(pCurCtx->pzScan); char* pzEnd = (char*)skipScheme(pzText, pzText + strlen(pzText)); SCM res; size_t res_len; tScanCtx* pCtx; /* * Wrap the scheme expression with the `alist->autogen-def' function */ { char endCh = *pzEnd; *pzEnd = NUL; pzText = aprf(zWrap, pzText); *pzEnd = endCh; } /* * Run the scheme expression. The result is autogen definition text. */ procState = PROC_STATE_GUILE_PRELOAD; res = ag_scm_c_eval_string_from_file_line( pzText, pCurCtx->pzCtxFname, pCurCtx->lineNo ); /* * The result *must* be a string, or we choke. */ if (! AG_SCM_STRING_P(res)) { static char const zEr[] = "Scheme definition expression does not yield string:\n"; AG_ABEND(zEr); } res_len = AG_SCM_STRLEN(res); procState = PROC_STATE_LOAD_DEFS; pCurCtx->pzScan = pzEnd; AGFREE(pzText); /* * Now, push the resulting string onto the input stack * and link the new scan data into the context stack */ pCtx = (tScanCtx*)AGALOC(sizeof(tScanCtx) + 4 + res_len, "lex scan ctx"); pCtx->pCtx = pCurCtx; pCurCtx = pCtx; /* * Set up the rest of the context structure */ AGDUPSTR(pCtx->pzCtxFname, zSchemeText, "scheme text"); pCtx->pzScan = \ pCtx->pzData = (char*)(pCtx+1); pCtx->lineNo = 0; memcpy((void*)(pCtx->pzScan), (void*)AG_SCM_CHARS(res), res_len); pCtx->pzScan[ res_len ] = NUL; /* * At this point, the next token will be obtained * from the newly allocated context structure. * When empty, input will resume from the '}' that we * left as the next input token in the old context. */ }
/* * skipToElseEnd * * Skip through the text to a matching "#endif" or "#else" or * "#elif*def". We do this when we are skipping code due to a failed * "#if*def" test. */ static char* skipToElseEnd(char* pzStart) { char* pzScan = pzStart; char* pzRet; for (;;) { /* * 'pzScan' is pointing to the first character on a line. * Check for a directive on the current line before scanning * later lines. */ if (*pzScan == '#') pzScan++; else { char* pz = strstr(pzScan, zCheckList); if (pz == NULL) AG_ABEND(aprf(zNoEndif, pCurCtx->pzCtxFname, pCurCtx->lineNo)); pzScan = pz + STRSIZE(zCheckList); } while (IS_WHITESPACE_CHAR(*pzScan)) pzScan++; switch (findDirective(pzScan)) { case DIR_ELSE: /* * We found an "else" directive for an "ifdef"/"ifndef" * that we were skipping over. Start processing the text. */ ifdefLevel++; /* FALLTHROUGH */ case DIR_ENDIF: { /* * We reached the end of the "ifdef"/"ifndef" we were * skipping (or we dropped in from above). * Start processing the text. */ char* pz = strchr(pzScan, NL); if (pz != NULL) pzRet = pz+1; else pzRet = pzScan + strlen(pzScan); goto leave_func; } case DIR_IFDEF: case DIR_IFNDEF: /* * We have found a nested "ifdef"/"ifndef". * Call "skipToEndif()" to find *its* end, then * resume looking for our own "endif" or "else". */ pzScan = skipToEndif(pzScan); break; default: /* * We either don't know what it is or we do not care. */ break; } /* switch (findDirective(pzScan)) */ } leave_func: while (pzStart < pzRet) { if (*(pzStart++) == NL) pCurCtx->lineNo++; } return pzRet; }