/*=directive line * * text: * * Alters the current line number and/or file name. You may wish to * use this directive if you extract definition source from other files. * @command{getdefs} uses this mechanism so AutoGen will report the correct * file and approximate line number of any errors found in extracted * definitions. =*/ static char* doDir_line(char* pzArg, char* pzScan) { /* * The sequence must be: #line <number> "file-name-string" * * Start by scanning up to and extracting the line number. */ while (IS_WHITESPACE_CHAR(*pzArg)) pzArg++; if (! IS_DEC_DIGIT_CHAR(*pzArg)) return pzScan; pCurCtx->lineNo = strtol(pzArg, &pzArg, 0); /* * Now extract the quoted file name string. * We dup the string so it won't disappear on us. */ while (IS_WHITESPACE_CHAR(*pzArg)) pzArg++; if (*(pzArg++) != '"') return pzScan; { char* pz = strchr(pzArg, '"'); if (pz == NULL) return pzScan; *pz = NUL; } AGDUPSTR(pCurCtx->pzCtxFname, pzArg, "#line file name"); return pzScan; }
/*=directive define * * arg: name [ <text> ] * * text: * Will add the name to the define list as if it were a DEFINE program * argument. Its value will be the first non-whitespace token following * the name. Quotes are @strong{not} processed. * * After the definitions file has been processed, any remaining entries * in the define list will be added to the environment. =*/ static char* doDir_define(char* pzArg, char* pzScan) { char* pzName = pzArg; /* * Skip any #defines that do not look reasonable */ if (! IS_VAR_FIRST_CHAR(*pzArg)) return pzScan; while (IS_VARIABLE_NAME_CHAR(*pzArg)) pzArg++; /* * IF this is a macro definition (rather than a value def), * THEN we will ignore it. */ if (*pzArg == '(') return pzScan; /* * We have found the end of the name. * IF there is no more data on the line, * THEN we do not have space for the '=' required by PUTENV. * Therefore, move the name back over the "#define" * directive itself, giving us the space needed. */ if (! IS_WHITESPACE_CHAR(*pzArg)) { char* pzS = pzName; char* pzD = (pzName -= 6); *pzArg = NUL; while ((*(pzD++) = *(pzS++)) != NUL) ; pzD[-1] = '='; pzD[ 0] = NUL; } else { /* * Otherwise, insert the '=' and move any data up against it. * We only accept one name-type, space separated token. * We are not ANSI-C. ;-) */ char* pz = pzArg+1; *pzArg++ = '='; while (IS_WHITESPACE_CHAR(*pz)) pz++; for (;;) { if ((*pzArg++ = *pz++) == NUL) break; if (! IS_UNQUOTABLE_CHAR(*pz)) { *pzArg = NUL; break; } } } SET_OPT_DEFINE(pzName); return pzScan; }
/** * Associate a name with a boolean value * * @param[in,out] pp argument list to add to * @param[in] name the name of the "suboption" * @param[in] nm_len the length of the name * @param[in] val the boolean value for the suboption * @param[in] d_len the length of the value * * @returns the new value structure */ static tOptionValue * add_bool(void ** pp, char const * name, size_t nm_len, char const * val, size_t d_len) { size_t sz = nm_len + sizeof(tOptionValue) + 1; tOptionValue * new_val = AGALOC(sz, "bool val"); /* * Scan over whitespace is constrained by "d_len" */ while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { d_len--; val++; } if (d_len == 0) new_val->v.boolVal = 0; else if (IS_DEC_DIGIT_CHAR(*val)) new_val->v.boolVal = (unsigned)atoi(val); else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val); new_val->valType = OPARG_TYPE_BOOLEAN; new_val->pzName = (char *)(new_val + 1); memcpy(new_val->pzName, name, nm_len); new_val->pzName[ nm_len ] = NUL; addArgListEntry(pp, new_val); return new_val; }
/* addBoolValue * * Associate a name with either a string or no value. */ static tOptionValue* addBoolValue( void** pp, char const* pzName, size_t nameLen, char const* pzValue, size_t dataLen ) { tOptionValue* pNV; size_t sz = nameLen + sizeof(*pNV) + 1; pNV = AGALOC( sz, "option name/bool value pair" ); if (pNV == NULL) return NULL; while (IS_WHITESPACE_CHAR(*pzValue) && (dataLen > 0)) { dataLen--; pzValue++; } if (dataLen == 0) pNV->v.boolVal = 0; else if (IS_DEC_DIGIT_CHAR(*pzValue)) pNV->v.boolVal = atoi(pzValue); else pNV->v.boolVal = ! IS_FALSE_TYPE_CHAR(*pzValue); pNV->valType = OPARG_TYPE_BOOLEAN; pNV->pzName = (char*)(pNV + 1); memcpy( pNV->pzName, pzName, nameLen ); pNV->pzName[ nameLen ] = NUL; addArgListEntry( pp, pNV ); return pNV; }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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; }
/* addNumberValue * * Associate a name with either a string or no value. */ static tOptionValue* addNumberValue( void** pp, char const* pzName, size_t nameLen, char const* pzValue, size_t dataLen ) { tOptionValue* pNV; size_t sz = nameLen + sizeof(*pNV) + 1; pNV = AGALOC( sz, "option name/bool value pair" ); if (pNV == NULL) return NULL; while (IS_WHITESPACE_CHAR(*pzValue) && (dataLen > 0)) { dataLen--; pzValue++; } if (dataLen == 0) pNV->v.longVal = 0; else pNV->v.longVal = strtol(pzValue, 0, 0); pNV->valType = OPARG_TYPE_NUMERIC; pNV->pzName = (char*)(pNV + 1); memcpy( pNV->pzName, pzName, nameLen ); pNV->pzName[ nameLen ] = NUL; addArgListEntry( pp, pNV ); return pNV; }
/** * Associate a name with strtol() value, defaulting to zero. * * @param[in,out] pp argument list to add to * @param[in] name the name of the "suboption" * @param[in] nm_len the length of the name * @param[in] val the numeric value for the suboption * @param[in] d_len the length of the value * * @returns the new value structure */ static tOptionValue * add_number(void ** pp, char const * name, size_t nm_len, char const * val, size_t d_len) { size_t sz = nm_len + sizeof(tOptionValue) + 1; tOptionValue * new_val = AGALOC(sz, "int val"); /* * Scan over whitespace is constrained by "d_len" */ while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { d_len--; val++; } if (d_len == 0) new_val->v.longVal = 0; else new_val->v.longVal = strtol(val, 0, 0); new_val->valType = OPARG_TYPE_NUMERIC; new_val->pzName = (char *)(new_val + 1); memcpy(new_val->pzName, name, nm_len); new_val->pzName[ nm_len ] = NUL; addArgListEntry(pp, new_val); return new_val; }
/** * Scan off the xml element name, and the rest of the header, too. * Set the value type to NONE if it ends with "/>". * * @param[in] name the first name character (alphabetic) * @param[out] nm_len the length of the name * @param[out] val set valType field to STRING or NONE. * * @returns the scan resumption point, or NULL on error */ static char const * scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val) { char const * scan = SPN_VALUE_NAME_CHARS(name + 1); *nm_len = (size_t)(scan - name); if (*nm_len > 64) return NULL; val->valType = OPARG_TYPE_STRING; if (IS_WHITESPACE_CHAR(*scan)) { /* * There are attributes following the name. Parse 'em. */ scan = SPN_WHITESPACE_CHARS(scan); scan = parse_attrs(NULL, scan, &option_load_mode, val); if (scan == NULL) return NULL; /* oops */ } if (! IS_END_XML_TOKEN_CHAR(*scan)) return NULL; /* oops */ if (*scan == '/') { /* * Single element XML entries get inserted as an empty string. */ if (*++scan != '>') return NULL; val->valType = OPARG_TYPE_NONE; } return scan+1; }
static token_list_t * alloc_token_list(char const * str) { token_list_t * res; int max_token_ct = 2; /* allow for trailing NULL pointer & NUL on string */ if (str == NULL) goto enoent_res; /* * Trim leading white space. Use "ENOENT" and a NULL return to indicate * an empty string was passed. */ while (IS_WHITESPACE_CHAR(*str)) str++; if (*str == NUL) goto enoent_res; /* * Take an approximate count of tokens. If no quoted strings are used, * it will be accurate. If quoted strings are used, it will be a little * high and we'll squander the space for a few extra pointers. */ { cc_t* pz = (cc_t*)str; do { max_token_ct++; while (! IS_WHITESPACE_CHAR(*++pz)) if (*pz == NUL) goto found_nul; while (IS_WHITESPACE_CHAR(*pz)) pz++; } while (*pz != NUL); found_nul: res = malloc(sizeof(*res) + (pz - (cc_t*)str) + (max_token_ct * sizeof(ch_t*))); } if (res == NULL) errno = ENOMEM; else res->tkn_list[0] = (ch_t*)(res->tkn_list + (max_token_ct - 1)); return res; enoent_res: errno = ENOENT; 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)); }
/** * Print the usage information for a single vendor option. * * @param pOpts the program option descriptor * @param pOD the option descriptor * @param pAT names of the option argument types */ static void prt_one_vendor(tOptions * pOptions, tOptDesc * pOD, arg_types_t * pAT, char const * usefmt) { prt_preamble(pOptions, pOD, pAT); { char z[ 80 ]; char const * pzArgType; /* * Determine the argument type string first on its usage, then, * when the option argument is required, base the type string on the * argument type. */ if (pOD->fOptState & OPTST_ARG_OPTIONAL) { pzArgType = pAT->pzOpt; } else switch (OPTST_GET_ARGTYPE(pOD->fOptState)) { case OPARG_TYPE_NONE: pzArgType = pAT->pzNo; break; case OPARG_TYPE_ENUMERATION: pzArgType = pAT->pzKey; break; case OPARG_TYPE_FILE: pzArgType = pAT->pzFile; break; case OPARG_TYPE_MEMBERSHIP: pzArgType = pAT->pzKeyL; break; case OPARG_TYPE_BOOLEAN: pzArgType = pAT->pzBool; break; case OPARG_TYPE_NUMERIC: pzArgType = pAT->pzNum; break; case OPARG_TYPE_HIERARCHY: pzArgType = pAT->pzNest; break; case OPARG_TYPE_STRING: pzArgType = pAT->pzStr; break; case OPARG_TYPE_TIME: pzArgType = pAT->pzTime; break; default: goto bogus_desc; } while (IS_WHITESPACE_CHAR(*pzArgType)) pzArgType++; if (*pzArgType == NUL) snprintf(z, sizeof(z), "%s", pOD->pz_Name); else snprintf(z, sizeof(z), "%s=%s", pOD->pz_Name, pzArgType); fprintf(option_usage_fp, usefmt, z, pOD->pzText); switch (OPTST_GET_ARGTYPE(pOD->fOptState)) { case OPARG_TYPE_ENUMERATION: case OPARG_TYPE_MEMBERSHIP: displayEnum = (pOD->pOptProc != NULL) ? AG_TRUE : displayEnum; } } return; bogus_desc: fprintf(stderr, zInvalOptDesc, pOD->pz_Name); exit(EX_SOFTWARE); }
/* handleDirective * * "pzText" points to a "<?" sequence. * For the moment, we only handle "<?program" directives. */ static char* handleDirective( tOptions* pOpts, char* pzText ) { char ztitle[32] = "<?"; size_t title_len = strlen( zProg ); size_t name_len; if ( (strncmp( pzText+2, zProg, title_len ) != 0) || (! IS_WHITESPACE_CHAR(pzText[title_len+2])) ) { pzText = strchr( pzText+2, '>' ); if (pzText != NULL) pzText++; return pzText; } name_len = strlen( pOpts->pzProgName ); strcpy( ztitle+2, zProg ); title_len += 2; do { pzText += title_len; if (IS_WHITESPACE_CHAR(*pzText)) { while (IS_WHITESPACE_CHAR(*++pzText)) ; if ( (strneqvcmp( pzText, pOpts->pzProgName, (int)name_len) == 0) && (pzText[name_len] == '>')) { pzText += name_len + 1; break; } } pzText = strstr( pzText, ztitle ); } while (pzText != NULL); return pzText; }
LOCAL void mungeString(char* pzTxt, tOptionLoadMode mode) { char* pzE; if (mode == OPTION_LOAD_KEEP) return; if (IS_WHITESPACE_CHAR(*pzTxt)) { char* pzS = pzTxt; char* pzD = pzTxt; while (IS_WHITESPACE_CHAR(*++pzS)) ; while ((*(pzD++) = *(pzS++)) != NUL) ; pzE = pzD-1; } else pzE = pzTxt + strlen(pzTxt); while ((pzE > pzTxt) && IS_WHITESPACE_CHAR(pzE[-1])) pzE--; *pzE = NUL; if (mode == OPTION_LOAD_UNCOOKED) return; switch (*pzTxt) { default: return; case '"': case '\'': break; } switch (pzE[-1]) { default: return; case '"': case '\'': break; } (void)ao_string_cook(pzTxt, NULL); }
static char* assembleArgValue(char* pzTxt, tOptionLoadMode mode) { static char const zBrk[] = " \t\n:="; char* pzEnd = strpbrk(pzTxt, zBrk); int space_break; /* * Not having an argument to a configurable name is okay. */ if (pzEnd == NULL) return pzTxt + strlen(pzTxt); /* * If we are keeping all whitespace, then the modevalue starts with the * character that follows the end of the configurable name, regardless * of which character caused it. */ if (mode == OPTION_LOAD_KEEP) { *(pzEnd++) = NUL; return pzEnd; } /* * If the name ended on a white space character, remember that * because we'll have to skip over an immediately following ':' or '=' * (and the white space following *that*). */ space_break = IS_WHITESPACE_CHAR(*pzEnd); *(pzEnd++) = NUL; while (IS_WHITESPACE_CHAR(*pzEnd)) pzEnd++; if (space_break && ((*pzEnd == ':') || (*pzEnd == '='))) while (IS_WHITESPACE_CHAR(*++pzEnd)) ; return pzEnd; }
/*=gfunc version_compare * * what: compare two version numbers * general_use: * * exparg: op, comparison operator * exparg: v1, first version * exparg: v2, compared-to version * * doc: Converts v1 and v2 strings into 64 bit values and returns the * result of running 'op' on those values. It assumes that the version * is a 1 to 4 part dot-separated series of numbers. Suffixes like, * "5pre4" or "5-pre4" will be interpreted as two numbers. The first * number ("5" in this case) will be decremented and the number after * the "pre" will be added to 0xC000. (Unless your platform is unable * to support 64 bit integer arithmetic. Then it will be added to 0xC0.) * Consequently, these yield true: * @example * (version-compare > "5.8.5" "5.8.5-pre4") * (version-compare > "5.8.5-pre10" "5.8.5-pre4") * @end example =*/ static ver_type_t str2int_ver(char* pz) { char* pzStr = pz; ver_type_t val = 0; int ix = 4; while (--ix >= 0) { unsigned int v; val <<= VER_UNIT_SHIFT; while (IS_WHITESPACE_CHAR(*pz)) pz++; next_number: if (! IS_DEC_DIGIT_CHAR(*pz)) break; v = (unsigned int)strtoul(pz, &pz, 0) & ((1 << VER_UNIT_SHIFT) - 1); if (pz == NULL) break; val += v; if (*pz == '-') pz++; switch (*pz) { case 'p': if ((pz[1] == 'r') && (pz[2] == 'e')) { pz += 3; val = (val << 2) - 1; val <<= (VER_UNIT_SHIFT - 2); if (--ix < 0) goto leave_str2int_ver; goto next_number; } /* FALLTHROUGH */ default: goto leave_str2int_ver; case '.': if (! IS_DEC_DIGIT_CHAR(*(++pz))) goto leave_str2int_ver; break; } } leave_str2int_ver: ; while (--ix >= 0) val <<= VER_UNIT_SHIFT; if (OPT_VALUE_TRACE >= TRACE_EXPRESSIONS) fprintf(pfTrace, "0x%016llX <<== '%s'\n", (long long)val, pzStr); return val; }
/*=macfunc INCLUDE * * what: Read in and emit a template block * handler_proc: * load_proc: * * desc: * * The entire contents of the named file is inserted at this point. * The contents of the file are processed for macro expansion. The * arguments are eval-ed, so you may compute the name of the file to * be included. The included file must not contain any incomplete * function blocks. Function blocks are template text beginning with * any of the macro functions @samp{CASE}, @samp{DEFINE}, @samp{FOR}, * @samp{IF} and @samp{WHILE}; extending through their respective * terminating macro functions. =*/ tMacro* mFunc_Include(tTemplate* pT, tMacro* pMac) { tTemplate * pNewTpl; ag_bool needFree; char const * pzFile = evalExpression(&needFree); tMacro* pM; if (*pzFile != NUL) { pNewTpl = loadTemplate(pzFile, pT->pzTplFile); /* * Strip off trailing white space from included templates */ pM = pNewTpl->aMacros + (pNewTpl->macroCt - 1); if (pM->funcCode == FTYP_TEXT) { char* pz = pNewTpl->pzTemplText + pM->ozText; char* pzE = pz + strlen(pz); while ((pzE > pz) && IS_WHITESPACE_CHAR(pzE[-1])) --pzE; /* * IF there is no text left, remove the macro entirely */ if (pz == pzE) pNewTpl->macroCt--; else *pzE = NUL; } if (OPT_VALUE_TRACE > TRACE_DEBUG_MESSAGE) { fprintf(pfTrace, TRACE_FN_INC_TPL, pNewTpl->pzTplFile); if (OPT_VALUE_TRACE == TRACE_EVERYTHING) fprintf(pfTrace, TRACE_FN_INC_LINE, pCurTemplate->pzTplFile, pMac->lineNo); } generateBlock(pNewTpl, pNewTpl->aMacros, pNewTpl->aMacros + pNewTpl->macroCt); unloadTemplate(pNewTpl); pCurTemplate = pT; } if (needFree) AGFREE((void*)pzFile); return pMac + 1; }
static void trim_whitespace(void) { char* pz = pCurCtx->pzScan; if (*pz == NL) pCurCtx->lineNo++; *(pz++) = NUL; /* * This ensures that any names found previously * are NUL terminated. */ while (IS_WHITESPACE_CHAR(*pz)) { if (*pz == NL) pCurCtx->lineNo++; pz++; } pCurCtx->pzScan = pz; }
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; }
static void lex_escaped_char(void) { char* pz = strchr(pCurCtx->pzScan, ';'); for (;;) { if (pz == NULL) { pz = pCurCtx->pzScan + strlen(pCurCtx->pzScan); break; } if (IS_WHITESPACE_CHAR(pz[1])) { *pz = NUL; pz[1] = ';'; break; } pz = strchr(pz+1, ';'); } lastToken = DP_EV_STRING; pz_token = pz; }
/** * handle AutoOpts mode flags */ static char * aoflags_directive(tOptions * pOpts, char * pzText) { char * pz = pzText; while (IS_WHITESPACE_CHAR(*++pz)) ; pzText = strchr(pz, '>'); if (pzText != NULL) { size_t len = pzText - pz; char * ftxt = AGALOC(len + 1, "aoflags"); memcpy(ftxt, pz, len); ftxt[len] = NUL; set_usage_flags(pOpts, ftxt); AGFREE(ftxt); pzText++; } return pzText; }
LOCAL void mungeString(char * txt, tOptionLoadMode mode) { char * pzE; if (mode == OPTION_LOAD_KEEP) return; if (IS_WHITESPACE_CHAR(*txt)) { char * pzS = SPN_WHITESPACE_CHARS(txt+1); size_t l = strlen(pzS) + 1; memmove(txt, pzS, l); pzE = txt + l - 1; } else pzE = txt + strlen(txt); pzE = SPN_WHITESPACE_BACK(txt, pzE); *pzE = NUL; if (mode == OPTION_LOAD_UNCOOKED) return; switch (*txt) { default: return; case '"': case '\'': break; } switch (pzE[-1]) { default: return; case '"': case '\'': break; } (void)ao_string_cook(txt, NULL); }
/** * handle program segmentation of config file. */ static char * program_directive(tOptions * pOpts, char * pzText) { static char const ttlfmt[] = "<?"; size_t ttl_len = sizeof(ttlfmt) + strlen(zCfgProg); char * ttl = AGALOC(ttl_len, "prog title"); size_t name_len = strlen(pOpts->pzProgName); memcpy(ttl, ttlfmt, sizeof(ttlfmt) - 1); strcpy(ttl + sizeof(ttlfmt) - 1, zCfgProg); do { while (IS_WHITESPACE_CHAR(*++pzText)) ; if ( (strneqvcmp(pzText, pOpts->pzProgName, (int)name_len) == 0) && (IS_END_XML_TOKEN_CHAR(pzText[name_len])) ) { pzText += name_len; break; } pzText = strstr(pzText, ttl); } while (pzText != NULL); AGFREE(ttl); if (pzText != NULL) for (;;) { if (*pzText == NUL) { pzText = NULL; break; } if (*(pzText++) == '>') break; } return pzText; }
static char * assemble_arg_val(char * txt, tOptionLoadMode mode) { char * end = strpbrk(txt, ARG_BREAK_STR); int space_break; /* * Not having an argument to a configurable name is okay. */ if (end == NULL) return txt + strlen(txt); /* * If we are keeping all whitespace, then the modevalue starts with the * character that follows the end of the configurable name, regardless * of which character caused it. */ if (mode == OPTION_LOAD_KEEP) { *(end++) = NUL; return end; } /* * If the name ended on a white space character, remember that * because we'll have to skip over an immediately following ':' or '=' * (and the white space following *that*). */ space_break = IS_WHITESPACE_CHAR(*end); *(end++) = NUL; end = SPN_WHITESPACE_CHARS(end); if (space_break && ((*end == ':') || (*end == '='))) end = SPN_WHITESPACE_CHARS(end+1); return end; }
/** * "txt" points to the start of some value name. * The end of the entry is the end of the line that is not preceded by * a backslash escape character. The string value is always processed * in "cooked" mode. */ static char * handle_cfg(tOptions * opts, tOptState * ost, char * txt, int dir) { char* pzName = txt++; char* pzEnd = strchr(txt, NL); if (pzEnd == NULL) return txt + strlen(txt); txt = SPN_VALUE_NAME_CHARS(txt); txt = SPN_WHITESPACE_CHARS(txt); if (txt > pzEnd) { name_only: *pzEnd++ = NUL; loadOptionLine(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED); return pzEnd; } /* * Either the first character after the name is a ':' or '=', * or else we must have skipped over white space. Anything else * is an invalid format and we give up parsing the text. */ if ((*txt == '=') || (*txt == ':')) { txt = SPN_WHITESPACE_CHARS(txt+1); if (txt > pzEnd) goto name_only; } else if (! IS_WHITESPACE_CHAR(txt[-1])) return NULL; /* * IF the value is continued, remove the backslash escape and push "pzEnd" * on to a newline *not* preceded by a backslash. */ if (pzEnd[-1] == '\\') { char* pcD = pzEnd-1; char* pcS = pzEnd; for (;;) { char ch = *(pcS++); switch (ch) { case NUL: pcS = NULL; /* FALLTHROUGH */ case NL: *pcD = NUL; pzEnd = pcS; goto copy_done; case '\\': if (*pcS == NL) ch = *(pcS++); /* FALLTHROUGH */ default: *(pcD++) = ch; } } copy_done:; } else { /* * The newline was not preceded by a backslash. NUL it out */ *(pzEnd++) = NUL; } /* * "pzName" points to what looks like text for one option/configurable. * It is NUL terminated. Process it. */ loadOptionLine(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED); return pzEnd; }
/*=export_func ao_string_tokenize * * what: tokenize an input string * * arg: + char const* + string + string to be tokenized + * * ret_type: token_list_t* * ret_desc: pointer to a structure that lists each token * * doc: * * This function will convert one input string into a list of strings. * The list of strings is derived by separating the input based on * white space separation. However, if the input contains either single * or double quote characters, then the text after that character up to * a matching quote will become the string in the list. * * The returned pointer should be deallocated with @code{free(3C)} when * are done using the data. The data are placed in a single block of * allocated memory. Do not deallocate individual token/strings. * * The structure pointed to will contain at least these two fields: * @table @samp * @item tkn_ct * The number of tokens found in the input string. * @item tok_list * An array of @code{tkn_ct + 1} pointers to substring tokens, with * the last pointer set to NULL. * @end table * * There are two types of quoted strings: single quoted (@code{'}) and * double quoted (@code{"}). Singly quoted strings are fairly raw in that * escape characters (@code{\\}) are simply another character, except when * preceding the following characters: * @example * @code{\\} double backslashes reduce to one * @code{'} incorporates the single quote into the string * @code{\n} suppresses both the backslash and newline character * @end example * * Double quote strings are formed according to the rules of string * constants in ANSI-C programs. * * example: * @example * #include <stdlib.h> * int ix; * token_list_t* ptl = ao_string_tokenize(some_string) * for (ix = 0; ix < ptl->tkn_ct; ix++) * do_something_with_tkn(ptl->tkn_list[ix]); * free(ptl); * @end example * Note that everything is freed with the one call to @code{free(3C)}. * * err: * NULL is returned and @code{errno} will be set to indicate the problem: * @itemize @bullet * @item * @code{EINVAL} - There was an unterminated quoted string. * @item * @code{ENOENT} - The input string was empty. * @item * @code{ENOMEM} - There is not enough memory. * @end itemize =*/ token_list_t* ao_string_tokenize(char const* str) { token_list_t* res = alloc_token_list(str); ch_t* pzDest; /* * Now copy each token into the output buffer. */ if (res == NULL) return res; pzDest = (ch_t*)(res->tkn_list[0]); res->tkn_ct = 0; do { res->tkn_list[ res->tkn_ct++ ] = pzDest; for (;;) { int ch = (ch_t)*str; if (IS_WHITESPACE_CHAR(ch)) { found_white_space: str = SPN_WHITESPACE_CHARS(str+1); break; } switch (ch) { case '"': copy_cooked(&pzDest, &str); if (str == NULL) { free(res); errno = EINVAL; return NULL; } if (IS_WHITESPACE_CHAR(*str)) goto found_white_space; break; case '\'': copy_raw(&pzDest, &str); if (str == NULL) { free(res); errno = EINVAL; return NULL; } if (IS_WHITESPACE_CHAR(*str)) goto found_white_space; break; case NUL: goto copy_done; default: str++; *(pzDest++) = (unsigned char)ch; } } copy_done:; /* * NUL terminate the last token and see if we have any more tokens. */ *(pzDest++) = NUL; } while (*str != NUL); res->tkn_list[ res->tkn_ct ] = NULL; return res; }
/** * Will add the name to the define list as if it were a DEFINE program * argument. Its value will be the first non-whitespace token following * the name. Quotes are @strong{not} processed. * * After the definitions file has been processed, any remaining entries * in the define list will be added to the environment. */ char * doDir_define(directive_enum_t id, char const * dir, char * scan_next) { char * def_name = SPN_WHITESPACE_CHARS(dir); (void)id; /* * Skip any #defines that do not look reasonable */ if (! IS_VAR_FIRST_CHAR(*def_name)) return scan_next; dir = SPN_VARIABLE_NAME_CHARS(def_name); /* * IF this is a macro definition (rather than a value def), * THEN we will ignore it. */ if (*dir == '(') return scan_next; /* * We have found the end of the name. * IF there is no more data on the line, * THEN we do not have space for the '=' required by PUTENV. * Therefore, move the name back over the "#define" * directive itself, giving us the space needed. */ if (! IS_WHITESPACE_CHAR(*dir)) { char * pzS = (char *)dir; char * pzD = (def_name - 6); *pzS = NUL; // whatever it used to be, it is a NUL now. pzS = def_name; while ((*(pzD++) = *(pzS++)) != NUL) ; pzD[-1] = '='; pzD[ 0] = NUL; def_name -= 6; } else { /* * Otherwise, insert the '=' and move any data up against it. * We only accept one name-type, space separated token. * We are not ANSI-C. ;-) */ char * pz = (char *)(dir++); *pz++ = '='; dir = SPN_WHITESPACE_CHARS(dir); /* * Copy chars for so long as it is not NUL and does not require quoting */ for (;;) { if ((*pz++ = *dir++) == NUL) break; if (! IS_UNQUOTABLE_CHAR(*dir)) { *pz = NUL; break; } } } SET_OPT_DEFINE(def_name); return scan_next; }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * A quoted string has been found. * Find the end of it and compress any escape sequences. */ static bool contiguous_quote(char ** pps, char * pq, int * lnct_p) { char * ps = *pps + 1; for (;;) { while (IS_WHITESPACE_CHAR(*ps)) if (*(ps++) == NL) (*lnct_p)++; /* * IF the next character is a quote character, * THEN we will concatenate the strings. */ switch (*ps) { case '"': case '\'': *pq = *(ps++); /* assign new quote character and return */ *pps = ps; return true; case '/': /* * Allow for a comment embedded in the concatenated string. */ switch (ps[1]) { default: *pps = NULL; return false; case '/': /* * Skip to end of line */ ps = strchr(ps, NL); if (ps == NULL) { *pps = NULL; return false; } break; case '*': { char * p = strstr( ps+2, "*/" ); /* * Skip to terminating star slash */ if (p == NULL) { *pps = NULL; return false; } while (ps < p) { if (*(ps++) == NL) (*lnct_p)++; } ps = p + 2; } } continue; default: /* * The next non-whitespace character is not a quote. * The series of quoted strings has come to an end. */ *pps = ps; return false; } } }
/*=macfunc SELECT * * what: Selection block for CASE function * in-context: * alias: | ~ | = | * | ! | + | * unload-proc: * * desc: * This macro selects a block of text by matching an expression * against the sample text expression evaluated in the @code{CASE} * macro. @xref{CASE}. * * You do not specify a @code{SELECT} macro with the word ``select''. * Instead, you must use one of the 19 match operators described in * the @code{CASE} macro description. =*/ static tMacro* mLoad_Select(tTemplate * pT, tMacro* pMac, char const ** ppzScan) { char const * pzScan = *ppzScan; /* text after macro */ char* pzCopy = pT->pNext; /* next text dest */ char const * pzSrc = (char*)pMac->ozText; /* macro text */ long srcLen = pMac->res; /* macro len */ static char const zInvSel[] = "Invalid selection clause"; int typ = (int)FTYP_SELECT_COMPARE_FULL; /* * Set the global macro loading mode */ papLoadProc = apCaseLoad; pMac->res = 0; if (srcLen == 0) AG_ABEND_IN(pT, pMac, "Empty macro text"); /* * IF the first character is an asterisk, * THEN the match can start anywhere in the string */ if (*pzSrc == '*') { pzSrc++; if (IS_WHITESPACE_CHAR(*pzSrc) || (*pzSrc == NUL)) { typ = (int)FTYP_SELECT_MATCH_ANYTHING; srcLen = 0; pMac->ozText = 0; goto selection_done; } typ |= (int)FTYP_SELECT_COMPARE_SKP_START; } /* * The next character must indicate whether we are * pattern matching ('~') or doing string compares ('=') */ switch (*pzSrc++) { case '~': /* * Or in the pattern matching bit */ typ |= (int)FTYP_SELECT_MATCH_FULL; pMac->res = REG_EXTENDED; /* FALLTHROUGH */ case '=': /* * IF the '~' or '=' is doubled, * THEN it is a case sensitive match. Skip over the char. * ELSE or in the case insensitive bit */ if (pzSrc[0] == pzSrc[-1]) { pzSrc++; } else { typ |= (int)FTYP_SELECT_EQUIVALENT_FULL; } break; case '!': case '+': switch (*pzSrc) { case 'e': case 'E': break; default: goto bad_sel; } if ((pzSrc[1] != NUL) && (! IS_WHITESPACE_CHAR(pzSrc[1]))) goto bad_sel; typ = (int)((pzSrc[-1] == '!') ? FTYP_SELECT_MATCH_NONEXISTENCE : FTYP_SELECT_MATCH_EXISTENCE); srcLen = 0; pMac->ozText = 0; goto selection_done; default: bad_sel: AG_ABEND_IN(pT, pMac, zInvSel); } /* * IF the last character is an asterisk, * THEN the match may end before the test string ends. * OR in the "may end early" bit. */ if (*pzSrc == '*') { pzSrc++; typ |= (int)FTYP_SELECT_COMPARE_SKP_END; } if (! IS_WHITESPACE_CHAR(*pzSrc)) AG_ABEND_IN(pT, pMac, zInvSel); while (IS_WHITESPACE_CHAR(*pzSrc)) pzSrc++; srcLen -= pzSrc - (char const *)pMac->ozText; if (srcLen <= 0) AG_ABEND_IN(pT, pMac, zInvSel); /* * See if we are doing case insensitive regular expressions */ if ( (typ & (int)FTYP_SELECT_EQV_MATCH_FULL) == (int)FTYP_SELECT_EQV_MATCH_FULL) { int bitSet; pMac->res = REG_EXTENDED | REG_ICASE; /* * Turn off the case comparison mode for regular expressions. * We don't have to worry about it. It is done for us. */ bitSet = ~(int)FTYP_SELECT_EQUIVALENT_FULL; bitSet |= (int)FTYP_SELECT_COMPARE_FULL; /* dont turn this bit off! */ typ &= bitSet; } /* * Copy the expression */ pzScan = pzCopy; pMac->ozText = (pzCopy - pT->pzTemplText); if (typ == (int)FTYP_SELECT_EQUIVALENT) { do { *(pzCopy++) = toupper(*(pzSrc++)); } while (--srcLen > 0); } else { do { *(pzCopy++) = *(pzSrc++); } while (--srcLen > 0); } *(pzCopy++) = NUL; *(pzCopy++) = NUL; pT->pNext = pzCopy; if ((*pzScan == '"') || (*pzScan == '\'')) { void * ptr = (void *)pzScan; spanQuote(ptr); } selection_done: pMac->funcCode = (teFuncType)typ; current_case.pSelect->sibIndex = (pMac - pT->aMacros); current_case.pSelect = (tMacro*)pMac; return pMac + 1; }
LOCAL void set_usage_flags(tOptions * opts, char const * flg_txt) { typedef struct { size_t fnm_len; uint32_t fnm_mask; char const * fnm_name; } ao_flag_names_t; # define _aof_(_n, _f) AOUF_ ## _n ## _ID, typedef enum { AOFLAG_TABLE AOUF_COUNT } ao_flag_id_t; # undef _aof_ # define _aof_(_n, _f) AOUF_ ## _n = (1 << AOUF_ ## _n ## _ID), typedef enum { AOFLAG_TABLE } ao_flags_t; # undef _aof_ # define _aof_(_n, _f) { sizeof(#_n)-1, _f, #_n }, static ao_flag_names_t const fn_table[AOUF_COUNT] = { AOFLAG_TABLE }; # undef _aof_ ao_flags_t flg = 0; if (flg_txt == NULL) { flg_txt = getenv("AUTOOPTS_USAGE"); if (flg_txt == NULL) return; } while (IS_WHITESPACE_CHAR(*flg_txt)) flg_txt++; if (*flg_txt == NUL) return; for (;;) { int ix = 0; ao_flag_names_t const * fnt = fn_table; for (;;) { if (strneqvcmp(flg_txt, fnt->fnm_name, fnt->fnm_len) == 0) break; if (++ix >= AOUF_COUNT) return; fnt++; } /* * Make sure we have a full match. Look for whitespace, * a comma, or a NUL byte. */ if (! IS_END_LIST_ENTRY_CHAR(flg_txt[fnt->fnm_len])) return; flg |= 1 << ix; flg_txt += fnt->fnm_len; while (IS_WHITESPACE_CHAR(*flg_txt)) flg_txt++; if (*flg_txt == NUL) break; if (*flg_txt == ',') { /* * skip the comma and following white space */ while (IS_WHITESPACE_CHAR(*++flg_txt)) ; if (*flg_txt == NUL) break; } } { ao_flag_names_t const * fnm = fn_table; while (flg != 0) { if ((flg & 1) != 0) { if ((fnm->fnm_mask & OPTPROC_LONGOPT) != 0) opts->fOptSet &= fnm->fnm_mask; else opts->fOptSet |= fnm->fnm_mask; } flg >>= 1; fnm++; } } }
/* * Load an option from a block of text. The text must start with the * configurable/option name and be followed by its associated value. * That value may be processed in any of several ways. See "tOptionLoadMode" * in autoopts.h. */ LOCAL void loadOptionLine( tOptions* pOpts, tOptState* pOS, char* pzLine, tDirection direction, tOptionLoadMode load_mode ) { while (IS_WHITESPACE_CHAR(*pzLine)) pzLine++; { char* pzArg = assembleArgValue(pzLine, load_mode); if (! SUCCESSFUL(opt_find_long(pOpts, pzLine, pOS))) return; if (pOS->flags & OPTST_NO_INIT) return; pOS->pzOptArg = pzArg; } switch (pOS->flags & (OPTST_IMM|OPTST_DISABLE_IMM)) { case 0: /* * The selected option has no immediate action. * THEREFORE, if the direction is PRESETTING * THEN we skip this option. */ if (PRESETTING(direction)) return; break; case OPTST_IMM: if (PRESETTING(direction)) { /* * We are in the presetting direction with an option we handle * immediately for enablement, but normally for disablement. * Therefore, skip if disabled. */ if ((pOS->flags & OPTST_DISABLED) == 0) return; } else { /* * We are in the processing direction with an option we handle * immediately for enablement, but normally for disablement. * Therefore, skip if NOT disabled. */ if ((pOS->flags & OPTST_DISABLED) != 0) return; } break; case OPTST_DISABLE_IMM: if (PRESETTING(direction)) { /* * We are in the presetting direction with an option we handle * immediately for disablement, but normally for disablement. * Therefore, skip if NOT disabled. */ if ((pOS->flags & OPTST_DISABLED) != 0) return; } else { /* * We are in the processing direction with an option we handle * immediately for disablement, but normally for disablement. * Therefore, skip if disabled. */ if ((pOS->flags & OPTST_DISABLED) == 0) return; } break; case OPTST_IMM|OPTST_DISABLE_IMM: /* * The selected option is always for immediate action. * THEREFORE, if the direction is PROCESSING * THEN we skip this option. */ if (PROCESSING(direction)) return; break; } /* * Fix up the args. */ if (OPTST_GET_ARGTYPE(pOS->pOD->fOptState) == OPARG_TYPE_NONE) { if (*pOS->pzOptArg != NUL) return; pOS->pzOptArg = NULL; } else if (pOS->pOD->fOptState & OPTST_ARG_OPTIONAL) { if (*pOS->pzOptArg == NUL) pOS->pzOptArg = NULL; else { AGDUPSTR(pOS->pzOptArg, pOS->pzOptArg, "option argument"); pOS->flags |= OPTST_ALLOC_ARG; } } else { if (*pOS->pzOptArg == NUL) pOS->pzOptArg = zNil; else { AGDUPSTR(pOS->pzOptArg, pOS->pzOptArg, "option argument"); pOS->flags |= OPTST_ALLOC_ARG; } } { tOptionLoadMode sv = option_load_mode; option_load_mode = load_mode; handle_opt(pOpts, pOS); option_load_mode = sv; } }